feat: ctx pad undo, out-of-band audit channel (ctxctl), OS-native memory pressure#104
Merged
Conversation
Every destructive `ctx pad` operation now writes the prior pad
blob to .context/scratchpad.history/ before overwriting, and a
new `ctx pad undo` subcommand restores the most recent snapshot.
Snapshots are the existing pad bytes byte-for-byte (no
re-encryption), so plaintext and encrypted modes both get the
safety net the same way. Retention is a bounded ring: 20
snapshots or 30 days, whichever trips first. Prune failures
degrade to a warn; the safety net never blocks a write.
The undo command is itself a mutation that snapshots first, so
`ctx pad undo; ctx pad undo` yields a redo. Empty history prints
a friendly message and exits 0 (cron-quiet on fresh projects).
Wired through the existing i18n pipeline: new DescKeys in
internal/config/embed/text/{err_pad,pad}.go with matching
translations in internal/assets/commands/text/{errors,write}.yaml.
Warn formats live in internal/config/warn (cfgWarn.PadHistory*)
per project convention.
Phase 2 (--list, --to, --prune, --clear flags + .ctxrc retention
tuning + skill/recipe doc updates) tracked separately under the
same TASKS.md entry.
Also bundled (session bookkeeping; no separate spec since these
inherit this commit per spec-trailer-discipline §bundle-rule):
- TASKS.md completion annotations for 7 already-shipped items
(lines 393 ConstantTimeCompare, 406 input validation, 427
connect listen, 442 ctx backup deprecation, 482 GLOSSARY
append, 543 ctx-architecture-failure-analysis) plus the
skip-with-reason annotation for line 463 (publish
architecture docs) and the line-543 structural cleanup.
Tests: 6 new cases in internal/cli/pad/pad_test.go covering
first-write-no-snapshot, snapshot-preserves-exact-bytes,
undo-restores-pre-mutation, undo-is-itself-snapshotted (redo),
empty-history-exits-zero, plaintext-mode round-trip.
Spec: specs/pad-undo-snapshot.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
The previous commit (6bcaf88) shipped `ctx pad undo` and the snapshot safety net but labeled SKILL.md and recipe updates as "Phase 2" — which is exactly the deferral the Constitution forbids ("I can create a follow-up task"). User caught this at review. This commit closes the gap and installs a persistent rule so future agents see the requirement at session start. User-facing surface caught up: - internal/assets/claude/skills/ctx-pad/SKILL.md: add `undo` to the command-mapping table and a dedicated Execution entry documenting redo-by-double-undo and empty-history exit shape. - docs/recipes/scratchpad-with-claude.md: add `ctx pad undo` to the commands table and a new "If You Delete the Wrong Thing" section covering snapshot-on-mutate semantics, redo-by-double-undo, key-loss caveat, and retention bounds. Persistent rule (CONVENTIONS.md → "User-Facing Surface Completeness"): enumerates the docs surfaces that must be updated in the same commit as any user-facing change (commands.yaml + examples.yaml + SKILL.md + integrations skills + recipes + docs/cli pages) and explicitly forbids "Phase 2" deferral of docs. CONVENTIONS.md loads into agent context at session start, so the rule is in front of every future agent's eyes. Captured context: - DECISIONS.md: snapshot lives at the store.WriteEntriesWithIDs choke point (not per-subcommand), with byte-for-byte copy of the existing pad blob (no re-encryption), so plaintext and encrypted modes share the safety net. - LEARNINGS.md: audit gates around new packages — TestNoMixedVisibility forces <name>_internal.go splits, TestNoMagicStrings requires warn formats in internal/config/warn/, TestDocCommentStructure demands full Parameters/Returns blocks on every helper. TASKS.md: Phase 1 entry flipped to [x] with commit ref (6bcaf88); Phase 2 unchanged. Spec: specs/pad-undo-snapshot.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Captures the design for moving discipline enforcement off the in-band code path (CONVENTIONS prose that didn't survive the pad-undo Phase 1 commit's tunnel vision) onto the only discipline channel in this codebase that empirically does survive agent tunnel vision: verbatim relay. Shape: - An out-of-band auditor (a different Claude Code session, human-triggered, runs on the user's plan-billed CC instead of API to avoid per-commit cost) invokes a skill like /ctx-surface-audit. - The skill drops a structured report at .context/audit/<kind>.md (yaml frontmatter + verbatim body). - A UserPromptSubmit hook `ctx system checkaudit` reads the audit directory and verbatim-relays each not-yet-dismissed report in the same ┌─ ... ─┐ envelope as `ctx remind`, `ctx system checkknowledge`, and the journal/knowledge notices. - Dismissal mirrors `ctx remind`: `ctx audit list / show / dismiss / dismiss --all`. Central trust boundary: the auditor refuses to run inside a session with uncommitted working-tree changes to the audit target range — the implementer cannot grade their own homework. Fresh-context judgment is the point. Phase 1 ships the channel + the first skill (/ctx-surface-audit). Phase 2 adds auto-dismissal on detected resolution and sibling audit skills (/ctx-spec-trailer-audit, /ctx-capture-audit, ...). Bundled here as the third commit on feat/pad-undo-snapshot because the spec grew directly out of the doc-gap fixup in commit 77fa01f — the audit channel is the structural fix for the failure mode that commit only papered over with a CONVENTIONS.md prose rule. Spec: specs/audit-channel.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…hook (Phase 1a) Stand up the audit channel sketched in specs/audit-channel.md: the structural substrate that lets out-of-band auditor sessions (a separate Claude Code instance, run on the user's plan to avoid per-commit API cost) drop structured reports into `.context/audit/<kind>.md`, and that surfaces those reports to the next in-session interaction via the only discipline channel in this codebase that empirically survives agent tunnel vision: verbatim relay. Phase 1a deliverables: - `ctx audit` CLI: `list` (default), `show ID`, `dismiss [ID...]` or `dismiss --all`. Dismissals are bound to the report digest at dismiss time so a fresh audit run with new findings re-surfaces the report. Mirrors `ctx remind` shape and registers in internal/bootstrap/group.go under the sessions group. - `ctx system check-audit` UserPromptSubmit hook: reads every report under `.context/audit/`, skips status:clean and dismissed-against-current-digest, renders each remaining body inside the standard nudge box (auditRender package), and prefixes reports older than [cfgAudit.StalenessAge] (30 days) with a STALE marker. Uses the same nudge.LoadAndEmit machinery as check-reminder. - Report format: YAML frontmatter (kind/status/commit-range/ generated-at/generator/digest) + verbatim body. Parsed by internal/cli/audit/core/parse with sentinel ErrNoFrontmatter / ErrUnterminatedFrontmatter in internal/err/audit. - Persistence: reports at `.context/audit/<id>.md`, dismissal ledger at `.context/audit/.dismissed.json` (NOT under `.context/state/` — a user nuking state should not silently re-surface dismissed audits, per spec Open Question #2). - Full i18n plumbing: 8 error keys, 4 writer keys, 7 check-audit hook keys, plus matching descriptors in commands.yaml / examples.yaml / flags.yaml / hooks.yaml / registry.yaml. - Tests: 8 CLI tests (list/show/dismiss happy paths + unknown-id + no-ids-no-all + fresh-digest-resurfaces); 4 parser tests (round-trip + sentinel errors + body byte-preservation); 5 hook tests (no-leak invariant + silent-on-clean + relays-findings-body + silent-when- dismissed + STALE-prefix-on-old-report). Phase 1b (separate commit, per the User-Facing Surface Completeness convention added in 77fa01f): the first auditor skill (/ctx-surface-audit SKILL.md with refuse-on-dirty-tree guard) + recipe touch-ups. Phase 2 (separate spec / commits): auto-dismissal on detected resolution, sibling audit skills (/ctx-spec-trailer-audit, /ctx-capture-audit), and stale- report graceful escalation. Bundled as the fourth commit on feat/pad-undo-snapshot because this work grew directly from the doc-gap fixup in 77fa01f — the audit channel is the structural fix for the tunnel-vision failure mode that commit only papered over with a CONVENTIONS prose rule. Spec: specs/audit-channel.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Complete the audit channel's user-facing surface, honoring the User-Facing Surface Completeness convention (added 77fa01f): a new user-facing capability is not done until its skill and recipe land in the same body of work. - internal/assets/claude/skills/ctx-surface-audit/SKILL.md: the first audit-family skill. Scans a git ref range (default main..HEAD) for user-facing surfaces — new subcommands, flags, behavior changes — that landed without matching SKILL.md / recipe / docs-cli coverage, and writes a structured report to .context/audit/surface.md. Central trust boundary: refuses to run in a session with a dirty worktree for the audit range (the implementer cannot grade their own homework); no override flag by design. Documents the report frontmatter shape, per-surface coverage checks (SKILL.md table → recipes → docs/cli → integrations), and the write-don't-remediate boundary. - docs/recipes/audit-channel.md: end-to-end recipe — why a separate session (fresh-context judgment + plan-vs-API cost shape), the five-step workflow, digest-bound dismissal, retention/staleness, when to run, and the future audit-skill family (/ctx-spec-trailer-audit, /ctx-capture-audit). - docs/recipes/index.md: register the recipe under Agents and Automation. This is itself a live test of the channel: a /ctx-surface-audit run against this branch should now report clean for the `ctx audit` surface, since SKILL coverage (this skill) and recipe coverage (this recipe) both exist. Spec: specs/audit-channel.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
- TASKS.md: add the out-of-band audit channel entry with Phase 1a and Phase 1b marked [x] (commits aefce51, 71c3dfa) and Phase 2 (auto-dismissal, sibling audit skills, settings.local.json hook wiring) pending. - DECISIONS.md: record the architectural choice to put discipline enforcement on the verbatim-relay channel and run the auditor out-of-band — the rationale (relay survives tunnel vision; fresh- context judgment; cost on plan not API) and consequences (generic kind-agnostic channel, digest-bound dismissal, no automated trigger in Phase 1). - LEARNINGS.md: extend the audit-gates entry with the fuller gate catalog hit while landing a whole new CLI command + hook (TestNoMagicValues, TestNoCmdPrintOutsideWrite, TestNoNakedErrors incl. sentinels, TestTypeFileConvention, TestCmdDirPurity, TestNoLiteralMdExtension, TestDocGoSubcommandDrift, TestDescKeyYAMLLinkage, TestRegistryCount, and the staticcheck QF1012 vs TestNoUncheckedFmtWrite tension). Spec: specs/audit-channel.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
The audit channel shipped into the ctx binary in aefce51 is maintainer tooling in the wrong home: the check-audit UserPromptSubmit hook would fire on every prompt for every end user, taxing them with filesystem reads for a feature they will never produce reports for, and `ctx audit` bloats the user command surface. The auditor (_ctx-surface-audit) only audits ctx's own internal layout. This spec stands up cmd/ctxctl — the maintainer/contributor binary planned in TASKS.md Phase BT since 2026-03 but never built because its build/release-script inhabitants were each blocked or deferred — with the audit channel as a cleaner, self-contained first inhabitant. Key decisions captured: - Same module (cmd/ctxctl), not a separate go.mod at tools/ctx/ctxctl. A separate module cannot cleanly import the parent's internal/ (the ~25 audit files); same-module still keeps audit out of the ctx binary via the import graph (only ctxctl's main imports it). Separate-go.mod's only win is dep isolation, which the audit channel does not need. - Refines the Phase-BT rule "hooks must call ctx": shipped product hooks call ctx; repo-local dev hooks (ctx's own gitignored settings) may call ctxctl. The audit-relay hook is the latter. - Migration moves wiring (cobra registration + hook entry) out of ctx; the internal logic packages stay put and are reused. The deliberately-dirty internal/assets/claude/hooks/hooks.json edit in the working tree is the burned bridge — left dirty as a forcing function. This spec is where that trail ends: the migration removes check-audit from the shipped hooks.json and re-homes it as a repo-local ctxctl call. The implementation tree is intentionally left mid-migration; only the plan (this spec + the TASKS.md tracker) is committed here. Spec: specs/ctxctl-bootstrap.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
- DECISIONS.md: cmd/ctxctl lives in the same Go module (not a separate go.mod) — binary-level import-graph isolation keeps audit out of the ctx binary while reusing internal/ verbatim; separate go.mod's only win (dep isolation) isn't needed yet. - LEARNINGS.md: skill shipping location — _ctx- prefix in .claude/skills/ is repo-internal, internal/assets/claude/skills/ is bundled and shipped; the ship-vs-repo-internal question generalizes to ctx-vs-ctxctl commands and shipped-vs-local hooks. - handovers/: session handover for the next agent — implement specs/ctxctl-bootstrap.md; the 4-file dirty tree is an intentional burned-bridge forcing function, not forgotten work. The migration implementation files (hooks.json, _ctx-surface-audit SKILL.md, audit-channel recipe, recipes index) remain deliberately uncommitted. Spec: specs/ctxctl-bootstrap.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Branch wrap-up commit for feat/pad-undo-snapshot (pad-undo Phase 1 shipped earlier on this branch). It bundles several arcs developed together under the "we own the branch, one commit" plan, plus a few unrelated items captured at the user's request. Grouped by area, with the spec each serves. ctxctl: separate maintainer module (per specs/ctxctl-bootstrap.md) The out-of-band audit channel leaves the shipped `ctx` binary entirely. Six logic trees (cli/audit, system/cmd/checkaudit, system/core/audit, config/audit, err/audit, write/audit) were relocated under internal/ctxctl/... with imports rewritten. A new separate Go module lives at tools/ctxctl/ (module path github.com/ActiveMemory/ctx/tools/ctxctl; its go.mod requires the ctx module via `replace ../..`; the repo-root go.work links both), so the ctx module never requires ctxctl and can never import it. tools/ctxctl wires `ctxctl audit list|show|dismiss` plus the `ctxctl audit-relay` hook (renamed from check-audit). The audit channel's English lives as plain Go constants under tools/ctxctl (no YAML i18n, no desc engine); the ctx-side audit YAML keys and DescKey* constants were deleted. The Makefile gains ctxctl, install-ctxctl and reinstall-ctxctl. An import-graph guard (internal/compliance/ctxctl_isolation_test.go) asserts cmd/ctx depends on no internal/ctxctl/... package and that the shipped hooks.json carries no check-audit. The repo-local UserPromptSubmit hook invokes `ctxctl audit-relay`. Typed audit errors (per specs/ctxctl-bootstrap.md) internal/ctxctl/err/audit now returns typed, text-free errors (ReadReportError, ParseReportError, WriteDismissalError, ReadDismissalError, UnknownIDError, plus the ErrIDRequired, ErrNoFrontmatter and ErrUnterminatedFrontmatter sentinels). The user-facing English moved to tools/ctxctl, which renders the errors at the command edge through a sole printer (SilenceErrors on the root, mirroring ctx's internal/write/err.With). This closes the one place ctxctl text still lived in internal/, bringing the code into full alignment with the spec. OS-native memory pressure (correctness fix; no dedicated spec, see DECISIONS.md 2026-05-27) Replaces the broken occupancy-percentage DANGER triggers (swap_used/total, memory_used/total) with the kernel's own pressure signal. macOS reads kern.memorystatus_vm_pressure_level; Linux parses /proc/pressure/memory PSI (some.avg10 >= 10 -> Warning, full.avg10 >= 10 -> Danger); other platforms report PressureSupported=false. The dead, always-OK doctor swap row and its orphaned constants are removed. PSI thresholds are named in config/stats for one-place retuning. check-anchor-drift cleanup (per specs/experiments/acdl-session-start.md) Primary-source archaeology proved check-anchor-drift was a deliberately-retired feature (deleted in fc7db22), not a phantom; its stale commands.yaml row is pruned. Follow-up tasks were filed: plugin hooks.json version-skew fix, `ctx system` unknown-subcommand verbatim relay, and Windows memory-pressure exploration. Docs docs/recipes/audit-channel.md and the recipes index reframe the channel as maintainer-only via ctxctl with a repo-local hook; a new "Maintainer Tooling: ctxctl" section in docs/home/contributing.md documents build and install (make ctxctl / install-ctxctl / reinstall-ctxctl). The rendered docs site (site/**) and feed were regenerated. Specs added specs/ctx-add-json-ingest.md (design for `ctx <add> --json` ingest, parked) and specs/experiments/acdl-session-start.md. Tooling and context (dogfooding; unrelated items bundled per request) .gitignore un-ignores go.work/go.work.sum (now tracked for the workspace) and ignores the built ctxctl binaries (/ctxctl, tools/ctxctl/ctxctl). CLAUDE.md and AGENTS.md gain a GitNexus code-intelligence section. TASKS, DECISIONS, LEARNINGS and CONVENTIONS were updated. Verification: both modules pass `make lint` (0 issues) and `make test` (green); `go build` is clean on each. Spec: specs/ctxctl-bootstrap.md Spec: specs/experiments/acdl-session-start.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch lands four independent pieces of work that grew together
on a single feature branch:
ctx pad undo— a snapshot-on-mutate safety net for theencrypted scratchpad.
maintainer-only binary (
ctxctl) rather than inside theuser-facing
ctxbinary.replacing broken occupancy-percentage triggers.
ctxctledge.Each is described below. The audit channel evolved within the branch
(it was first built inside
ctx, then extracted intoctxctl); thenet state delivered to
mainis described here, not thecommit-by-commit churn.
What's in this PR
1.
ctx pad undo— scratchpad safety net (Phase 1)Spec:
specs/pad-undo-snapshot.mdctx padoperation snapshots the prior pad blobto
.context/scratchpad.history/before overwriting.ctx pad undosubcommand restores the most recent snapshot.re-encryption), so plaintext and encrypted modes get the same net.
first. Prune failures degrade to a warning; the safety net never
blocks a write.
undois itself a snapshotting mutation, soctx pad undo; ctx pad undoacts as a redo. Empty history prints a friendly message andexits 0 (quiet on fresh projects).
--list/--to/--prune/--clear,.ctxrcretention tuning, skill/recipe docs) is tracked separately.
2. Out-of-band audit channel via
ctxctlSpecs:
specs/audit-channel.md,specs/ctxctl-bootstrap.mdThe audit channel decouples discipline enforcement from the in-band
commit cadence: a separate Claude Code session runs an audit skill,
drops a structured report into
.context/audit/<kind>.md, and aUserPromptSubmithook verbatim-relays it on the next turn.It lives in
ctxctl, a maintainer/contributor binary kept out ofthe shipped
ctxbinary, so end users never carry an audit hook theyhave no producer for.
tools/ctxctl/(modulegithub.com/ActiveMemory/ctx/tools/ctxctl); itsgo.modrequiresthe
ctxmodule viareplace ../.., and the repo-rootgo.worklinks both.
ctx'sgo.modnever requiresctxctl, soctxcannever import it.
ctxctl audit list | show | dismissplus thectxctl audit-relayhook.ctxctlowns its user-facing English as plain Go constants (no YAMLi18n / desc engine).
_ctx-surface-auditskill +docs/recipes/audit-channel.mdshowthe pattern (ctx dogfoods an internal auditor over its own
internal/tree).make ctxctl,make install-ctxctl,make reinstall-ctxctl(installs to/usr/local/bin/ctxctl).internal/compliance/ctxctl_isolation_test.go)asserts
cmd/ctxdepends on nointernal/ctxctl/...package andthat the shipped
hooks.jsoncarries no audit hook.3. OS-native memory pressure
No dedicated spec — correctness fix; rationale in
.context/DECISIONS.md(2026-05-27)Replaces the broken occupancy-percentage DANGER triggers
(
swap_used/total,memory_used/total) with the kernel's ownpressure signal:
sysctl kern.memorystatus_vm_pressure_level./proc/pressure/memoryPSI (some.avg10 >= 10->Warning,
full.avg10 >= 10-> Danger).PressureSupported=false(Windowsdetection filed as a follow-up).
removed; PSI thresholds are named in
config/statsfor one-placeretuning.
4. Typed audit errors
Spec:
specs/ctxctl-bootstrap.mdinternal/ctxctl/err/auditreturns typed, text-free errors(
ReadReportError,ParseReportError,WriteDismissalError,ReadDismissalError,UnknownIDError, plusErrIDRequired/ErrNoFrontmatter/ErrUnterminatedFrontmattersentinels). Theuser-facing English moved to
tools/ctxctl, which renders the errorsat the command edge through a sole printer (
SilenceErrorson theroot, mirroring
ctx'sinternal/write/err.With). This brings theimplementation into full alignment with the spec's "all
ctxctltextlives under
tools/ctxctl" rule.Docs
docs/recipes/audit-channel.md+ recipes index: the channel ismaintainer-only via
ctxctl, with a repo-local hook.ctxctl" section indocs/home/contributing.mddocumenting build/install.site/**) and Atom feed regenerated.Testing
make lint: 0 issues (ctx module) and 0 issues (ctxctlmodule).
make test: green (ctx module);tools/ctxctlmodule tests pass.go buildclean on both modules.list/show/dismiss/relay (incl. dismissal-digest re-surfacing and
stale-report prefixing); audit error rendering (unknown id, missing
id, malformed-report parse path); per-platform pressure parsing.
Notes for reviewers
ctxbinary — it isctxctl, a separate module. Reviewing the net tree is easier thanthe add-then-extract commit sequence.
tools/ctxctlis a second module; CI / local checks must cover itin addition to the main module.
.gitignoreun-ignoresgo.work/go.work.sum(now tracked forthe workspace) and ignores the built
ctxctlbinaries.Follow-ups (tracked in
.context/TASKS.md)hooks.jsonwith the cwd-anchoredbinary (fixes a per-prompt help-dump from version skew) — high.
ctx system <unknown>: emit a verbatim relay instead of a silenthelp dump + exit 0 — medium.
ctx <add> --jsoningest (design parked inspecs/ctx-add-json-ingest.md) — medium.ctx pad undoPhase 2 flags + retention tuning.