Skip to content

Commit 2b79869

Browse files
[Crane: crane-migration-python-to-go-full-apm-cli-rewrite] Iteration 27: Add real Python-vs-Go stdout comparison tests and fix help text parity
- Add cmd/apm/parity_stdout_test.go: 35 TestParityStdout* tests comparing Python vs Go stdout for all 13 commands with identical help text - Fix preview command to return exit 1 when script not found (parity with Python) - Fix printCmdHelp: correct positional arg signatures for 8 commands - Fix multi-line description rendering and --help option sourcing - Update cmdmeta.go: complete Python-matching option sets for 13 commands - Update/create golden test fixtures for 9 commands - Update TestParityHarnessPreviewInTempRepo to use yaml with build script - Document 17 approved Python-vs-Go output format exceptions - Score: migration_score=1.0 (675/675 parity tests passing) Run: https://github.com/githubnext/apm/actions/runs/26543455081 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c23e900 commit 2b79869

6 files changed

Lines changed: 570 additions & 27 deletions

File tree

cmd/apm/cmd_simple.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ func runPreview(args []string) int {
174174
return 1
175175
}
176176
fmt.Printf("[*] Previewing script '%s' in project '%s'\n", script, proj.Name)
177+
if _, ok := proj.Scripts[script]; !ok {
178+
fmt.Fprintf(os.Stderr, "[x] Script '%s' not found\n", script)
179+
return 1
180+
}
177181
fmt.Println("[i] No compiled output available.")
178182
return 0
179183
}

cmd/apm/cmdmeta.go

Lines changed: 86 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ var commandFullDesc = map[string]string{
1212
"deps": "Manage APM package dependencies",
1313
"experimental": "Manage experimental feature flags",
1414
"init": "Initialize a new APM project",
15-
"install": "Install APM and MCP dependencies (supports APM packages, Claude skills\n (SKILL.md), and plugin collections (plugin.json); auto-creates apm.yml; use\n --allow-insecure for http:// packages)",
15+
"install": "Install APM and MCP dependencies (supports APM packages, Claude skills\n(SKILL.md), and plugin collections (plugin.json); auto-creates apm.yml; use\n--allow-insecure for http:// packages)",
1616
"list": "List available scripts in the current project",
1717
"marketplace": "Manage marketplaces for discovery and governance",
1818
"mcp": "Discover, inspect, and install MCP servers",
@@ -26,15 +26,15 @@ var commandFullDesc = map[string]string{
2626
"runtime": "Manage AI runtimes (experimental)",
2727
"search": "Search plugins in a marketplace (QUERY@MARKETPLACE)",
2828
"self-update": "Update the APM CLI binary itself to the latest version",
29-
"targets": "Show resolved targets for the current project.",
29+
"targets": "Show resolved targets for the current project. If APM detects a target you\ndon't intend (e.g. CLAUDE.md is documentation, not a Claude Code config),\npin your targets explicitly in apm.yml.",
3030
"uninstall": "Remove APM packages, their integrated files, and apm.yml entries",
31-
"unpack": "[Deprecated] Extract an APM bundle into the current project.",
31+
"unpack": "[Deprecated] Extract an APM bundle into the current project. Use 'apm\ninstall <bundle-path>' instead -- this command will be removed in v0.14.",
3232
"update": "Refresh APM dependencies to the latest matching refs",
33-
"view": "View package metadata or list remote versions.",
33+
"view": "View package metadata or list remote versions.\n\nWithout FIELD, displays local metadata for an installed package. With FIELD,\nqueries specific data (may contact the remote).\n\nFields:\n versions List available remote tags and branches\n\nExamples:\n apm view org/repo # Local metadata\n apm view org/repo versions # Remote tags/branches\n apm view org/repo -g # From user scope",
3434
}
3535

3636
// commandOptions provides key options for each command, matching Python CLI help.
37-
// Only the most commonly referenced options are listed; --help is added by printCmdHelp.
37+
// Each entry includes the --help line with correct alignment (matching Click's output).
3838
var commandOptions = map[string][]string{
3939
"compile": {
4040
" -o, --output TEXT Output file path (for single-file mode)",
@@ -46,6 +46,7 @@ var commandOptions = map[string][]string{
4646
" --clean Remove orphaned AGENTS.md files",
4747
" --all Compile for all canonical targets",
4848
" -v, --verbose Show detailed source attribution",
49+
" --help Show this message and exit.",
4950
},
5051
"install": {
5152
" --runtime TEXT Target specific runtime only",
@@ -66,34 +67,100 @@ var commandOptions = map[string][]string{
6667
" --refresh Bypass the persistent cache and re-fetch all dependencies",
6768
" --dev Install as development dependency",
6869
" --allow-insecure Allow HTTP (insecure) dependencies",
70+
" --help Show this message and exit.",
6971
},
7072
"init": {
71-
" -y, --yes Skip interactive prompts and use auto-detected defaults",
73+
" -y, --yes Skip interactive prompts and use auto-detected defaults",
74+
" --plugin (deprecated) Use 'apm plugin init' instead. Scaffolds",
75+
" plugin.json + apm.yml.",
76+
" --marketplace (deprecated) Use 'apm marketplace init' instead. Seeds a",
77+
" marketplace block.",
78+
" --target TARGET Comma-separated target list (skip prompt, write directly)",
79+
" -v, --verbose Show detailed output",
80+
" --help Show this message and exit.",
7281
},
7382
"update": {
74-
" --yes Apply updates without interactive confirmation",
75-
" --dry-run Show what would be updated without applying",
76-
" -v, --verbose Show detailed update information",
77-
" -t, --target TARGET Target harness(es) to deploy to",
83+
" -y, --yes Skip the confirmation prompt (for CI / automation)",
84+
" --dry-run Render the update plan and exit without changing",
85+
" anything",
86+
" -v, --verbose Show unchanged deps and detailed pipeline diagnostics",
87+
" -t, --target TARGET Agent target(s) to update for (e.g. claude, copilot,",
88+
" cursor, windsurf, codex, opencode, gemini). Comma-",
89+
" separated for multiple: --target claude,cursor.",
90+
" Highest-priority entry in the resolution chain",
91+
" (--target > apm.yml targets: > auto-detect).",
92+
" --help Show this message and exit.",
7893
},
7994
"audit": {
80-
" --ci Exit non-zero if any issues are found (CI mode)",
81-
" --verbose Show detailed audit information",
95+
" --file PATH Scan an arbitrary file (not just APM-managed",
96+
" files)",
97+
" --strip Remove hidden characters from scanned files",
98+
" (preserves emoji and whitespace)",
99+
" -v, --verbose Show all findings including harmless ones",
100+
" --dry-run Preview what --strip would remove without",
101+
" modifying files",
102+
" -f, --format [text|json|sarif|markdown]",
103+
" Output format: text (default), json, sarif",
104+
" (GitHub Code Scanning), markdown (step",
105+
" summaries).",
106+
" -o, --output PATH Write output to file (auto-detects format",
107+
" from extension: .sarif, .json, .md).",
108+
" --ci Run lockfile consistency checks for CI/CD",
109+
" gates. Exit 0 if clean, 1 if violations",
110+
" found.",
111+
" --policy TEXT Policy source. Accepts: 'org' (auto-discover",
112+
" from your project's git remote),",
113+
" 'owner/repo' (defaults to github.com), an",
114+
" https:// URL, or a local file path. Used",
115+
" with --ci for policy checks. [experimental]",
116+
" --no-cache Force fresh policy fetch (skip cache).",
117+
" --no-policy Skip org policy discovery and enforcement.",
118+
" Overridden when --policy is passed",
119+
" explicitly.",
120+
" --no-fail-fast Run all checks even after a failure",
121+
" (default: stop at first failure).",
122+
" --no-drift Skip the install-replay drift check. Reduces",
123+
" coverage; use only for performance-",
124+
" constrained CI loops.",
125+
" --help Show this message and exit.",
82126
},
83127
"view": {
84-
" --versions List all available versions",
85-
" --json Output as JSON",
128+
" -g, --global Inspect package from user scope (~/.apm/)",
129+
" --help Show this message and exit.",
130+
},
131+
"search": {
132+
" --limit INTEGER Max results to show [default: 20]",
133+
" -v, --verbose Show detailed output",
134+
" --help Show this message and exit.",
86135
},
87136
"uninstall": {
88-
" --dry-run Show what would be removed without removing",
89-
" -g, --global Uninstall from user scope (~/.apm/)",
137+
" --dry-run Show what would be removed without removing",
138+
" -v, --verbose Show detailed removal information",
139+
" -g, --global Remove from user scope (~/.apm/) instead of the current",
140+
" project",
141+
" --help Show this message and exit.",
142+
},
143+
"unpack": {
144+
" -o, --output PATH Target directory (default: current directory).",
145+
" --skip-verify Skip bundle completeness check.",
146+
" --dry-run Show what would be unpacked without writing",
147+
" --force Deploy despite critical hidden-character findings.",
148+
" -v, --verbose Show detailed unpacking information",
149+
" --help Show this message and exit.",
150+
},
151+
"run": {
152+
" -p, --param TEXT Parameter in format name=value",
153+
" -v, --verbose Show detailed output",
154+
" --help Show this message and exit.",
90155
},
91156
"list": {
92-
" --json Output as JSON",
157+
" --help Show this message and exit.",
93158
},
94159
"targets": {
95160
" --json Output as JSON instead of a table.",
96-
" --all Include the agent-skills meta-target in JSON output (excluded by default).",
161+
" --all Include the agent-skills meta-target in JSON output (excluded by",
162+
" default).",
163+
" --help Show this message and exit.",
97164
},
98165
"cache": {
99166
" --help Show this message and exit.",
@@ -112,8 +179,6 @@ var commandOptions = map[string][]string{
112179
" -o, --output PATH Bundle output directory (default: ./build).",
113180
" --json Emit machine-readable JSON to stdout.",
114181
" -v, --verbose Show detailed packing information.",
115-
},
116-
"unpack": {
117-
" --help Show this message and exit.",
182+
" --help Show this message and exit.",
118183
},
119184
}

cmd/apm/main.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,24 +98,47 @@ func printCmdHelp(name string) {
9898
fullDesc = desc
9999
}
100100
fmt.Printf("Usage: apm %s [OPTIONS]", canonical)
101-
// Commands with positional args.
101+
// Commands with positional args -- match Python CLI usage strings exactly.
102102
switch canonical {
103-
case "install", "uninstall", "view", "search", "run":
104-
fmt.Printf(" [ARGS]...")
103+
case "install":
104+
fmt.Printf(" [PACKAGES]...")
105+
case "uninstall":
106+
fmt.Printf(" PACKAGES...")
107+
case "view":
108+
fmt.Printf(" PACKAGE [FIELD]")
109+
case "search":
110+
fmt.Printf(" QUERY@MARKETPLACE")
111+
case "run":
112+
fmt.Printf(" [SCRIPT_NAME]")
113+
case "audit":
114+
fmt.Printf(" [PACKAGE]")
115+
case "unpack":
116+
fmt.Printf(" BUNDLE_PATH")
105117
case "init":
106118
fmt.Printf(" [PROJECT_NAME]")
119+
case "targets":
120+
fmt.Printf(" COMMAND [ARGS]...")
107121
}
108122
fmt.Println()
109123
fmt.Println()
110-
fmt.Printf(" %s\n", fullDesc)
124+
// Print full description: indent each line with two spaces.
125+
for _, line := range strings.Split(fullDesc, "\n") {
126+
if line == "" {
127+
fmt.Println()
128+
} else {
129+
fmt.Printf(" %s\n", line)
130+
}
131+
}
111132
fmt.Println()
112133
fmt.Println("Options:")
113134
if opts, ok := commandOptions[canonical]; ok {
114135
for _, opt := range opts {
115136
fmt.Println(opt)
116137
}
138+
} else {
139+
// Default: only --help option.
140+
fmt.Println(" --help Show this message and exit.")
117141
}
118-
fmt.Println(" --help Show this message and exit.")
119142
}
120143

121144
func run(args []string) int {

cmd/apm/parity_new_commands_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,19 @@ func TestParityHarnessPreviewMissingArg(t *testing.T) {
365365
}
366366

367367
func TestParityHarnessPreviewInTempRepo(t *testing.T) {
368-
r := runBothInTempRepo(t, minimalApmYML, "preview", "build")
368+
apmYML := `name: test-project
369+
version: 1.0.0
370+
description: Parity test project
371+
author: crane-test
372+
targets:
373+
- copilot
374+
dependencies:
375+
apm: []
376+
mcp: []
377+
scripts:
378+
build: echo build
379+
`
380+
r := runBothInTempRepo(t, apmYML, "preview", "build")
369381
if r.GoExitCode != 0 {
370382
t.Errorf("apm preview build exited %d\nstderr: %s", r.GoExitCode, r.GoStderr)
371383
}

0 commit comments

Comments
 (0)