Skip to content

Crane Migration: Python to Go -- Full APM CLI Rewrite #78

@mrjf

Description

@mrjf

schedule: every 5m
strategy: greenfield
source-language: python
target-languages: [go]
target-metric: 1.0
metric_direction: higher
completion-framework: deletion-grade-cli-parity-v2

APM CLI: Python to Go -- Full Rewrite

Complete rewrite of the APM CLI from Python to Go. The Python implementation
stays in place as the authoritative reference until the Go CLI proves complete
black-box equivalence. The end state is 100% Go for the shipping apm command,
but Python must not be removed until the deletion-grade parity gates below pass.

Current guidance supersedes prior completion claims.

Previous Crane iterations reported migration_score: 1.0 and marked this
migration complete, but that was not deletion-grade proof. The current Go CLI
still contains simplified command bodies and approved parity exceptions. This
issue is not complete until the stricter framework in
#96 is implemented and all gates here
are green.

Source

  • Language: Python 3.10+ with Click/Rich
  • Paths:
    • src/apm_cli/ -- authoritative Python implementation
    • tests/ -- Python reference test suite
  • Role during migration: runnable oracle for CLI behavior, filesystem
    mutations, generated artifacts, lockfiles, config, cache, and benchmark
    comparison.

Target

  • Language: Go
  • Paths:
    • cmd/apm/ -- CLI entry point
    • internal/ -- internal packages mirroring Python behavior
    • pkg/ -- public library APIs if needed
  • Bridge: none for the final shipping CLI. Temporary test-time use of
    Python is only for parity verification.

Completion Definition

migration_score: 1.0 must mean the Python CLI can be removed from the
shipping path. It is not enough for TestParity* tests to pass, for help text
to match, or for Go command stubs to return successful exit codes.

Crane may mark this migration complete only when every gate is true:

  1. python_reference_required = true
  2. surface_parity = 100%
  3. help_parity = 100%
  4. functional_contracts = 100%
  5. state_diff_contracts = 100%
  6. known_exceptions = 0
  7. go_tests = pass
  8. python_tests = pass
  9. benchmarks = pass
  10. release/install/cutover path ships the Go CLI or documents a temporary shim
    with explicit removal criteria.

If any gate is false, the score must be less than 1.0.

Required Crane Framework Update

Implement the framework described in #96 before accepting further completion
claims:

  • Missing APM_PYTHON_BIN is a hard failure, never a warning or vacuous pass.
  • Empty or cached Go test event streams must fail scoring.
  • The score script must not derive completion from test names alone.
  • "Approved exceptions", "simplified help", and "Go is more lenient" logs are
    allowed only as temporary progress notes; final completion requires zero.
  • Every PR commit on a Crane branch must run parity and benchmarks, then post
    the benchmark and parity summary back to the PR.

Surface Parity Gate

Generate a CLI inventory for both Python and Go, then diff them.

The inventory must include:

  • commands and subcommands
  • aliases, including hidden aliases
  • positional arguments
  • options and flags
  • defaults
  • required/repeated/multiple-value behavior
  • hidden/deprecated status
  • invalid usage behavior

Fail on any missing or mismatched public behavior unless the issue explicitly
records a temporary non-cutover exception.

Help And Usage Gate

For every command and subcommand, compare Python and Go:

  • apm --help
  • apm <command> --help
  • apm <command> <subcommand> --help
  • invalid option
  • missing required argument
  • unknown subcommand

Compare exit code, stdout, and stderr after only deterministic normalization.
Final cutover requires no help truncation exceptions.

Functional State-Diff Gate

Crane must use a black-box contract harness:

  1. Create two identical temp homes and repos.
  2. Run Python in one.
  3. Run Go in the other.
  4. Compare observable results.

Compare:

  • exit code
  • stdout/stderr
  • files created, removed, and modified
  • file contents
  • apm.yml
  • apm.lock.yaml
  • .apm/ package directories
  • managed-file manifests
  • generated AGENTS.md, CLAUDE.md, Copilot, Codex, Gemini, Cursor,
    Windsurf, and OpenCode files
  • marketplace config
  • user config
  • cache layout where deterministic
  • audit artifacts such as JSON, SARIF, and markdown

Mutating commands do not count as complete until their filesystem and config
effects match Python, not just their exit code.

Required Command Contract Matrix

Crane must cover the full supported Python CLI surface, including:

  • init: defaults, explicit project name, explicit targets, plugin mode,
    marketplace mode, existing apm.yml, non-interactive behavior.
  • compile: all canonical targets, --target, --all, --clean,
    --validate, --dry-run, generated file parity, orphan cleanup.
  • install: local bundle, local plugin directory, local skill bundle, fixture
    git repo, transitive deps, skill subset, MCP deps, global scope, --frozen,
    policy, collisions, --force, lockfile writes, managed-file writes.
  • uninstall: direct deps, transitive orphan cleanup, global scope, dry-run,
    stale integrated files, manifest mutation.
  • update and deps update: no-op, changed refs, selected packages,
    target-specific update, lockfile mutation, dry-run.
  • deps: list, tree, info, clean, global scope, insecure-only views.
  • pack and unpack: bundle contents, marketplace artifacts, manifest fields,
    hidden-character audit, JSON output, dry-run, --force, --skip-verify.
  • marketplace: add/list/remove/update/browse/validate,
    init/check/outdated/doctor/publish dry-run/migrate, package add/set/remove,
    config mutation, fixture refs.
  • audit: package scan, arbitrary file scan, hidden Unicode findings, --strip,
    --dry-run, text/json/sarif/markdown, --ci,
    policy/no-policy/no-cache/no-drift.
  • policy: status, check, local policy, inherited policy, no-cache, output
    format parity.
  • mcp: list, search, inspect/show, install, fixture registry, client config
    mutation.
  • run, preview, and list: scripts, params, missing script, default script,
    prompt output parity, verbose output.
  • config: get, set, unset, config path handling, missing config, invalid
    key/value.
  • runtime: list, setup, remove, status, fixture runtime adapters.
  • targets: auto-detection, explicit apm.yml targets, --json, --all,
    ambiguous/missing target behavior.
  • view: installed package metadata, missing package, versions using fixture
    refs, global scope.
  • cache: info, clean, prune, cache path/env behavior.
  • prune: no-op, undeclared package removal, dry-run, integration cleanup.
  • outdated: missing lockfile, no outdated deps, outdated dependency fixture,
    prerelease behavior where applicable.
  • self-update: --check, platform-specific messaging, no real install or
    network in parity tests.

Fixture Policy

Parity tests must not depend on live GitHub, registries, or external services.
Use local git repos, local tarballs, local fixture registries, local HTTP
fixture servers, fake homes, fake config paths, and fake cache roots.

Real-network smoke tests may exist, but they are not cutover evidence.

Python Tests

Keep running the original Python unit suite while Python exists. Python tests
prove the reference still works; they do not prove Go parity. Cutover readiness
comes from black-box contracts that both Python and Go satisfy.

Benchmarks

Benchmarks must run on every Crane PR commit and post results to the PR. A
benchmark pass is required for final completion, but speed cannot compensate for
missing parity.

Crane Operating Rule

For every accepted iteration:

  1. Pick one Python CLI behavior.
  2. Add a black-box Python-vs-Go contract test for it.
  3. See the test fail for Go.
  4. Implement Go behavior.
  5. Confirm the contract passes.
  6. Update the PR/issue report with the exact behavior now covered and the
    remaining gates still false.

No final cutover exceptions. No vacuous parity. No deleting Python until the
deletion-grade gate is green.

Verification

Minimum local checks:

uv sync --extra dev
go test ./...
APM_PYTHON_BIN="$PWD/.venv/bin/apm" go test -count=1 ./...
APM_PYTHON_BIN="$PWD/.venv/bin/apm" go test -count=1 -json ./... | go run .crane/scripts/score.go
python scripts/ci/migration_cli_benchmark.py \
  --python-bin "$PWD/.venv/bin/apm" \
  --go-bin /tmp/apm-go \
  --json-out /tmp/migration-cli-benchmark.json \
  --markdown-out /tmp/migration-cli-benchmark.md

The exact harness can be richer, but final completion must execute both CLIs
and compare observable behavior. A skipped Python invocation, a Go-only unit
test, or a help-only fixture is not completion evidence.

Out Of Scope During Migration

  • Do not delete src/apm_cli/ until all deletion-grade gates pass.
  • Do not remove the existing Python tests while they are still the reference.
  • Do not claim completion from benchmark speed alone.
  • Do not rely on live external services for parity evidence.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions