Keeping adopted packs current
Reconcile your ctxgrd.toml pack blocks with a pack’s current shape after a
ctxgrd upgrade, without losing your own edits.
What a drift report means
pack add copies a pack’s namespace blocks into ctxgrd.toml and stamps each
one:
# pack: [email protected] sha:600e483bb0c6d663pack outdated re-checks that stamp against the pack’s current shape and
sorts every stamped block into one of three states:
- Drift – the pack changed since the block was stamped. The block needs
your attention;
pack outdatedshows the diff. - No baseline – the stamp predates the fingerprint (a bare
# pack: <name>, written before 0.35.0) or is missing entirely, so there is nothing to compare against. Listed by name; nothing resolves it automatically. - Current – silent.
Only drift sets a nonzero exit code. Your own edits – an added rule, an
overridden paths, a customized rule param, a set owner – are never drift,
however much of the block you have rewritten. The stamp records the pack’s
text at the moment you copied it, and that is what your block is compared
against, not what it now contains.
1. Check for drift
ctxgrd pack outdatedRead-only – nothing is written.
Exit codes:
0– no pack has moved. Blocks with no baseline may still be listed.1– one or more packs have moved.2– config error (ctxgrd.tomlunreadable or malformed).
2. Preview the migration
ctxgrd pack migrate --dry-runShows what pack migrate would do without touching ctxgrd.toml. Each
provenance-stamped block lands in one of three outcomes:
- Byte-identical to the pack – rewritten to the pack’s current shape and re-stamped. This is the only condition under which re-rendering loses nothing, so it is the only one that authorizes a write.
- Edited by you – left untouched, always. If its pack has also moved, the diff between your block and the pack’s current shape is printed for you to reconcile. Migrate never merges.
- Byte-identical, but stamped by an old binary – the block text is left
exactly as it is; only its
# pack:line is rewritten, so it gains the baseline drift detection needs. Reported as a stamp-only change, and it never fails the gate.
For agent or CI use:
ctxgrd pack migrate --dry-run --format json3. Apply the migration
ctxgrd pack migrateRewrites every clean block in place and re-stamps its provenance. Dirty blocks are left untouched, and their diffs print to stdout so you can reconcile them by hand – or hand the diff to an agent.
Running pack migrate a second time on an already-migrated config is a
no-op.
Exit codes:
0– migration complete; no dirty blocks remain.1– migration ran but one or more dirty blocks were left for manual resolution.2– config error.
4. Resolve a dirty-block diff
For each dirty block the output shows:
- On-disk – what is currently in
ctxgrd.toml. - Proposed – what the pack’s current shape would write. This is the
pack’s block, not a merge – it does not carry your
owner, your added rules, or yourpathsoverride.
Open ctxgrd.toml and reconcile the two by hand. Common cases:
- You added a rule the new shape also includes – keep it once.
- You added a rule the new shape does not include – keep your addition, accept the structural changes around it.
- You renamed or removed a namespace section – take the structural rename from the proposed block and keep your customizations.
After editing, run ctxgrd to confirm the config lints clean, then run
ctxgrd pack outdated again. The block keeps reporting drift until its stamp
records the pack you reconciled against – reconciling changed your block,
not the stamp, so the stamp still points at the old pack.
There is no command that adopts a new baseline for an edited block: on an
edited block, “your copy is current” is an assertion only you can make, and
ctxgrd has no way to verify it for you. To clear the report, delete the
block and re-run ctxgrd pack add <name> for the namespace. That writes a
freshly stamped copy, which you then re-apply your customizations to.
Blocks with no baseline
A block stamped # pack: <name> with no sha:, or carrying no stamp at all,
cannot be judged: there is no record of what the pack looked like when the
block was copied, so “has the pack moved?” has no answer. pack outdated
lists these under their own heading and never fails the gate on them.
Nothing clears them automatically, and no command will. pack migrate
upgrades the stamp of a block that still matches its pack byte-for-byte –
that is provable – but a block you have customized is exactly the case
where adopting today’s pack as its baseline would be a guess.
It is not a guess for you, though, and the report hands you the digest:
1 block(s) carry no baseline -- a pre-v2 `# pack:` stamp, or none:
[CR] (pack intake) -- baseline it with sha:79ebf75f5c1a1492Writing that digest into the block’s stamp declares “the installed pack is this block’s baseline”. Do it deliberately, in three steps:
Read the pack’s current shape and compare it against your block, so you know what you are adopting:
ctxgrd pack show intakeConfirm the digest – the same value, from the command that is the source of truth for it. Every namespace in
pack show --format jsoncarries the fingerprint its stamp must match:ctxgrd pack show intake --format json \ | jq -r '.namespaces[] | select(.namespace == "CR") | .fingerprint'Append it to that block’s
# pack:line, keeping the pack name and version already there. Only that line changes:-# pack: [email protected] +# pack: [email protected] sha:79ebf75f5c1a1492
Re-run ctxgrd pack outdated and the block is gone from the list. It is now
judged like any other: your customizations stay silent, and the next time the
intake pack actually moves, that block reports drift.
What you have asserted is that your block is current as of the installed pack, not that it matches the pack’s text. If you would rather not make that claim, the alternatives are to leave the block listed – it never sets the exit code – or to re-add the namespace from the pack and re-apply your edits, which produces a stamp ctxgrd wrote itself.
Machine-readable output
Both commands support --format json for CI or agent-driven fleet
workflows:
ctxgrd pack outdated --format json
ctxgrd pack migrate --dry-run --format jsonThe exit-code contract (0 / 1 / 2) is stable across formats, so a
script or agent can branch on outcome without parsing text. The three states
are distinguishable from the JSON alone: diffs[] is drift, unknown[] is
the no-baseline group, and a rewrites[] entry with "stamp_only": true is
housekeeping rather than a shape change.
Each unknown[] entry carries its namespace, its pack, and the
fingerprint that would baseline it – so an agent can apply the manual
remedy above without a second command:
{ "namespace": "CR", "pack": "intake", "fingerprint": "79ebf75f5c1a1492" }What migration does not do
- It does not remove namespace blocks whose
pathsglob matches no files on disk. That cleanup – for example a[GEMINI]block in a repo with noGEMINI.md– is a separate step, not yet surfaced as a command. - It does not merge your edits into the new shape automatically. The diff is yours to resolve.
- It does not push or commit anything. Migration rewrites
ctxgrd.tomlin place; committing the result is your step.
Next steps
- Rule packs – the built-in pack catalog and what
pack addwrites. - Configuring namespaces –
[<NS>].pathsand the claim model a pack block relies on. - CLI reference – every
packsubcommand and its exit codes.