Surviving a reprint: how connector fixes outlive cli-printing-press

Every connector under skills/<slug>/cli is generated by cli-printing-press and then carries connector-specific edits: live-API quirks, synthesized identifiers, corrected examples, and bug-fix back-ports. The press is upstream and refreshes on its own cadence; we pull new binaries and reprint connectors whenever it improves.

A reprint regenerates those DO NOT EDIT files. The danger is that it can silently clobber a hand-fix - and go build / go test stay green while the fix is gone, because nothing tests the live-API behavior the fix encoded. This happened with axcient: a full reprint reverted the Python-style id_ primary-key fallback, reintroducing all_items_failed_id_extraction (0 rows) for appliances/vaults. Only an adversarial review caught it.

This doc is how we keep msp-skills and the press independent - fix connectors at any time, refresh the press at any time - while features, bug-fixes, and live-API lessons survive.

Why regen-merge isn’t enough

cli-printing-press regen-merge preserves whole hand-authored files (NOVEL) and new top-level declarations (TEMPLATED-WITH-ADDITIONS). It does not preserve inline body edits to generated files - adding "id_" to an existing slice literal, or a Hand-wired: block inside an existing function, is classified TEMPLATED-BODY-DRIFT and left for a human to merge by hand. A blind “overwrite every DO NOT EDIT file” reprint drops them. The decl-set comparison is structural; it can’t see that a one-line change inside a function is load-bearing.

The three layers (use them in this order)

1. Encode it in the spec (best - survives by construction)

If the press can express the behavior as a spec extension, that’s the most reprint-proof home: it becomes input to generation, so any press version regenerates it correctly. The id_ case is the textbook example - the generated code itself prints “Annotate the spec with x-resource-id to fix”. Prefer x-resource-id, x-pp-resource, sync-hints, x-auth-* over a hand-edit whenever the press supports it. (For true independence the annotated spec should travel with the connector; today the spec lives in the press library, so spec-encoding also means a press change or a vendored spec.)

When the press can’t express it (e.g. synthesizing an id for a resource that has none), or you need the fix shipped now, fall through to layer 2.

2. Record it in the hand-fix ledger (enforced safety net)

Each connector may carry skills/<slug>/handfixes.json recording every edit to a generated file with a grep-able marker. check_handfixes.py asserts every marker is still present (and any banned anti-pattern still absent) and fails CI if a reprint dropped one - turning a silent clobber into a loud, blocking failure. It runs in the per-skill CI build job and in verify_all.sh. Skills without a ledger are a no-op.

Add an entry whenever you hand-edit a generated file:

{
  "slug": "<slug>",
  "doc": "docs/reprint-survival.md",
  "handfixes": [
    {
      "id": "short-kebab-id",
      "summary": "one line",
      "why": "the live-API truth / rationale a future maintainer needs",
      "status": "active",
      "spec_encode_followup": "optional: the x-... annotation this should become",
      "asserts": [
        {"file": "cli/internal/store/store.go", "contains": "\"id_\"", "min_count": 2},
        {"file": "cli/internal/mcp/tools.go", "not_contains": "fmt.Sprintf(\"%v\", v), 1)"},
        {"file": "cli/internal/config/config.go", "contains": "stripAuthScheme(",
         "min_count": 1, "code_only": true}
      ]
    }
  ]
}

Pick markers that are specific (a distinctive substring of the fix), not generic. When you intentionally change a fix, update its ledger entry in the same PR.

code_only: what the contains needle is counted in

An assert counts its needle in the whole file, comments included. So an assert whose needle a doc comment also carries stays green after the code it guards is deleted - the exact failure the gate exists to catch (issue #252). code_only is how you say which one you meant:

value what it does when to use it
absent (default) whole-file count fine when the needle cannot appear in prose
true count comment-stripped source only - strictly stronger, the needle must survive in code the normal fix for a needle a comment also contains
false whole-file count, and the lint stays quiet the comment is the thing a reprint must not drop (a Hand-wired: marker, an explanatory paragraph)

Three rules the gate enforces, each of them a hard ledger error rather than a silent no-op:

Two ways this is checked. check_handfixes.py --lint-asserts reports every comment-satisfiable contains assert repo-wide; it is advisory (exit 0) and runs on every CI run, because 26 asserts are in that shape today and some of them deliberately. The hard gate is --changed, which fires only on an assert a change adds or weakens - editing an entry’s why never trips it.

python3 tools/maintainer/check_handfixes.py --lint-asserts   # advisory report

3. Capture the lesson (this doc + press retros)

Process lessons live here. Things the press itself should fix generically (e.g. handle Python-id_ keys, or have regen-merge detect body-level slice edits) go upstream as a /printing-press-retro issue, so the whole fleet benefits and we stop carrying the hand-fix.

Unique / transcendent novel commands (e.g. axcient’s health, client-rollup) are already mostly safe - they’re whole hand-authored files regen-merge keeps, recorded in .printing-press.json. A ledger entry asserting “the novel command still resolves” is a cheap backstop.

When fixing or reprinting a connector

The gate above is the reactive half (CI blocks a clobbered merge). The --brief mode is the proactive half: read the ledger before you regenerate so you know what to preserve. The repo’s AGENTS.md makes this mandatory for any agent.

First, check for open fleet advisories. Before any reprint or re-vendor, read the findings that apply to every connector. They tell you whether a reprint is even the right fix, what it will and will not resolve, and what to verify afterwards:

gh issue list --repo Servosity/msp-skills --state open --label fleet-advisory

These are fleet-scope findings - a generator defect, an MCP boundary gap, a security issue inherited by every printed CLI - not per-connector bugs. A reprint inherits whatever the engine has already fixed and nothing it has not, so the advisory is what tells you which half you are in. Occasionally it will also tell you that the obvious hand-patch is the wrong fix because the engine solved it a different way, in which case patching by hand just creates drift in a DO NOT EDIT file that the reprint would have resolved for free.

When you file a finding that affects every connector, label it fleet-advisory so this check keeps working for the next agent.

  1. Before reprinting / re-onboarding / bulk-overwriting cli/, read what you must preserve:

    python3 tools/maintainer/check_handfixes.py --brief --slug <slug>
    
  2. Prefer surgical over a full reprint when the goal is a targeted fix. Revert the cli tree to main, apply only the fix. A full reprint absorbs all upstream improvements but risks clobbering hand-fixes and can’t be live-verified without credentials.
  3. If you do full-reprint, 3-way merge every BODY-DRIFT file (preserve the hand-fix, apply the template delta) - never blind-overwrite.
  4. After regenerating, run python3 tools/maintainer/check_handfixes.py --slug <slug> (it’s also in verify_all.sh and CI). Restore anything it flags before committing, and add/refresh ledger entries for any new hand-edit.
  5. Link the originating GitHub issue to the ledger entry. Issues are for triage; the ledger is the source of truth a reprint is checked against.

Find connectors whose hand-fixes are not yet recorded (back-fill candidates): python3 tools/maintainer/check_handfixes.py --discover.

Why not “GitHub issues as the source of truth”?

Issues are where bugs arrive and get discussed, but they’re the wrong durable home for reprint-survival: not co-located with the code, no mechanical enforcement, they close and get buried, and a reprint shouldn’t have to parse prose from a closed issue. The ledger is versioned with the connector and checked in CI; issues link to it.

Fleet advisories (above) are not an exception to this. They are not per-connector facts a reprint is checked against - that is still the ledger’s job, enforced in CI. They are fleet-scope notices about whether and how to reprint at all, which no per-connector ledger can carry.