|
| 1 | +--- |
| 2 | +phase: quick-260521-afl |
| 3 | +plan: 01 |
| 4 | +type: execute |
| 5 | +wave: 1 |
| 6 | +depends_on: [] |
| 7 | +files_modified: |
| 8 | + - cmd/docker-deploy/main.go |
| 9 | + - cmd/docker-deploy/main_test.go |
| 10 | +autonomous: true |
| 11 | +requirements: [] |
| 12 | +must_haves: |
| 13 | + truths: |
| 14 | + - "Deploy complete message omits the colon when using default port 22" |
| 15 | + - "Deploy complete message includes :PORT when a non-default port is used" |
| 16 | + artifacts: |
| 17 | + - path: cmd/docker-deploy/main.go |
| 18 | + provides: "Fixed deploy complete status message formatting" |
| 19 | + contains: "formatHostTarget" |
| 20 | + - path: cmd/docker-deploy/main_test.go |
| 21 | + provides: "Tests for both default-port and custom-port message formats" |
| 22 | + key_links: |
| 23 | + - from: "runDeploy()" |
| 24 | + to: "fmt.Fprintf(os.Stdout, Deploy complete...)" |
| 25 | + via: "formatHostTarget() helper" |
| 26 | + pattern: "formatHostTarget" |
| 27 | +--- |
| 28 | + |
| 29 | +<objective> |
| 30 | +Fix the deploy completion status message to omit the colon separator when the default SSH port (22) is used. |
| 31 | + |
| 32 | +Purpose: The current format `host:/path` is confusing — it looks like a host:port prefix with an empty port. The correct formats are `host/path` (default port) and `host:2222/path` (custom port). |
| 33 | +Output: Updated main.go with a `formatHostTarget` helper and a test covering both cases. |
| 34 | +</objective> |
| 35 | + |
| 36 | +<execution_context> |
| 37 | +@/Users/mniedre/git/docker-deploy/.claude/get-shit-done/workflows/execute-plan.md |
| 38 | +@/Users/mniedre/git/docker-deploy/.claude/get-shit-done/templates/summary.md |
| 39 | +</execution_context> |
| 40 | + |
| 41 | +<context> |
| 42 | +@/Users/mniedre/git/docker-deploy/.planning/PROJECT.md |
| 43 | +@/Users/mniedre/git/docker-deploy/.planning/ROADMAP.md |
| 44 | +@/Users/mniedre/git/docker-deploy/.planning/STATE.md |
| 45 | +@/Users/mniedre/git/docker-deploy/cmd/docker-deploy/main.go |
| 46 | +@/Users/mniedre/git/docker-deploy/cmd/docker-deploy/main_test.go |
| 47 | +</context> |
| 48 | + |
| 49 | +<tasks> |
| 50 | + |
| 51 | +<task type="auto" tdd="true"> |
| 52 | + <name>Task 1: Fix deploy complete message format and add tests</name> |
| 53 | + <files>cmd/docker-deploy/main.go, cmd/docker-deploy/main_test.go</files> |
| 54 | + <behavior> |
| 55 | + - formatHostTarget("192.168.1.99", 22, "/opt/test-deploy") -> "192.168.1.99/opt/test-deploy" |
| 56 | + - formatHostTarget("192.168.1.99", 2222, "/opt/test-deploy") -> "192.168.1.99:2222/opt/test-deploy" |
| 57 | + - formatHostTarget("192.168.1.99", 0, "/opt/test-deploy") -> "192.168.1.99/opt/test-deploy" (0 treated as default) |
| 58 | + </behavior> |
| 59 | + <action> |
| 60 | + In main_test.go, add TestFormatHostTarget with three table-driven sub-tests covering the three cases above. Run the tests — they will fail (RED) because formatHostTarget does not exist yet. |
| 61 | + |
| 62 | + In main.go, add a package-level helper function: |
| 63 | + |
| 64 | + func formatHostTarget(hostname string, port int, path string) string { |
| 65 | + if port == 0 || port == 22 { |
| 66 | + return hostname + path |
| 67 | + } |
| 68 | + return fmt.Sprintf("%s:%d%s", hostname, port, path) |
| 69 | + } |
| 70 | + |
| 71 | + Replace line 346 in runDeploy(): |
| 72 | + // Before: |
| 73 | + fmt.Fprintf(os.Stdout, "Deploy complete: %d files copied to %s:%s\n", fileCount, resolved.Host.Hostname, resolved.Path) |
| 74 | + // After: |
| 75 | + fmt.Fprintf(os.Stdout, "Deploy complete: %d files copied to %s\n", fileCount, formatHostTarget(resolved.Host.Hostname, port, resolved.Path)) |
| 76 | + |
| 77 | + The variable `port` is already in scope at line 346 (resolved in step 5, assigned to dialCfg). |
| 78 | + </action> |
| 79 | + <verify> |
| 80 | + <automated>cd /Users/mniedre/git/docker-deploy && go test ./cmd/docker-deploy/... -run TestFormatHostTarget -v</automated> |
| 81 | + </verify> |
| 82 | + <done> |
| 83 | + TestFormatHostTarget passes for all three cases. go test ./... passes with no regressions. |
| 84 | + </done> |
| 85 | +</task> |
| 86 | + |
| 87 | +</tasks> |
| 88 | + |
| 89 | +<threat_model> |
| 90 | +## Trust Boundaries |
| 91 | + |
| 92 | +| Boundary | Description | |
| 93 | +|----------|-------------| |
| 94 | +| stdout output | Informational message only — no user-controlled data in the format string itself | |
| 95 | + |
| 96 | +## STRIDE Threat Register |
| 97 | + |
| 98 | +| Threat ID | Category | Component | Disposition | Mitigation Plan | |
| 99 | +|-----------|----------|-----------|-------------|-----------------| |
| 100 | +| T-afl-01 | Information Disclosure | formatHostTarget stdout | accept | Port number in success message is benign — user already knows their own port config | |
| 101 | +</threat_model> |
| 102 | + |
| 103 | +<verification> |
| 104 | +Run full test suite to confirm no regressions: |
| 105 | + |
| 106 | +``` |
| 107 | +cd /Users/mniedre/git/docker-deploy && go test ./... |
| 108 | +``` |
| 109 | + |
| 110 | +Manual smoke-check of output format: |
| 111 | +- Default port (22 or 0): message ends with `host/path` (no colon before path) |
| 112 | +- Custom port (e.g. 2222): message ends with `host:2222/path` |
| 113 | +</verification> |
| 114 | + |
| 115 | +<success_criteria> |
| 116 | +- `go test ./cmd/docker-deploy/... -run TestFormatHostTarget` passes (3 sub-tests) |
| 117 | +- `go test ./...` passes with no regressions |
| 118 | +- Line 346 of main.go uses `formatHostTarget()` instead of bare `%s:%s` |
| 119 | +- Default port 22 produces `host/path` format (no colon) |
| 120 | +- Custom port produces `host:PORT/path` format |
| 121 | +</success_criteria> |
| 122 | + |
| 123 | +<output> |
| 124 | +After completion, create `.planning/quick/260521-afl-fix-deploy-complete-status-message-omit-/260521-afl-SUMMARY.md` |
| 125 | +</output> |
0 commit comments