# `templates/` — Shipped Content & Subsystem Internals (detailed reference)

This is the on-demand detail tier for `templates/` (the upstream source of
truth for everything CC ships) plus the detailed per-module behavior. Root
`CLAUDE.md` § Key Files carries one-line current contracts and § Modules
carries a one-line-each module list; this file carries the full detail and
the layered `act:`-by-`act:` history. Read this when working in `templates/`.

Module → template-path wiring lives in `lib/modules.js` (the `MODULES`
registry, extracted from `lib/cli.js` in act:e82baead). Wherever an entry
below says a file is "registered in the X module", that registration is a
`lib/modules.js` array entry — NOT `lib/cli.js` (only the wiring *code* that
consumes the registry stays in cli.js).

## mux config (`templates/mux/config/`)

- `worktree-session-health.sh` — self-healing worktree health check (4
  checks, auto-fix, cache + tmux indicator); the single home for the
  worktree LANE LIFECYCLE (act:dc80380c): PROVISION (a per-project
  `.mux-worktree-provision` file declares gitignored env files copied
  main → worktree at creation and freshness-checked on later health
  runs — a locally-modified worktree copy is never overwritten; reports
  name paths/hashes/mtimes only, never file CONTENTS), `--preflight`
  (pre-spawn source check: every declared provision file exists in MAIN
  and every declared time-boxed `check:` command passes; exit 1 gates
  parallel lane spawns — surfaced as `mux worktree preflight`), and
  `--reap` (removal-time docker container reap: exact
  compose-project-label match on the worktree dir name, containers only
  NEVER volumes, always exits 0 — a failed reap is loud but never blocks
  removal; every removal path delegates here). Also owns the worktree
  MERGE-OR-LOSE noise immunity (act:c008862c): `rebuild_worktree_excludes`
  makes per-worktree excludes LIVE via `extensions.worktreeConfig` + a
  worktree-scoped `core.excludesFile` (git never consults a linked
  worktree's `worktrees/<name>/info/exclude` — the old rules were
  silently dead), copying the user's own excludesFile patterns in FIRST
  so the authored-record negations win, then hiding untracked identity
  symlinks (root-anchored; only while actually a symlink into main) and
  machine-written generated state (names kept in step with
  `GENERATED_STATE_PATTERNS` in watchtower-lib and `GENERATED_STATE_RE`
  in worktree-dirty-check.sh — the merge tail diffs the lists);
  `hide_identity_symlink_typechange` sets git's skip-worktree bit on
  TRACKED `.mcp.json`/`.claudeignore` mux symlinks (typechange churn is
  exclude-immune) — granted only while the symlink resolves into main,
  cleared whenever a real file returns.
- `worktree-health-popup.sh` — tmux popup for health details (triggered by
  double-clicking a `·wt` tab).
- `worktree-dirty-check.sh` — single shared worktree dirty-detection
  (fail-DIRTY on any error, `--untracked-files=all`; the authored-record
  set is derived from the worktree's index — any top-level `.claude/`
  entry holding tracked files — with the static plans/methodology pair as
  fallback floor). act:c008862c taught it the noise/work boundary:
  untracked identity symlinks (`?? .mcp.json`/`.claudeignore`/`pib.db`)
  are churn ONLY while the path is a live symlink — a real file there is
  real work — and a skip-worktree'd path that no longer holds a symlink
  counts DIRTY (a real edit may hide behind the bit; a clean-verdict
  deletion must never race the health heal); untracked generated state
  (`GENERATED_STATE_RE`: advisories-state.json, checklist-stats.json,
  `.claude/verification/`, `e2e/.verify-progress.jsonl` — the shell twin
  of watchtower-lib's `GENERATED_STATE_PATTERNS`, list-diffed at the
  program merge tail) is subtracted from BOTH counting passes, so a
  fully-merged worktree no longer reads "MERGE OR LOSE" over runtime
  residue while a tracked-and-modified copy still counts dirty.
  Delegated to by both `bin/mux` and the pane-exited
  `worktree-cleanup.sh` — whose dirty-close inbox filing mirrors ring1's
  earned-urgency register split (`urgent` only with unmerged commits,
  `normal` for uncommitted-only, `evidence.unmerged` carried) and whose
  clean-close removal reaps lane containers via `--reap`, routing an
  incomplete reap to the desk's sticky notes, never silence;
  sandboxed fixture suite in `templates/mux/__tests__/`,
  which also holds the `mux-fail-loud` suite (bin/mux fail-loud worktree
  creation + resume reuse, run against a private tmux server), the
  `claude-carveout` suite (tracked `.claude/` = authored: never frozen with
  assume-unchanged, never copy-overwritten; only gitignored infra is
  disposable), the `station-liveness` suite (ensure_main_station
  pane-liveness: marker absence is never launch evidence — a live unmarked
  Claude is positively verified via `pane_is_live_claude` and gets
  `@mux_claude` backfilled by `qa_pane_state`, the single shared classifier;
  unknown programs get a loud refusal, never keystrokes), and the
  `qa-badge-reconcile` suite (act:ca6a19a0 — the ·N badge is a projection of
  the queue dir that mux does NOT solely own: watchtower-queue.mjs writes
  descriptors on a merge-pending merge and deletes them on every gate exit,
  fs-only. So the badge reconciles on every `mux qa` verb — reads included,
  since a read is when the operator doubts it — on desk open, and on every
  window switch via an `after-select-window` hook, and `qa_count` /
  `qa_inflight_count` treat a missing desk dir as zero rather than letting
  `pipefail` kill the caller. The suite's coverage guard parses `cmd_qa`'s
  own case list, so a new verb added without a reconcile assertion fails
  instead of shipping a fresh stale badge).
- `mux.tmux.conf` — durable tmux key bindings (DoubleClick health popup,
  copy-mode→pbcopy, Y=unwrap-copy); sourced by `~/.tmux.conf` so bindings
  survive tmux server restarts.
- `unwrap-copy.py` — hard-wrap removal filter piped through by the Y binding.
- `screenshot-to-clipboard.sh` — launchd-driven screenshot watcher; writes
  BOTH PNG image data and a file reference to one pasteboard item (image
  pastes in Claude Code/Gmail/Slack, file pastes in Finder; PNG-only
  fallback); managed by `setupDarwinIntegration()`.

## Skills & cabinet members (`templates/skills/`)

- `templates/skills/` — skill definitions (SKILL.md) and phase files.
- `templates/skills/onboard/` — conversational onboarding skill.
- `templates/skills/cabinet-*/` — 34 expert cabinet member definitions.
- `templates/skills/verify/` — walkthrough-verification skeleton skill
  (Cucumber + Playwright; opt-in via the `verify` module).
- `templates/skills/cc-site-audit/` — /cc-site-audit skill definition +
  install.sh + phase seams.
- `templates/skills/collab-client/` — client-side engagement skill
  (subcommands: default inbox, updates, messages, input, requests,
  progress, delegate, help).
- `templates/skills/collab-consultant/` — consultant-side engagement skill
  (subcommands: default dashboard, inbox, sync, messages, input, requests,
  create, add, edit, invoice, help).
- `templates/skills/engagement-{help,create}/` — 2 redirect stubs pointing
  to /collab-* skills (the only stubs still invoked per skill telemetry;
  the other 7 engagement stubs + handoff-* + `decisions` were pruned
  2026-06-08 as never-invoked dead weight).
- `templates/skills/generate-plan-groups/` — generator skill: groups
  `grp:`-tagged plans for parallel execution (no execution, generator only).
- `templates/skills/execute-group/` — execution skill: runs one parallel
  plan group as a 3-stage pipeline (interactive CP1 at skill level →
  implementation workflow → review+completion workflow); Step 0 reads the
  shared `worktree-invocation-contract.md` (sync main first) and keeps its
  orchestrator-only items inline — Stage 2 agents branch from + merge into
  main regardless of cwd.

## Packed runtimes (`templates/*-runtime/`, engagement, server)

- `templates/verify-runtime/` — `cabinet-verify` npm package source
  (lifted from de[sic]ify's e2e/support/; packed into the install dir
  by `lib/verify-setup.js`).
- `templates/site-audit-runtime/` — `@claude-cabinet/site-audit` npm
  package source (14-check engine with per-check modules in `src/checks/`;
  packed by `lib/site-audit-setup.js`).
- `templates/engagement/` — engagement infrastructure: pure render engine
  (engagement.mjs), pib-db adapter (pibdb-adapter.mjs), schema docs, crypto,
  transport (email/MCP/file/API), secure input, key generation (merged from
  the former templates/handoff/ in the handoff→engagement evolution). The
  engine INTERNALIZES the decision projection (act:1373b98d/85f40e37):
  `renderPacket` synthesizes a `needs_you` decision per `engagement.yaml`
  `decide` item with a deterministic FID_PATTERN-valid backing fid
  (`decisionFid` → `act:<8hex>`) so the ref/refmap/feedback answer-spine
  works as for a real action — deduped against any live `needs:decision`
  action carrying an `engagement_key:<key>` notes marker (structural
  invariant #3). Options are structured `{value,label}` objects
  (comma-safe; no comma-join) with a `decision_type` of `'choice'`
  (`assertPacketInvariants` fail-closes a choice with <2 options) or
  `'free_text'`. The full contract is engagement-schema.md §5.1.
  `engagement-preview.mjs` is the read-only `/collab-consultant sync
  --dry-run` (renders exactly what a recipient would receive — leak gate
  run, ZERO writes/POST; resolves `yaml` gracefully, no hard dep).
- `templates/engagement-server/` — central multi-engagement API server
  (server.mjs, schema.sql, engage-server.mjs CLI, Dockerfile, railway.toml).

## Cabinet infrastructure (`templates/cabinet/`)

- `templates/cabinet/` — cabinet infrastructure (committees, lifecycle, etc.).
- `skill-output-conventions.md` — AskUserQuestion usage conventions, runtime
  output format standards, and the §9 plain-English operator-facing register
  (filed artifact stays technical, spoken briefing is plain English) for
  skill authors.
- `critique-contract.md` — Stage-2 critic output format for the deliberative
  audit workflow.
- `output-contract.md` — the audit output contract read by cabinet members
  at audit time (role framing + finding quality standards); together with
  `finding-schema.json` it is the single source for finding structure and
  the severity vocabulary (`critical`/`warn`/`info`/`idea`).
- `memory-lifecycle-contract.md` — the memory working-set model: MEMORY.md =
  pinned tier + recency block + region pointers (reachability replaces
  enumeration), the `.memory-pressure.jsonl` spill-rate sampler, and the
  gated Stage-2 lifecycle verbs (decay/consolidate/supersede). Stage 1 =
  act:02d15a70; shipped in the memory module (`lib/modules.js`).
- `elicitation-methods.md` — 10 BMAD-derived interview techniques for drawing
  out what users/stakeholders actually want; registered in the planning and
  lifecycle module arrays (`lib/modules.js`); Read-seams wired into
  onboard/seed/plan/checklist-discover SKILL.md files.
- `advisories-state-schema.md` — schema and anti-trap rules for per-project
  advisory-dismissal state (`.claude/cabinet/advisories-state.json`,
  generated at runtime by the advisory pass on first write — never shipped
  as a template to avoid clobber-on-reinstall). Shipped with the watchtower
  module (in the `lib/modules.js` template array, act:f9ea075d — previously
  a dangling reference). The single owner of the dismissal rules is
  `templates/scripts/watchtower-advisories.mjs` (`runAdvisoryPass`); the
  actor, tri-state install-probe, `_meta` throttle key, and bounded
  sorted-token signal scan are all documented in the schema doc.
- `checklist-stats-schema.md` — schema for the per-project checklist hit-rate
  sidecar (`.claude/cabinet/checklist-stats.json` — runtime state, never
  shipped as a template); fires recorded by /execute's post-impl-checklist,
  catches by /debrief's checklist-feedback, consumed by the audit
  `checklist-pruning.md` instruction phase (audit SKILL.md step 6) to retire
  dead checklist dimensions.
- `checkpoint-protocol.md` — shared checkpoint mechanism with three modes
  (Interactive CP, Advisory CP, Full CP) read by both `/execute` and
  `/execute-group`.
- `worktree-invocation-contract.md` — shared universal Step-0 preamble for
  skills that may run from a linked worktree (act:1067adee): (1) detect a
  linked worktree (`--git-common-dir` != toplevel, forced absolute); (2)
  sync main *into* the worktree before reading plan/code state, HALTING the
  whole skill on conflict (operator resolves — never auto-resolve); (3)
  resolve completion artifacts to the MAIN checkout
  (`MAIN="$(dirname "$(git rev-parse --path-format=absolute
  --git-common-dir)")"`; the worktree's `.claude/verification/` is
  gitignored and lost on cleanup); (4) READ main-only `.claude/cabinet/`
  briefing/context files through the same `$MAIN` (they are gitignored, so
  absent in a worktree — §4). Read-and-followed by `/execute` and
  `/execute-group`; caller-specific merge models stay in each caller's own
  Step 0. The MAIN_RESOLVE git-truth (`dirname` of `git rev-parse
  --path-format=absolute --git-common-dir`) has several siblings to keep in
  step, beyond this doc: the `execute-group-implement.js` +
  `execute-group-complete.js` constants, the `action-completion-gate.sh`
  shell spelling, and the `execute/SKILL.md` + `qa-handoff/SKILL.md` inline
  spellings. A drift-guard test (`test/worktree-resolve-drift/`) asserts
  every one of these carries `rev-parse --path-format=absolute`. Registered
  in the planning module (`lib/modules.js`).
- `watchtower-contracts.md` — shared contracts for all watchtower components
  (atomic writes, no-index, session-ID join, schema versioning, project-state
  section ownership — Ring 3 owns `## Last Session` once authored — and
  deferred schemas). Also home of "Detector Symmetry" (act:4d11fd53,
  grp:wt-noise-immunity): every ring-filed inbox category declares its
  mechanical retraction condition + filing exclusions, or an explicit
  exemption — encoded in `INBOX_DETECTOR_REGISTRY` (watchtower-lib) and
  enforced by a structural filing-site test; machine retractions resolve
  through `autoReconcileItem` (`resolution_type: 'auto-reconciled'` +
  `evidence.actor`, excluded from engagement buckets); reconcilers fail
  toward KEEPING (retract only on positive evidence — fid-not-found
  keeps); the long-lived branch exclusion list
  (`defaults.long_lived_branches`, staging/production exact-match +
  `backup/*` glob, consumer-overridable) gates FILING only, never the
  attention surfaces; and the standing-debt aggregation contract
  (act:e888dd63) — a standing condition files ONE item per (project,
  debt-class), APPENDS evidence with `first_seen` = oldest and a
  session-count urgency escalation, and post-resolution recurrence files
  FRESH, never a refile loop.

## Workflows (`templates/workflows/`)

- `deliberative-audit.js` — dynamic workflow script: four-stage audit
  (Review → Critique → Rebuttal → Synthesize). Its `deliberation-report.json`
  also carries `withdrawnFindings` (act:8e1fa16f) — `findings` excludes them
  by design, but the report doubles as the merge's overlay source and the
  per-member file a withdrawal applies to predates the rebuttal, so without
  the extra key a retracted claim merged back in as live.
- `execute-group-implement.js` — Stage 2 workflow: parallel worktree
  implementation + sequential merge. Purely mechanical, no cabinet review.
  Captures a `/validate` baseline on main before the first merge (concurrent
  with implementation, awaited before the merge loop); merge agents revert
  only on NEW failures vs that baseline. Completion artifacts (per-plan
  breadcrumbs + the Completion Report) resolve to the MAIN checkout via a
  MAIN_RESOLVE preamble baked into agent prompts; breadcrumbs persist
  immediately after each merge, before branch deletion/worktree cleanup.
- `execute-group-complete.js` — Stage 3 workflow: advisory CP3 review +
  integration check + completion report. CP3 is advisory only — it never
  halts or reverts. Gates only on NEW `/validate` failures vs Stage 2's
  baseline (`validateBaseline` arg, `knownDebt` operator fallback);
  inherited main debt is reported under `pre_existing_debt`, never gated.

## Watchtower foundation (`templates/watchtower/`)

- `templates/watchtower/` — watchtower foundation: config schema template,
  state file templates (summary.md, per-project), queue item JSON schema
  (incl. the `project_unresolved` + `desk` fields added 2026-06-10).

## Watchtower scripts (`templates/scripts/watchtower-*.mjs`)

- `watchtower-lib.mjs` — shared watchtower library; exports
  `resolveProjectIdentity()`, the canonical "which project is this?" answer
  (git-aware: a worktree resolves to its main repo's config project; doubles
  as a CLI that prints the identity JSON; 12 unit tests in
  `templates/scripts/__tests__/resolve-project.test.mjs`); also exports the
  Ring 3 disk-wins helpers — `updateThreadFile` (thread files: model
  `is_new` is advisory, disk existence wins) and `preserveRing3LastSession`
  + `PROJECT_STATE_LAST_SESSION_HEADER` (Ring 1 never clobbers Ring 3's
  authored `## Last Session` section) plus `writeProjectStatePreservingRing3`
  (race-safe project-state rebuild write: re-read check-and-retry just before
  the atomic write, closing Ring 1's read-then-write race on the Last Session
  merge — Ring 1's call site delegates to it; 20 guard tests in
  `__tests__/ring-state-ownership.test.mjs`); also exports
  `flushFeedbackOutbox()` — Ring 1's feedback-delivery duty (act:6c3a4763):
  reads the GLOBAL `~/.claude/cc-feedback-outbox.json`, resolves the CC repo
  via `resolveCcSourceRepo()` (the single cc-registry → CC-repo-root
  resolver, package.json name match — also used by Ring 1's runtime-drift
  check, act:e81fe82f), writes real files into `feedback/` with
  skip-if-exists guards against `feedback/` AND `feedback/resolved/` —
  bidirectional: a re-report under a longer title matches its
  already-delivered shorter twin, but the reverse direction is narrow (only
  delivery-scheme `{date}-{slug}[-{seq}].md` filenames participate, matched
  on whole dash-token boundaries — no substring fuzz, hand-named files
  excluded — because silently suppressing new feedback is the worse failure
  mode), atomic outbox rewrite ([] on clean pass, failed items kept);
  replaced Ring 3's Phase 2h, which marked items delivered without
  delivering; 16 hermetic tests in `__tests__/feedback-outbox-flush.test.mjs`.
  Also exports `authoredClaudeDirs`/`claudeChurnIsDisposable` (rings
  re-include authored-`.claude/` churn in worktree dirty counts — tracked =
  authored, only gitignored infra disposable; act:e91fdfcf), the consolidated
  thread-reader helpers `loadActiveThreads`/`threadMatchesProject`/
  `threadsForItem`/`projectThreadCursorLines` (Ring 2, Ring 3, and the
  context builder share one reader; Ring 3's slug-substring membership
  fallback was deleted; act:3975348f), and `buildLastSessionBlock`/
  `upsertLastSessionSection` (Ring 3 writes the COMPLETE Last Session bullet
  set, byte-identical to the per-session record, unconditionally;
  act:ac119994). Also exports the recall-fix substrate
  (act:edd79e15/6354a9db): `recentSlice(text, n)` — the most-recent `n`
  chars of a preprocessed transcript (the single definition that replaced
  every Ring 3 `slice(0, N)` front-slice; fail-open returns the whole string
  on a non-positive budget), and `recordSuppression`/`suppressionLedgerPath`
  — appends one structured JSON line per Ring 3 dedup suppression to
  `state/suppression-ledger.jsonl` (fail-open observability, NOT the filing
  decision; the M5 Ring 2 canary reads it; shape in `watchtower-contracts.md`
  "Suppression Ledger"). Also exports `verifyDatabaseConstructor`
  (act:b9414039) — `loadBetterSqlite3` verifies each candidate by
  constructing a throwaway `:memory:` db inside the per-candidate try,
  because better-sqlite3 loads its native addon lazily at first construction,
  NOT at require(), so a bare require "succeeded" on an ABI-mismatched shadow
  copy (a stray `~/node_modules` install) and silently broke ring pib
  collection portfolio-wide — and the memory-reachability helpers
  (act:49cb1c27): `parseMemoryIndex`/`parseMemoryRegionPointers`/
  `memoryGlobToRegex`/`isMemoryFileReachable`/`checkMemoryReachability`, a
  deliberate MIRROR of `scripts/validate-memory.mjs` (the rule owner — the
  watchtower runtime can't import it) kept in lockstep by a parity test: a
  file is reachable via a direct index line (MEMORY.md AND the
  MEMORY-archive.md cold tier) OR a region-pointer glob. Ring 1's
  `checkMemoryIntegrity` delegates to it (the old filename-substring scan
  predated both mechanisms and flagged 203 false orphans while the validator
  passed green). Tests in `__tests__/bsql-loader-verify.test.mjs` +
  `__tests__/memory-reachability.test.mjs`. Lane A of
  grp:wt-noise-immunity added the reconciliation substrate:
  `autoReconcileItem` — the ONE resolution path for machine (ring)
  retractions: resolves through the real queue with `resolution_type:
  'auto-reconciled'`, then stamps `evidence.actor` + caller evidence
  naming WHY (action fid / branch); returns null when the item is no
  longer pending (a human got there first); a failed evidence stamp is
  logged and tolerated — the typed resolution already landed atomically.
  `INBOX_DETECTOR_REGISTRY` — the detector-symmetry ledger mapping every
  ring-filed inbox category to `{reconciler}` or `{exempt}` (exactly
  one), enforced by the structural filing-site test
  `__tests__/detector-registry.test.mjs`: it scans `category:` literals
  at createItem sites across ring1 + ring2 + ring3-close + **ring4 +
  routines** (read-only source scan) and fails on an unregistered
  category or a non-literal category value inside a filing owner
  (act:4d11fd53; act:ea23b3a5 added the last two scripts and their
  `doc-drift` / `routine` entries, closing the gap act:c2f06955 item 5
  named — the contract's "maps every ring-filed category" claim was
  false while ring4 and the routine engine sat outside the scan). Census
  at 2026-07-26: 27 filing sites, 20 distinct categories, 20 registry
  entries. **The census binds a `category:` literal to its NEAREST
  PRECEDING call opener and recognises only `createItem(` and `file(` as
  filing owners** — an injectable filing dep under any other name reads
  as a query filter and its category silently drops out of the census.
  That is not hypothetical: act:ea23b3a5's ring2 memory-budget dep, first
  written as `createItemFn`, dropped `watchtower-health` out of the
  filing set and the suite caught it. Name an injected filing seam
  `file`.
  `GENERATED_STATE_PATTERNS`/`generatedStateIsDisposable` — the JS SSOT
  for machine-written generated state (UNTRACKED `??` lines only:
  `.claude/verification/`, advisories-state.json, checklist-stats.json,
  `e2e/.verify-progress.jsonl` — a TRACKED file of the same name is
  authored by definition), folded into `claudeChurnIsDisposable` for
  ring1's `countRealUncommitted` and ring3's Phase 2a dirty filter; the
  mux shell twin (`GENERATED_STATE_RE` in worktree-dirty-check.sh) is
  list-diffed at the program merge tail (act:c008862c). And
  `resolveProjectFromTranscriptSlug` — the slug-parsing fallback beside
  `resolveProjectIdentity` for CLEANED-UP mux worktrees (act:29001b07,
  filesystem resolution has nothing left to resolve): decodes the
  transcript dir slug's `--mux-worktrees-` tail through mux's own
  `~/.config/mux/projects.json` (longest match on a `-` boundary;
  same-length tie = ambiguous → null), hands the MAIN-checkout path to
  the canonical resolver, and returns null on EVERY failure — the caller
  files `project_unresolved: true`, never a cwd guess. The export NAME
  is a cross-lane contract: ring3-close feature-detects it on its
  namespace import.
- `watchtower-ring3-close.mjs` — Ring 3 session-close transcript processing:
  the knowledge-extraction lenses + the dedup that gates whether an
  extraction is filed. The **recall-fix program** (audit act:7d6a4b3b;
  act:f8e7bd0a/16904ffc/edd79e15/6354a9db/ffae4eec) rebuilt the dedup.
  `isDuplicate` no longer does substring containment over MEMORY.md prose —
  it parses memory TITLES (`parseMemoryTitles`/`loadMemoryTitles`:
  `[Title](.md)` links only, topic-header + region/glob lines excluded, no
  prose-fallback) and matches whole-token "meaningful" overlap (`STOPWORDS`
  + `meaningfulTokens`) at one `OVERLAP_THRESHOLD = 3` across SEVEN corpora —
  memory, thread-cursor, pending, resolved, dismissed (the old 2-token
  `RESOLVED_/DISMISSED_OVERLAP_TOKENS` are DELETED — parity at 3;
  thread-cursors are now their own named corpus, split off the old merged
  `memoryLines`), plus the two that ship OFF by default (below).
  SHORT-TITLE FLOOR: an item that can't muster 3 meaningful
  tokens is never suppressed. M1b: `decisionExtraction` injects the nearest
  saved titles (`selectNearbyMemoryTitles`) into the extraction prompt and
  uses a per-item `covered_by` verdict as a DEFAULT-KEEP rescue gate
  (`modelRescues`) — the model may only RESCUE an isDuplicate-flagged item,
  NEVER suppress (historian landmine act:3975348f; `callFn` injectable for
  tests). M2: `recentSlice` replaced every `compressed.slice(0, N)`
  front-slice (the four knowledge-critical sites feed
  `SINGLE_CALL_TRANSCRIPT_BUDGET`); Phase B `chunkWithOverlap` +
  `mergeChunkExtractions` covers >budget sessions. M5: each of the 4
  suppression sites appends a `recordSuppression` ledger line. The SIXTH and
  SEVENTH corpora (act:a6bb5603 / act:02e21a69) both ship behind `=== true`
  opt-IN flags DEFAULT OFF — inverted from every other `defaults.*` flag,
  because a new corpus can only suppress MORE and a wrong suppression
  destroys knowledge invisibly; the standing rule now lives in
  `watchtower-contracts.md` ("New Dedup Corpora Ship OFF"). `session-actions`
  (`dedup_session_actions_corpus`) feeds action `text` — never NOTES — from
  ONE shared reader (`loadSessionFiledActions`, which commitment dedup already
  used) scoped by `sessionActionWindow` to `[session-start day, session-end
  day]`: bounded at BOTH ends, because `actions.created` is a DATE with no
  time component and an unbounded late end would let a reprocess of an old
  transcript sweep in every action filed since. Cross-session duplication is
  deliberately UNCOVERED and a same-calendar-day sibling session is the known
  residual imprecision. `rules` (`dedup_rules_corpus`) feeds HEADINGS + the
  filename slug of `.claude/rules/*.md` in BOTH the project and global scopes,
  never rule BODIES, with a 0.8 near-identity gate; measured at ship time it
  suppresses 3 of ~3100 filed items (all true positives — precise but nearly
  inert, because an item restating a rule restates its CONTENT, not its
  heading), so the 41-of-78 class act:02e21a69 measured stays OPEN. Both are
  appended LAST in the match order so first-match ledger attribution is
  unchanged. Tests in `__tests__/ring3-dedup-corpora.test.mjs`. Tests in
  `__tests__/ring3-dedup`, `ring3-memory-titles`, `ring3-novelty-rescue`,
  `ring3-recent-slice`, `ring3-chunk-merge`, `suppression-ledger`,
  `ring3-close-lenses`. Lane B of grp:wt-noise-immunity rebuilt the
  filing quality. Attribution (act:29001b07): BOTH entry points — live
  session close and manual reprocess — share ONE `resolveSessionProject`
  call that attributes the session by its TRANSCRIPT SLUG, never the
  runner's cwd (302 of 510 reprocess-recovered extractions were
  confidently misattributed to the runner's project before this): cwd is
  trusted only when it AGREES with the transcript's project-dir slug
  (`transcriptSlugFromPath`/`normalizeSlugKey`); a distrusted cwd falls
  to the lib's `resolveProjectFromTranscriptSlug` (feature-detected by
  name on the namespace import) and then to `project_unresolved: true` —
  inbox filing proceeds, but no state-file or thread key is minted from
  a raw slug. Phase 2c's `completionReviewEmitGuard` was **rebuilt on
  measurement** (act:ea23b3a5): act:9eebbac4's create-vs-complete
  day-window filter is DELETED and the model's `confidence: 'high'` is
  SUPPRESSED instead. Over all 223 items this category had ever filed,
  each joined against the cited action's OWN project db, `high` scored
  30% precision (12 acted-on / 28 dismissed-noise) against 60% for
  medium+low (48 / 32) — the model rates high when a session *talks
  about* an action (files it, plans it, discusses it), not when it
  quietly finishes one. The deleted filter exempted `high` from its
  born-this-session skip, so it suppressed the BEST cohort (same-day
  creation + medium/low: 74%) and passed the worst (same-day + high:
  33%); split at its 2026-07-12 ship date the category went **0-for-23**
  — zero acted-on, 19 dismissed as noise. A **calibration fence** sits on
  the check: the result holds for the Phase 2c confidence rubric AS
  WRITTEN, so rewording that rubric requires re-running the measurement.
  The per-fid dedup corpus gained `expired` + `superseded`, load-bearing
  because the category's 14-day expiry equals
  `COMPLETION_REVIEW_DEDUP_DAYS` (without them a fid leaves every corpus
  exactly when its item expires and refiles forever). A
  quoted-completion-evidence check (`verifyCompletionQuote` +
  `decodeTranscriptText`) ships as **INSTRUMENTATION ONLY** — stamped to
  `evidence.quote_verified`, counted per session, gating nothing: the
  haystack is `preprocessTranscript`'s JSON-serialized output, so a
  substring gate rejects ordinary multi-line prose (a newline is the two
  characters `\` + `n`) and reliably ACCEPTS serialized TodoWrite
  arguments; `decodeTranscriptText` drops `tool_use` blocks for exactly
  that reason, and even decoded, containment proves a span EXISTS, never
  that it means the work is done. Phase 2g (upstream friction) gained a
  **three-arm actionability gate** (act:ea23b3a5, Phase 2f's
  verification-gate shape): a named route from the closed vocabulary
  `cc-feedback | project-tracker | config-change` with a non-empty detail
  files `upstream-friction`; a durable no-route platform constraint files
  `knowledge-extraction` (4 of the 5 friction items the operator KEPT
  exited `captured-to-memory`, so a two-arm gate would have discarded the
  useful half of the lens); anything else files nothing and is COUNTED.
  Contract in `watchtower-contracts.md` ("Upstream-Friction
  Actionability"). coverage-warning is now a
  STANDING-DEBT aggregation (act:e888dd63): ONE pending item per
  (project, debt-class — `COVERAGE_DEBT_CLASS`) whose evidence APPENDS
  per session (session-id union, so a reprocess replay can't inflate
  counts; per-path counts; `first_seen` = oldest evidence date;
  `filed_at` bumped so the expiry clock measures the LAST recurrence),
  urgency escalating with session count (`coverageDebtUrgency`: normal
  at 5, urgent at 15); resolving closes the accumulated view and a
  persisting debt refiles FRESH next session. The `## Last Session`
  block names its authoritative per-session record (`appendRecordPointer`
  → `<slug>/sessions/<date>-<sessionId>.md`; act:8c076580). Phase 2d
  classifies non-durable facts AT extraction (act:471dd701 N2 +
  act:a69c21f8): `derivable` (recomputable from a live source → home
  `derivation`, the recompute instruction visible) and its twin
  `perishable` (true-at-a-moment, will become false, NOT recomputable —
  `perishes_when` names what falsifies it → home `session-record`);
  both ALWAYS FILED to acknowledge/dismiss, never memory, never
  dropped; derivable wins when both apply; the type/home vocabulary is
  the exported `KNOWLEDGE_EXTRACTION_TYPES`/`_HOMES` consts
  (watchtower-queue) with createItem boundary checks + a prompt-parity
  test (`__tests__/extraction-classification.test.mjs`).
- `watchtower-queue.mjs` — inbox queue CRUD library (atomic temp+rename,
  no-index convention); also home of the staff-QA recipient gate
  (2026-06-11): every terminal exit on a `qa-handoff` item is structural —
  `resolveItem` throws without a validated structured `qa_verdict`
  (`validateQaVerdict` + `normalizeQaVerdictToken`; bare "verified" is
  illegal), dismiss/supersede require typed reasons, and expiry never applies
  (`runExpiry` warns only; Ring 2's expiry loop in `watchtower-ring2.mjs`
  exempts the category). `emitPatternPromotion` files one deduplicated
  pattern-promotion inbox item at >=3 sweep instances of a failure class.
  `listItems()` exposes filtered item listing (project/category/status/since)
  for the Ring 3 dedup corpora. **Category expiry is single-sourced here**
  (act:ea23b3a5): `expiryDaysFor` / `categoryNeverExpires` /
  `CATEGORY_EXPIRE_DAYS` / `DEFAULT_EXPIRE_DAYS`, with
  `categoryNeverExpires` sitting beside `expireItem`'s throw because the
  qa-handoff carve-out is STRUCTURAL (returning anything but null for it
  crashes the caller's loop). There are TWO expiry engines — Ring 2's
  `escalateQueueItems` cron and `runExpiry`, which `/inbox` and
  `/briefing` call — and each previously hardcoded 30 days with its own
  inlined carve-out, so a per-category policy set in one was silently
  violated by whichever reached the item first. Both now delegate.
  completion-review is the first override at 14 days; note that a
  shortened expiry can open a refile loop when it meets its filing site's
  dedup window (see the "Category Expiry" contract). Terminal exits on any dispatched-category
  item (`qa-handoff`, `routine` — the DISPATCHED_CATEGORIES set; resolve /
  typed dismiss / supersede / expire) also clear the matching mux
  dispatch-queue descriptor — queued AND in-flight, across all desk dirs —
  so the dispatch queue can't drift from the inbox (act:796fe6dc).
  `applyBatch()` is the SINGLE bulk-disposition path (both /briefing's batch
  dispositions and /inbox's bulk triage delegate to it — never per-item
  dismiss/resolve loops): all-or-nothing pre-validation, typed reason
  required, and a structural refusal of GATED_CATEGORIES items (today:
  qa-handoff) — gated items never batch, each exits only through its own
  gate; `routine` is dispatched but NOT gated, so stale routines are legal
  batch fodder (19-test hermetic suite in `__tests__/batch-disposition.test.mjs`).
  Inbox bulk triage splits sign-off from judgment via `isHighConfidenceSignoff`/
  `partitionForBatchSignoff` — high-confidence pre-drafted extractions
  auto-group into one batch sign-off while judgment items stay per-item
  (act:7f1e4d91). qa-handoff items carry a first-class `evidence.merge_state`
  (act:971570c4): `'merged'` (default — filed after the worktree branch
  merged) or `'merge-pending'` (filed before the merge, ONLY when it's gated
  on something external). `normalizeMergeState` + `QA_MERGE_STATES` are
  exported; `createItem` rejects a present-but-illegal token at the boundary
  (a typo can't read as `'merged'`) and canonicalizes a legal one (additive —
  absent stays absent, readers default to `'merged'`). A `createItem`
  MERGE-COHERENCE guard (act:e859b3a3) also rejects a `'merged'` handoff
  whose `merged_into`/`merged_commit` free-texts a not-yet-merged marker
  (`PENDING`/`unmerged`/…) — pure string check, fenced to qa-handoff. On top
  of it, the MERGED-BY-CONSTRUCTION guards (act:3d1ac2b7) make the merge
  claim verified, not asserted — a two-layer defense with the drain's Step 0
  live re-check: a `'merged'` handoff requires a sha-shaped `merged_commit`
  (omission was the trivial bypass) which `verifyMergedAncestry` checks
  against git (queue-local origin/HEAD→main/master ref resolution, no fetch,
  execFile args, 5s timeout; REJECT only on positive refutation, file with a
  visible `evidence.ancestry_verified: 'unverifiable'` stamp when git can't
  answer); a `'merge-pending'` handoff requires a non-empty
  `evidence.merge_gate` naming the external blocker (rejection messages
  teach the merge-first close-out, self-correcting older skill text); and
  the ONE legal merge-pending→merged flip is `markHandoffMerged` — same
  verification, writes the deferred stage-2 dispatch descriptor directly
  into the mux queue dir (fs-only, desk token shape-guarded, in-flight
  aware), records `evidence.stage2_dispatch` so a failed dispatch retries
  by state — while `annotateItemEvidence` REFUSES `merge_state` patches on
  qa-handoffs (the side door is closed). The recipient gate is unchanged: a
  merge-pending handoff resolves only AFTER `markHandoffMerged` records the
  verified merge, then through a stamped `qa_verdict` cited against the
  post-merge commit. The skill defers stage-2 dispatch and the pickup
  prompt says "merge then QA" when merge-pending (`buildQaPickupPrompt` is
  the library sibling of the skill's merged-path prompt — pared to the gate
  essentials, not byte-parity). 32-test hermetic suite in
  `templates/scripts/__tests__/qa-handoff-gate.test.mjs` + 20-test
  `qa-handoff-merge-state.test.mjs` + 19-test
  `qa-handoff-merged-by-construction.test.mjs` (real temp git repos). Also home of the operator RESOLVE CLI
  (act:00030e1d): `resolve <id> --verdict-file <path.json>` (`parseResolveArgs`
  + `runResolveCli` + an isMain guard) reads the FULL `resolveItem` params
  object `{resolution, resolution_type, resolution_notes, qa_verdict}` from a
  JSON file and reuses `resolveItem`/`validateQaVerdict` verbatim (no new
  validation; unknown top-level keys rejected) — moving apostrophe-laden
  justification strings off the inline `node -e` shell line that broke every
  drain. Fail-loud exit policy: 2 = malformed invocation, 1 = can't-proceed
  (item left pending), 0 = resolved; never leaks the absolute queue path.
  qa-drain's Step 2 is the consumer; 23 tests in `__tests__/resolve-cli.test.mjs`.
  Also home of the surfacing-intelligence apparatus (act:00051dca,
  grp:wt-noise-immunity): `extractCitedActFids` (regex-validated `act:`
  fids from draft text, first-appearance order, capped at 50 — the
  matches are the ONLY values that ever reach a pib-db query, bound as
  parameters); the fold recipe — `foldTokens` (lowercase alnum runs,
  length ≥3, stopworded) + `unigramJaccard` with
  `FOLD_SIMILARITY_THRESHOLD = 0.22` and the `FOLD_MIN_TOKENS = 3`
  short-text floor (calibrated 2026-07-12 against the live pairs a full
  510-draft human read surfaced), `proposeFolds` annotating BOTH sides
  of a pair (reciprocity is the self-validating criterion); and
  `annotateItemEvidence` — the additive evidence-only write path
  (fresh read from disk, PENDING-only fence, deep-equal skip so
  idempotent re-sweeps never rewrite). `isHighConfidenceSignoff` DEMOTES
  on `evidence.freshness.overtaken` or a non-empty
  `evidence.possible_duplicate_of` — the item drops out of batch
  sign-off into individual review, never auto-dismissed. Every
  caller-supplied id → path join routes through `assertLegalItemId`
  (`itemPath` id-shape guard — persisted annotation id arrays flow back
  in as lookups).
- `watchtower-routines.mjs` — routine dispatch engine (act:c2a55c08):
  projects declare interactive routines in watchtower config (`{name,
  trigger, script}`; triggers: time-of-day, interval, path-nonempty,
  session-close); Ring 1 ticks the mechanical triggers, Ring 3 raises
  session-close events, and a firing files a `routine` inbox item + pushes a
  descriptor through `mux qa dispatch` — the SAME hardened desk-dispatch path
  as qa-handoffs, never a fork. Pending-item dedup blocks refiring; stale
  pending firings (default 24h) are superseded + redispatched.
  `sweepExpiredMoments` (act:ea23b3a5) adds **end-of-day moment expiry for
  `time-of-day` triggers**: Ring 1's tick supersedes a pending item once
  the calendar day it was FILED on has ended, instead of waiting 24h for
  the next firing. End-of-day rather than a fixed window is a measured
  choice — the portfolio's one time-of-day routine had 10 real pickups in
  six weeks, 8 within four hours and 2 after (4.4h, 8.6h), so a 4h window
  would have destroyed two genuine pickups to clear one stale item.
  Scoped to Ring 1's PORTFOLIO tick (Ring 3's single-project
  session-close call would otherwise sweep every other project's items,
  once per replayed session during a reprocess drain), classified from
  `item.evidence.trigger` rather than config (so a de-registered routine
  still classifies instead of rotting), and an unparseable `filed_at`
  ABSTAINS loudly rather than sweeping (the `stale_after_hours` path
  treats the same input as stale — two mechanisms disagreeing silently is
  worse than one abstaining out loud). For time-of-day the moment sweep
  now pre-empts the `stale_after_hours` supersede; interval /
  path-nonempty / session-close have no moment and are untouched.
  `buildPickupPrompt` (the dispatch pickup-prompt wording, including the
  resolve-on-run that clears the badge) is exported and reused by the
  build-context missed-routine re-delivery (act:4b4fa7d9), so the wording is
  single-sourced. Declaration schema in `watchtower-contracts.md` ("Routine
  Dispatch"); hermetic suite in `templates/scripts/__tests__/routine-dispatch.test.mjs`.
- `watchtower-advisories.mjs` — environment-advisory engine (act:f9ea075d):
  the single home for the LSP / Railway-MCP / hookify / briefing-file /
  registry-orphan advisories with per-project dismissal memory
  (`.claude/cabinet/advisories-state.json`). Owns all logic + I/O —
  tri-state install-probe (`claude plugin list` → `true|false|null`),
  bounded depth-3 stack scan with sorted-token signal, atomic state write,
  daily throttle via the reserved `_meta` key, never throws. HOME is the
  SessionStart context builder, NOT Ring 1 (Ring 1's launchd cron PATH can't
  reach `claude` for the probe). Schema + anti-trap rules in
  `templates/cabinet/advisories-state-schema.md`; 20 hermetic tests in
  `templates/scripts/__tests__/advisories.test.mjs`. orient/SKILL.md no
  longer re-encodes the dismissal rules — it skips when watchtower owns them
  and otherwise shells this module as one-shot hints.
- `watchtower-build-context.mjs` — SessionStart context builder (consumed by
  `watchtower-session-start.sh`): assembles the ambient state injection
  under a 9500-char budget with named priority constants
  (`PRIORITY_NEVER`=summary..`PRIORITY_PATTERNS`=4, truncated first). Loads
  project-root `.claude/memory/patterns/` (index-style two-field line scan,
  `_*` excluded, top-5 capped via `MAX_PATTERN_LINES`; act:202e5934) and runs
  `runAdvisoryPass` (act:f9ea075d) so captured enforcement lessons +
  environment advisories keep shaping behavior past orient. Also
  AUTO-DELIVERS missed routines at the project's next MAIN session open
  (act:4b4fa7d9): a `routine` item that fired while the desk was closed/busy
  and never ran is surfaced as a never-truncated (`PRIORITY_NEVER`)
  run-first directive at the top of the injection (`renderMissedRoutineSection`,
  inline-capped at 3), gated to the main checkout via `isMainCheckout()`
  (git-dir == git-common-dir; worktree windows stay silent) and built from
  the shared `buildPickupPrompt` so the resolve-on-run wording is
  single-sourced; qa-handoff items are excluded by the `category: 'routine'`
  filter (operator-initiated by design). Also OPENS every injection with a
  session-start acknowledgment directive (act:058d00f0,
  `renderSessionAckSection`, `PRIORITY_NEVER`): a short instruction telling
  the session to begin its first reply with ONE plain-English line
  confirming the context loaded — positive confirmation the hook chain
  works (the injection is invisible in the terminal, so a silent session
  was indistinguishable from a broken hook). Prompt-layer convention only;
  a frontier-model warning still precedes it, resume re-injections skip it;
  tests in `__tests__/session-ack.test.mjs`. Exports
  `renderMissedRoutineSection`/`renderSessionAckSection`/`isMainCheckout`
  for tests.
- `watchtower-snapshot.mjs` — mid-session awareness polling (act:a8057f87):
  the sibling primitive behind `/catch-up`. The SessionStart hook captures a
  per-session BASELINE snapshot (keyed by the stdin `.session_id`, written to
  `state/session-snapshots/<id>.json`) as a guarded side-write that never
  touches the injection; `/catch-up` re-reads current state and diffs it
  against that baseline to show only what CHANGED since session start.
  Deliberately NOT folded into `watchtower-build-context.mjs` (that builder's
  one job is the start-of-session injection). Exports pure `buildSnapshot`
  (reads inbox via `listPending`, threads via `loadActiveThreads`/
  `currentCursor`, git via the `git-attention.json` sidecar — project-scoped
  via `resolveProjectIdentity`, NOT through the builder's truncation path) /
  `diffSnapshots` (inbox by item-id SET so a file+resolve that nets zero
  count still surfaces the new item; threads new/advanced/closed; git
  added/removed) / `renderDelta` (plain-English, concept-first, per-section
  caps) / `pruneSnapshots` (>7d, on each emit). CLI modes
  `--emit-snapshot`/`--snapshot`/`--diff` are additive; the builder is
  untouched. 18 hermetic tests in `__tests__/watchtower-snapshot.test.mjs`.
- `watchtower-narrative-corpus.mjs` — read-only substrate for the `/briefing
  lineage` decision-lineage drill-down (Plan 12, act:a2efc0ce). A PROJECTION
  over existing stores, never a fifth store; writes NOTHING (the read-only
  invariant — the hermetic test asserts the filesystem is untouched). Sibling
  of `watchtower-snapshot.mjs` (same reader family), touches zero ring code
  (the rings are mid-soak). Exports `assembleNarrativeCorpus({cwd})` —
  current project ONLY (worktree-aware via `resolveProjectIdentity`),
  returning inbox knowledge-extraction decisions (captured-to-memory
  duplicates dropped so an inbox draft that became a memory file isn't
  double-counted; `superseded` KEPT) + thread `cursor_history` timelines +
  `per_store_counts` + `skipped_unresolved` (phantom-key items surfaced,
  never silently dropped) — plus the pure, tested `parseDecisionEdges`
  (RECORDED `**Supersedes:**` vs undirected `[[wikilink]]` see-also — a
  wikilink is NEVER a supersede) and `readMemoryDecisions(dir)` (the consumer
  resolves the memory dir per decision; the helper owns the edge rule). CLI
  `--corpus`/`--memory`. The `/briefing lineage` mode (answer-first: "what
  still constrains this project / is X reversible", recorded-vs-inferred
  provenance labeled, causal-pivot chapters below the answer) is the
  consumer; the edge-discipline stage is deferred and MERGES into the
  cross-store `supersede` verb (Ring 2-slow), not a fork. 16 hermetic tests
  in `__tests__/narrative-corpus.test.mjs`. Contract in
  `watchtower-contracts.md` ("Decision Lineage").
- `watchtower-cross-ring-reader.mjs` — the cross-ring reader (act:f5814d6c,
  Stage 1): /briefing's synthesis SUBSTRATE. One call assembles, per
  configured project, Ring 1's parsed state-file sections + git-attention
  facts, Ring 3's recent sessions + thread cursor timelines, and the inbox's
  pending counts + RESOLUTION HISTORY (`resolution_mix` — engaged vs
  discarded vs machine vs untyped; `machine` = `MACHINE_TYPES`
  auto-reconciled ring retractions, excluded from `ENGAGED_TYPES` so cron
  activity never reads as operator engagement). The script
  GATHERS; the consumer synthesizes — no convergence scoring
  (pattern-intelligence-first). Read-only projection (no write path; hermetic
  fs-snapshot test), a sibling of `watchtower-narrative-corpus.mjs`, touches
  NOTHING rings load (soak-safe). Per-store join keys are load-bearing and
  parity-tested vs `resolveProjectIdentity`: state file + sessions + threads
  by SLUG, git-attention + inbox by NAME (the critique caught a name-keyed
  draft that would have passed every test on this portfolio, where names ==
  slugs). No dedicated Ring 2 block BY DESIGN (pattern-promotion rides the
  inbox; the recall canary is already in Ring 1's Standing Issues — a block
  would double-report). Portfolio honesty fields: `unattributed_items`
  (config-key membership partition, not the flag), `orphan_threads`,
  `orphaned_state_files`, `duplicate_path_warning`, `path_exists`; sessions
  order by content `Date:` line (same-day filenames sort randomly);
  superseded/expired = filed-date-window counts, never blended into the
  disposition mix. Exports the ONE definition of `projectCursorTimeline`
  (narrative-corpus imports it — watchtower-lib is ring-loaded, soak-frozen).
  OUTPUT-SIZE CAPS with totals (CP3: uncapped live portfolio = 1.37 MB):
  thread histories newest 3 + `history_total` (cap at the call site — the
  projection itself stays uncapped for lineage), resolution-event list newest
  20 + `resolution_events_total` (mix keeps full counts); 238 KB live after
  caps. Consumer: /briefing Step 1d (REPLACEMENT gather path for the old
  1d/1e raw reads, with a script-absent fallback) — consumed via mktemp file
  + selective Reads, never echoed through Bash — feeding Step 3a + Step 4.
  CLI `--portfolio | --project <name-or-unique-slug> | --project-path <cwd>`,
  `--since` (default 14d UTC; overflow-guarded). Stage 2 (Ring 2 slow
  precompute/cache) deferred to post-soak. 31 hermetic tests in
  `__tests__/cross-ring-reader.test.mjs`. Contract in
  `watchtower-contracts.md` ("Cross-Ring Reader").
- `watchtower-inbox-assessment.mjs` — the "is the watchtower working?" trust
  surface (act:6f37c7ee). A read-only projection answering ONE verdict per
  project, answer-first: `keeping` / `losing` / `not-consumed` /
  `too-little-data`. Sibling of the cross-ring reader / narrative-corpus
  reader family; WRITES NOTHING (hermetic fs-snapshot test), touches NOTHING
  the rings load — the recall canary is read DIRECTLY off
  `state/recall-canary.json`, never through `watchtower-ring*.mjs`. Four
  axes + one honesty signal feed the derived state: KEPT KNOWLEDGE (the
  last-N-decision keep ratio over an EVENT window — newest-N
  resolved/dismissed with NO time filter, so it survives months-apart
  decisions — AND all-time; every ratio carries its denominator so a null
  ratio is EXPLAINED, never fabricated), BACKLOG ROT (pending count/oldest
  age/over-30d/over-90d, CO-EQUAL with the keep ratio — the SURVIVORSHIP
  GUARD: a great keep ratio on an untouched old pile with ~0 recent
  dispositions is `not-consumed`, not `keeping`), THREAD HEALTH (freshness +
  the /threads Step-3 segmentation smells: a session in >5 threads =
  over-eager, threads with no `related_fids`/`lineage` = unlinked), RINGS
  ALIVE (each health sidecar's recency vs cadence + status), and NOT-
  DISCARDING (the recall canary's rate/baseline/alert/`net_durably_saved`,
  null-aware — a null net flags "durable usefulness unknown", never zero).
  Classifies through the cross-ring reader's ONE `bucketResolution`
  classifier (`ENGAGED_TYPES`/`DISCARDED_TYPES` imported, not re-derived —
  those consts + `bucketResolution` were made exports for exactly this).
  Pure `deriveAssessmentState` (the state precedence: not-consumed →
  losing → too-little-data floor → keeping) + injectable
  `watchtowerDir`/`stateDir`/`now` assemblers. CLI `--portfolio` (one
  plain-English state line per project) / `--project <name-or-slug>` (full
  drill-down) / `--window <N>` — answer-first, verbs + counts, NEVER
  decimals. Consumers: /inbox Step 2a (answer-first verdict before the
  walk/bulk/summary choice, healthy projects suppressed) and /briefing Step
  3b (the "backlog rot" weighable candidate). act:ab4927eb
  (grp:wt-noise-immunity) sharpened the verdict:
  `deriveAssessmentDetail` returns `{state, state_reason}` and two
  ABSOLUTE not-consumed arms override a healthy keep ratio — a pile ≥50
  pending (`deep-pile`) or an oldest tail ≥21d on a pile ≥10
  (`old-tail`) can never read `keeping` (the 2026-07-12 morning
  regression: 884 pending / 29d oldest reported all-keeping); rendered
  wordings are state_reason-honest. `worked_recent` and the keep-ratio
  event window EXCLUDE machine acts (`bucketResolution` 'machine' /
  `MACHINE_TYPES`, re-exported from the cross-ring reader; surfaced as a
  `machine_resolutions` count) so cron retractions can't suppress the
  not-consumed verdict. Backlog rot handles AGGREGATED standing-debt
  items: age = `evidence.first_seen` (falls back to filed_at) and weight
  = the accumulated session count (`evidence.session_ids` — the live
  producer spelling — /`session_count`/`sessions`), so one 49-session
  debt can trip `MIN_PILE_FOR_ROT` alone; raw item counts ride beside
  the weighted ones. `assessRingsAlive` also reads Ring 2's
  `draft-annotations-health.json` sidecar (its named reader) —
  informational only, never flips `all_ok`. 12 hermetic tests in
  `__tests__/inbox-assessment.test.mjs`.
- `watchtower-migrate-keys.mjs` — one-time migration re-keying inbox items,
  thread memberships, and state dirs filed under pre-resolver
  worktree-basename phantom keys (dry-run default, `--apply` backs up all
  three stores first, idempotent).
- `watchtower-hook-runner.mjs` — Plan 9 consumer hook runner (act:4a6e907c):
  the formal home for NON-interactive consumer customs at the ring lifecycle
  seams (interactive customs stay with routine dispatch, act:c2a55c08).
  Registered ONCE per seam in `config.hooks` via `--register` — the rings are
  NOT modified, they already iterate `config.hooks[<seam>]`. Resolves the
  target project(s) from the ring's stdin state (single-project
  `ring1-post-collect` / `ring3-close-post`) or `config.projects` (portfolio
  `ring2-slow-post`), discovers per-project scripts in
  `<project>/.claude/watchtower/hooks/<seam>/` (presence = registration;
  name-sorted; `_`/`.` skipped), runs each with cwd=project root +
  `WATCHTOWER_*` env + state on stdin + per-hook isolation + a budget fitting
  the ring's timeout (skips reported `budget-exhausted`, never silent), and
  surfaces Ring 1's `additional_checks` from hook stdout. Bulletproof: always
  exits 0 with a valid envelope. Contract in `watchtower-contracts.md`
  ("Consumer Hook Contract (Plan 9)"); 20 hermetic tests in
  `__tests__/hook-runner.test.mjs` + `phase-shim.test.mjs`.
- `watchtower-phase-shim.mjs` — Plan 9 migration adapter (act:4a6e907c):
  runs an EXISTING repo script (a former orient/debrief phase that expects
  the project root as cwd) as a hook via one `config.hooks` line, with
  cwd/env/stdin normalization, transparent JSON passthrough (preserving
  `additional_checks`), and exit-code propagation. New hooks prefer the
  drop-in directory; the shim is for scripts that already exist.
- `watchtower-sync.mjs` — content-based 3-tier hash-diff + auto-heal for the
  watchtower script set (act:4e05ff53). The runtime launchd/cron executes
  lives at `~/.claude-cabinet/watchtower/`, so a durable one-file fix must
  keep THREE tiers in step: template (`templates/`, source repo only),
  tracked (the project's committed `scripts/` + `.claude/`), runtime
  (`~/.claude-cabinet/watchtower/`). Hash-diffs the tiers from ACTUAL
  on-disk bytes (never a cached manifest, which can itself drift) and either
  reports the divergence (`--check`, read-only — also what `/watchtower
  status` surfaces) or heals it (`--heal`, with `--dry-run` to preview).
  Discovers the managed set by scanning the tiers (union on disk) so the
  detector can't hardcode-drift its own file list. Backs the `/watchtower
  sync` subcommand. Also wired into Ring 1 startup as a READ-ONLY drift
  attention line (act:e81fe82f): each tick, Ring 1 resolves the CC source
  repo via `resolveCcSourceRepo` (watchtower-lib), diffs the live runtime
  against the source templates, and surfaces "watchtower runtime drift: N
  script(s) differ … run `/watchtower sync`" in summary attention — but stays
  SILENT (no nag) whenever there is no authoritative source to compare against
  (no CC repo in cc-registry, no template/tracked tier, no runtime), never
  auto-heals from cron, and is gated on `defaults.script_sync_check`.
  Hermetic suite in `templates/scripts/__tests__/watchtower-sync.test.mjs`
  (+ the Ring 1 integration tests in `ring1-script-drift.test.mjs`).

## Top-level `scripts/` (installed from `templates/scripts/`)

- `scripts/pib-db-path.mjs` (+ `templates/scripts/` mirror) — the SINGLE
  pib.db path resolver (act:5380d35b). `resolvePibDbPath({cwd,env})`:
  explicit `PIB_DB_PATH` wins; else a linked worktree (git-common-dir !=
  git-dir) whose MAIN checkout has a pib.db resolves to that main db (one
  project-level db shared across worktrees); else `cwd/pib.db`. Forces
  `--path-format=absolute` and fails OPEN (any git failure → cwd default,
  never throws at module load). The CLI (`pib-db.mjs`) and MCP server both
  call it; the MCP server's old `resolveDbPath()` + 8KB size heuristic are
  DELETED (a stale >8KB worktree db could win over main). Deliberately
  SEPARATE from `pib-db-lib.mjs` (preserves its no-native-module contract);
  mirrors the shell spelling in `action-completion-gate.sh`. Registered in
  the work-tracking AND audit module templates (`lib/modules.js`); the
  engagement overlays (`templates/engagement/pib-db-patches/*.mjs`) and
  `load-triage-history.js` route through it too. 10 tests in
  `test/pib-db-path/`.

## Briefing templates (`templates/briefing/`)

- `templates/briefing/` — project briefing scaffolds for consuming projects
  (`_briefing-*-template.md`). The finding-format block was removed 2026-07-05
  (act:1bfc9999): members follow the audit output contract
  (`output-contract.md` + `finding-schema.json`), not a hand-maintained YAML
  in the briefing.

---

# Module internals (detailed behavior)

Root `CLAUDE.md` § Modules lists each module in a line or two of current
behavior. This is the full per-module detail with `act:` history. Seventeen
modules total.

## session-loop (mandatory)

The LEGACY orient + debrief skeleton skills plus shared session verbs.
Watchtower (default-installed) is now the live session loop; orient/debrief
are the opt-out fallback, kept shipping via this mandatory module.
`/orient` leads with the frontier-model watchdog (core phase #0): compares
the session model against the per-operator `frontierModel` key in
`~/.claude/cc-registry.json` (set via the `--frontier-model` installer flag;
exact match for full model IDs with bracketed runtime suffixes like `[1m]`
stripped, substring match for family aliases) and warns loudly on mismatch —
visibility only, nothing is pinned or blocked. Watchtower installs get an
earlier SessionStart boundary in `watchtower-session-start.sh` (reports
SKIPPED, not passed, when the payload has no model id). 13 tests in
`test/frontier-watchdog/`. `/orient` no longer re-encodes the
environment-advisory dismissal rules: when watchtower is installed the
SessionStart context builder already surfaced them (advisory pass,
act:f9ea075d) so orient SKIPS the LSP/hookify/briefing-file checks; without
watchtower it runs them as one-shot hints (preferring to shell
`watchtower-advisories.mjs`, the single rule owner).
`/debrief`'s cabinet-consultations step (step 3) is structurally BLOCKING
(act:f3cbbd37): roster discovery must be evidenced from `_index.json`, a
failed member spawn surfaces and STOPS debrief (the user retries or
explicitly waives — Claude never waives), and step 16b (pre-summary
verification, also BLOCKING) re-checks the conversation's agent-tool history
for one completed spawn per debrief-mandated member before any report; both
gates apply in Quick Mode unchanged. User phase emphasis ("make sure the docs
get updated") is DEFENSIVE, never scope-limiting — narrowing requires
explicit "only/skip" language. Both skeletons' cabinet-consultations phases
persist ACTIONABLE member findings before the briefing/report renders
(act:84daa7bc) — `advisor-finding` inbox item (same channel, cap, and
dedup-vs-pending discipline as Ring 3 Phase 2m; see "Session Advisor Pass" in
`watchtower-contracts.md`), pib-db action fallback without watchtower, loud
UNTRACKED marker with neither — and the rendered line cites the filed id;
debrief's 16b also verifies persistence.
Ships `/session-handoff`, the **default session-close verb**: a thin wrapper
over `/close` plus the handoff extras the rings can't own after walk-away
(act:9ce4dfde — repointed off full `/debrief`, whose ring-redundant capture
was the defect). It runs three phases — (1) `/close` (commit + push, and on
a worktree its conditional gated-merge offer), then debrief's
`qa-handoff-sweep.md` phase (a `/qa-handoff` for every worktree→main merge
this session still needs one for) and `methodology-capture.md` (the one
capture phase with no ring twin — Ring 3 only DETECTS an already-written
`.claude/methodology/` record, it can't author one or ask the four-choice
question, so it stays on the default path); the heavy debrief capture
(lessons/knowledge/upstream-friction/session-advisors) is NOT run — the
rings own it after close; (2) propose a
next-session seed prompt (non-QA work thread only — QA drains belong to
window 1's standing station), operator approves/rejects, and ONLY on approve
launch the next session in a **fresh worktree** (via the `mux handoff
<descriptor>` verb — `create_worktree` + `queue_claude_start`; window named
by work-context, not the prompt; loud main-checkout fall-through only when
isolation is impossible). The seeded session merges to main when done;
main-only tail work (propagation, dogfood reinstall, publish) is planned
explicitly for window 1 — fresh worktrees lack `.ccrc.json`. A third,
non-blocking **epilogue** phase then runs in the closing tab (after the next
window is already up): it surfaces this project's watchtower threads/cursors
in plain English (read-only, temporal-labeled, cap 3) and offers at most one
well-explained inbox decision (drained via `applyBatch`, gated/dispatched
categories excluded) — strictly skippable, two frozen sections, growth routes
to `/briefing`. Plain `/close` is the "nothing queued" close; plain
`/debrief` is the opt-in heavy synchronous capture (kept, off the default
path — act:9ce4dfde). The sweep
self-skips without watchtower; the launch degrades to a saved seed path
without mux; the epilogue self-skips without watchtower.
Also ships `/orient-classic` and `/debrief-classic` (act:6fa60c7d):
STANDALONE pre-retirement copies of the orient/debrief skeleton skills, kept
invokable so the orient/debrief retirement (the watchtower transition) is
reversible at the per-project flip. They do NOT delegate to the live
orient/debrief (which change at retirement) — they ARE the old skills under a
new name, with `name`/`description`/`argument-hint` frontmatter and the
`related:` phase paths rewritten to each skill's own `-classic/phases/` tree
so they never depend on the live skills. orient-classic ships SKILL.md only
(phases use skeleton defaults, like live orient); debrief-classic ships
SKILL.md + the same 5 instruction phases as live debrief
(audit-pattern-capture, methodology-capture, record-lessons,
upstream-feedback, qa-handoff-sweep), registered as individual file entries
in the session-loop `templates` array in `lib/modules.js`. 4 tests in
`test/session-loop-classic/classic-skills.test.js` guard registration,
`-classic` naming, and the standalone guarantee (no refs into the live
orient/debrief phase trees).

## hooks (default-installed)

PreToolUse guard scripts (destructive git ops, raw SQL on the actions table,
edits to CC-managed files, action quality/completion gates), the
skill-telemetry hooks, and two read-only tools over what they produce —
`cc-drift-check.cjs` and `scripts/skill-usage.mjs` (dead-skill reader over
the telemetry JSONL, surfaces never-invoked/stale skills; wired into
`cabinet/eval-protocol.md`). All guard hooks read the tool payload from
**stdin** (`.tool_input.*`), never from a `$CLAUDE_TOOL_INPUT` env var (CC
doesn't set one — a hook that reads it silently no-ops). The action
quality/completion gates now match the real `mcp__pib-db__*` MCP tool names
(act:ff693d4c — the old bare matchers never fired) and append a
fired-at-least-once record to `.claude/state/hooks-fired.jsonl`; a repo-local
`node --check` PostToolUse dev hook (`scripts/node-check-hook.sh`, registered
by `mergeCcDevHooks()` only when package.json name is `create-claude-cabinet`)
catches syntax errors in the source repo.

## verify (opt-in, off by default)

Cucumber + Playwright walkthrough harness. Runtime at
`~/.claude-cabinet/verify/<version>/`. /verify skeleton skill + opt-in
/plan, /execute, /debrief integration phases.

## site-audit (opt-in, off by default)

14-check deployed-site quality audit. Runtime at
`~/.claude-cabinet/site-audit/<version>/`. /cc-site-audit skill + comparison
mode + standalone HTML report.

## engagement (opt-in, off by default)

Ongoing client-engagement management built on pib-db — per-recipient packets
(rendered projections of the work backlog), role-gated billing (with
`/collab-consultant invoice [YYYY-MM] [--itemized]` rendering a month's
invoice document for upload to the operator's bank Relay's autofill — pure
`renderInvoiceDoc`, an `invoicing:` config block, written to
`invoices/<YYYY-MM>.html`; act:3ba771d9), client feedback flowing back as
events, plus secure credential handoff (RSA-OAEP + AES-GCM via OS dialog,
provider-agnostic email/MCP/file/API transport). 3 primary skills
(/collab-client, /collab-consultant, /setup-accounts) with 2 redirect stubs
for old skill names. `requires: ['work-tracking']` (the pib-db adapter is the
engine's data source). Evolved from the former handoff module.

## watchtower (default-installed, opt-out)

THE default session loop. Continuous background state management replacing
the legacy orient/debrief with four
processing rings (mechanical cron, Claude intelligence, session-aware,
periodic truth reconciliation). Installing the module ships the skills + a
dormant SessionStart hook; the metered background rings start only on
`/watchtower install`, so a fresh default install incurs no API cost until
the operator activates them. Opt out to fall back to the legacy
orient/debrief loop. All four rings implemented: Ring 1
(mechanical heartbeat, 5-min cron; also delivers the global CC feedback
outbox every tick via `flushFeedbackOutbox()`, ticks declared-routine
triggers via `runRoutinePass()`, surfaces a READ-ONLY
watchtower-runtime-drift attention line each tick via `watchtower-sync.mjs`'s
`analyze` (act:e81fe82f — runtime vs CC source templates; silent when no
source tier is resolvable, never auto-heals, gated on
`defaults.script_sync_check`), and — when the project is the CC source repo —
raises a loud TRIAGE AT ARRIVAL attention line whenever `feedback/` root or
`proposals/` is non-empty, recomputed every tick so it cannot expire while
files remain; act:b08efbc2; and counts overdue actions — `status IN
('open','in-progress','blocked') AND due GLOB '????-??-??' AND due <=
date('now')`, the GLOB guarding malformed free-text `due` and `<=` matching
pib-db-lib's convention — surfacing overdue + stale-project +
completion-candidate counts in the per-project state file's Standing Issues
(the data feed for /briefing's backlog-hygiene nudge) and an overdue line in
summary attention; act:4ca9af18. Standing Issues also renders the **flagged
action LIST** — fid + one-line text, capped (`FLAGGED_ACTIONS_SQL` +
`renderFlaggedEntry`), not just a count — so the background system surfaces
WHICH actions are flagged at parity with orient's work-scan (act:b1b21a15;
the absorption proof that this is Ring 1's job, NOT a new Ring 2 query —
Ring 2's fast tier holds no pib work-state). The flagged scope stays active
work (open/in-progress/blocked), deliberately narrower than orient's
`completed = 0` which also surfaced flagged *deferred* actions. Standing
Issues also renders the M5 over-suppression **recall line** (act:6354a9db) —
Ring 1 reads `state/recall-canary.json` (written by Ring 2 slow's
`recallCanary`) via `readRecallCanary` and surfaces an alerting project's
sample, so the operator can eyeball whether the dedup is wrongly killing
novel lessons. pib-collection failures are LOUD, never "no data"
(act:b9414039): `collectPibState` logs both error paths, the per-project
state file renders `pib-db error: <msg>` distinct from "No pib-db data.", and
summary attention aggregates portfolio-wide pib errors into ONE line (almost
always a single system-level fault; Ring 2's `openPibDb` logs instead of
silently nulling); the memory-integrity check delegates to
`checkMemoryReachability` (watchtower-lib) — reachability, never
filename-substring (act:49cb1c27). Standing Issues also renders the
**cabinet-roster ambient lines** (act:ea23b3a5, `readRosterMetrics` +
`renderRosterEntries`): dormant-skill and stale-briefing counts stopped
being `advisor-finding` queue items and became a Ring 2 measurement that
Ring 1 renders — the same relationship as the recall canary (the
measurement is Ring 2's, the render is Ring 1's, because Ring 1 owns the
per-project state file). Rendered ON CHANGE only and carrying skill
NAMES, because ambient state has no dismiss verb and a number that never
moves is furniture by the third read; the count itself had to be fixed
first, since `ROSTER_DORMANT_LIST_CAP` saturated it and every project
ever measured reported exactly "12 dead". Ring 1's worktree-unmerged +
branch-diverged detectors flag on CONTENT, not ahead-count (act:a152cf6c): a
squash-merge leaves a branch a non-ancestor of main with phantom "ahead"
commits but an IDENTICAL tree, so the old ancestry/`aheadCount` gates flagged
every squash-merged session permanently. `hasUnmergedContent()` runs a trial
in-memory merge (`git merge-tree --write-tree`, git >= 2.38) — main's tree
changes iff the branch carries real unmerged content; squash-robust and
fail-toward-flagging on any git error. `aheadCount`/`isMergedInto` are
retained for the human-readable line only. Compares against `origin/<main>`
(`resolveMainRef`; warns when local main lags its remote) and writes a
structured `state/git-attention.json` sidecar that
`watchtower-build-context.mjs` re-verifies against live git before relaying
any banner — so a stale "MERGE OR LOSE" line can't survive a merge that
already happened; act:6f36cbe2, act:a136b362. Since grp:wt-noise-immunity
Ring 1's tick also RETRACTS what the rings filed — every machine
retraction routes through `autoReconcileItem` (watchtower-lib;
`'auto-reconciled'` + `evidence.actor`, excluded from engagement buckets
portfolio-wide): `autoResolveBranchDivergedItems` retracts
branch-diverged items whose branch merged, is verifiably gone
(membership in a successful branch listing — a failed git call retracts
nothing), or sits on `defaults.long_lived_branches` (staging/production
exact + `backup/*` glob) — an exclusion that gates FILING ONLY, so an
excluded branch stays visible on all four attention surfaces (attention
line, git-attention sidecar, summary, Standing Issues; act:98649971);
`autoReconcileCompletionReviews` resolves completion-review items whose
action fid is verifiably CLOSED in its own project's pib-db (opened
read-only; fid-not-found KEEPS the item — act:9eebbac4);
worktree-unmerged filing splits an earned-urgency register
(act:c008862c): `urgent` only with real unmerged commits, `normal` for
uncommitted-files-only (`evidence.unmerged`), with REGISTER-AWARE dedup
— a register change supersedes + refiles under the current register, and
escalation is damped (a soft→urgent flip needs corroborating commits
ahead, since `hasUnmergedContent` fails toward true on git blips);
`countRealUncommitted` excludes generated runtime state via the
watchtower-lib SSOT. CP3 hardening: untracked filenames pass as execFile
args (never shell-interpolated), every branch-name path gates on
`isSafeRefName`, and the detector census fails loudly on a non-literal
`category:` inside a filing owner), Ring 2 (Claude intelligence,
fast 5-min + slow 30-min tiers; the slow tier also runs `recallCanary`
(act:6354a9db) — reads + prunes `state/suppression-ledger.jsonl`, writes
`state/recall-canary.json`, an over-suppression-only trend detector gated on
`defaults.recall_canary`; `buildRecallCanary`/`readLedgerRecords`/`withinDays`
exported — and `runDraftAnnotationSweep` (act:00051dca; gated on
`defaults.draft_annotations`, default ON): annotates pending
knowledge-extraction drafts with freshness (cited `act:` fids positively
CLOSED in the item's own project pib-db — parameter-bound lookups via
`openPibDb`, now `timeout: 5000` for lock parity with Ring 3) and fold
proposals (the watchtower-queue recipe), demoting them out of batch
sign-off, never dismissing; act:09184ad7 added a SECOND fold corpus —
pending pattern-promotion items, grouped per `evidence.target_member`
(cross-member similarity is never a duplicate), so near-duplicate
pattern variants arrive pre-annotated for the next consolidation pass
(`pattern_items_scanned`/`pattern_fold_annotated`/
`unscanned_pattern_items` counters); act:ea23b3a5 added a THIRD corpus —
pending `raised-unhandled` loose ends, grouped per project
(`loose_end_items_scanned`/`loose_end_fold_annotated`). That third corpus
is where the spec's "aggregate recurring loose ends into ONE item"
landed after measurement refused the merge: `proposeFolds` scores with
`overlapCoefficient` (act:421a8ab2 retired `unigramJaccard`), whose
`min()` denominator lets two three-token titles sharing ONE word clear
`FOLD_SIMILARITY_THRESHOLD = 0.22` — four false folds in fifteen pairs
against the six live titles — and the five timelog specimens were
resolved `deferred` (real work the operator postponed, across two
projects and three date ranges), so merging would have buried which gaps
remain. The threshold was calibrated for ADVISORY retrieval, not a
destructive merge: **a lexical matcher may propose, never silently
suppress.** Unreadable dbs and out-of-config
items are counted LOUDLY (`skipped_projects`/`unscanned_items`) and
every run writes the positive-confirmation sidecar
`state/draft-annotations-health.json`, so "no annotations" is
distinguishable from "never ran". The slow tier also owns the
**memory-budget standing debt + its retraction** (act:ea23b3a5):
`surfacePersistentViolations` files ONE accumulating `watchtower-health`
item per project instead of a fresh one every 7 days — keyed by CALENDAR
DAY (a per-run key would count ~48 cron ticks a day and make the replay
guard dead code), appended via `annotateItemEvidence` so `filed_at` is
never bumped, carrying `session_count` because that is the spelling
`itemSessionWeight` already reads — and `retractClearedMemoryBudgetItems`
retracts on the SAME pass over the `passProjects` `runMemoryHygiene`
already computes. Deliberately not in Ring 1: a second memory-health
producer is forbidden by name in `watchtower-contracts.md`, and at a
30-minute throttle against ring2's 30-minute cadence it would buy zero
freshness. Both selectors match the LEGACY `{violations, streak}` title
shape as well as the new `debt_class`, because every item on disk when
this shipped carried the former. The cabinet-roster review now files only
`uncovered-tech` (a proposed cabinet seat is a decision); `dormant-skill`
and `stale-briefing` became ambient metrics in `state/roster-review.json`
(`parseDormantSkills` reports TRUE counts — the cap bounds the NAME list
only) and `retireRosterFindingItems` supersedes any of the two retired
kinds still pending, since the filing site was the only code that ever
touched them. The slow tier's `scanAuditPatterns`
(the pattern-promotion filing site) now runs the act:09184ad7
emission-quality SHAPE GATE — `patternHasRequiredShape` requires the
`**Evidence:**` + `**Gap:**` blocks Phase 2e's prompt mandates, rejecting
document echoes (wave ledgers, dependency graphs, recipes, transcript
fragments — 17/144 items in the 2026-07-26 consolidation) BEFORE the
routing Claude call; rejections are counted loudly
(`rejected_malformed_total`/`last_scan_rejected` in
`state/pattern-detection.json`) and their hashes marked processed, never
retried. Calibrated against the consolidation corpus: 120/120 legitimate
bodies pass, 17/17 fragments fail. act:02e21a69 closed the hole that gate
left — it checked a `**Gap:**` block EXISTED, not that it ASSERTS a gap, so a
well-formed positive ("**Gap:** No gap — this is positive practice worth
reinforcing") shipped as a promotion. `gapBlockAssertsGap` rejects an empty
or gap-negating block, folding into the existing counters AND its own
`rejected_gap_negating_total`/`last_scan_rejected_gap_negating` pair;
calibrated on the live 2432-section corpus at 12 rejections, with `nothing`
measured OUT of the negation list (it rejected a real gap opening on a
quotation). Opt-OUT (`defaults.pattern_gap_assertion`), unlike the two dedup
corpora — it is a defect fix, not a new suppression surface; hermetic suite in
`__tests__/ring2-pattern-gate.test.mjs`), Ring 3 (session close, transcript
processing; fires session-close routines as Phase 2j2; runs the standing
session advisors as Phase 2m — see
below; runs three session-close extraction lenses (act:4ff2cfb3) — Phase 2n
raised-but-unhandled (loose ends the session raised but neither did nor filed
→ inbox category `raised-unhandled`), Phase 2o skill-candidate (a manual
procedure repeated by hand → `skill-candidate` inbox item), Phase 2p
checklist-catch (a surfaced change-impact check that caught a real bug →
`checklist-stats.json`); its Phase 2e quality-pattern prompt is the exported
`QUALITY_PATTERN_SYSTEM_PROMPT` carrying the act:09184ad7 generation-side
exclusions — no-gap positives, transcript-artifact observations, document
echoes are never patterns — the prompt half of ring2's mechanical shape
gate, guarded by `__tests__/ring2-pattern-gate.test.mjs`; its Phase 2h
feedback "flush" was deleted 2026-06-12
— it marked without delivering), Ring 4 (periodic truth reconciliation,
act:36dae795 — stage-1 BUILT, weekly cadence; cron/launchd registration is a
deferred `/watchtower install` follow-up). Ring 4 mechanically compares
documentary CLAIMS against codebase REALITY and files `doc-drift` inbox items
(`filed_by: 'ring4'`): (a) backtick-fenced repo-relative paths missing on
disk, gated on a real top-level dir + gitignore-awareness, plus precision
gates that skip removal-narrative spans (a same-clause `deleted`/`replaced
by`/`renamed` — one live occurrence re-arms the claim), suffix-fragments of a
longer real path referenced in the same doc (`bin/mux` pulled out of
`templates/mux/bin/mux`), and generated-artifact path segments
(`__pycache__` etc.; act:acbf5442); (b) opt-in config-declared count-claim
rules. Current-reality docs only (root CLAUDE.md, system-status, briefing
files, memory entries — `.claude/plans/` + `.claude/methodology/` excluded as
historical record); the memory pass is DRIFT-SUPERSEDE only (hygiene stays
Ring 2 slow). Weekly rotation prioritized by git recency, per-run project (3)
+ item (8) caps, deduped `doc-drift` items. Stage 1 makes NO Claude API call.
`templates/scripts/watchtower-ring4.mjs` + `watchtower-ring4-runner.sh`
(PID-locked, modeled on Ring 2's); 21 hermetic tests in
`__tests__/ring4-reconcile.test.mjs`. Config flags
`defaults.truth_reconciliation` + `ring_frequencies.ring4` (604800); contract
in `watchtower-contracts.md` ("Ring 4 — Truth Reconciliation (built, stage
1)"). SessionStart/End hooks register conditionally via
`mergeWatchtowerHooks()`; the SessionStart context builder
(`watchtower-build-context.mjs`) is the home for the environment-advisory
pass (`runAdvisoryPass` in `watchtower-advisories.mjs`, act:f9ea075d — NOT
Ring 1, whose cron PATH can't reach `claude` for the install-probe) and loads
project-root `.claude/memory/patterns/` (top-5) so enforcement lessons keep
shaping behavior past orient (act:202e5934).
Ships `/watchtower` (install/status/configure/uninstall/sync — configure now
manages declared routines; sync content-hash-diffs the 3 script tiers and
heals a single-file drift between full reinstalls, act:4e05ff53), `/inbox`
(triage extracted knowledge and signals, formerly `/decisions`; its
bulk-triage mode carves out gated items first — presented one-by-one with the
/briefing Step 3b two-path offer — and executes each approved ungated group
via ONE `applyBatch` call), `/briefing` (v2: the single daily surface with
/recap alias — a portfolio-wide curator chunked by project, decisions never
interleaved; per-chunk batch dispositions executed in-conversation via
`applyBatch` with typed reasons; anti-accretion invariant: every "briefing
should show X" duty routes through the curator's top-N selection inside the
owning project's chunk, no permanent sections; Step 3b enumerates the
weighable candidate classes — a new duty adds an entry there, never a section
— most recently the backlog-hygiene nudge (fires on the per-project state
file's >=3 stale or >=2 completion-candidate counts → consider /spring-clean)
and environment advisories (the context builder's `suggested`
advisories-state entries, read-only, no probe; act:5e8a9e89), and unprocessed
mux DX captures (active items in the mux global `dx.json` whose origin
resolves to a walk project, read at Step 1h, drained via `manage-dx.py
done`/`mark-filed`; act:891fb9e4); two floors — pending gated items are never
curated out, open `qa_verdict.filed_gaps` keep a one-line roll-up; Step 3c
runs the M4 SAVE-direction batch sign-off (`partitionForBatchSignoff` over the
high-confidence pre-drafted memory drafts) BEFORE the decay proposal
(act:ffae4eec); decay = the curator's default stale-batch proposal per chunk,
never a global sweep; full mode also spawns the live advisor panel — Step 1g
— whose observations route through Step 3b/Step 4, never their own section),
`/threads` (read-only cross-project thread map — renders work-thread cursors
in plain language and flags segmentation collapse), `/catch-up` (read-only
mid-session awareness refresh — diffs current watchtower state against a
per-session baseline captured at session start and shows only what CHANGED
since; new inbox items, advanced/new threads, new/cleared git attention,
freshly-due deferrals; thin orchestrator over `watchtower-snapshot.mjs`;
act:a8057f87), `/close` (fast session-end verb — commit + push, plus on an
ORPHANED worktree only (unmerged content AND no qa-handoff filed) a
conditional offer to land the branch via the existing `/execute` Step 8 /
qa-handoff merge-now recipe, so the rings don't file `worktree-unmerged`
(act:9ce4dfde) — `/qa-handoff` stays the primary merge path; the background
rings handle inventory/capture/routing after you walk away; targets 15–30s,
never `--no-verify`; act:8bf86966), and `/qa-handoff`
(package a worktree branch into a surfaced `qa-handoff` inbox item — what
merged, what the worktree could NOT runtime-verify, what hangs on the next
step — then push it to the desk's main window via `mux qa dispatch`; usually
filed right after the merge — an unmerged named branch now PROMPTS merge-now
(Claude merges → `merged`) vs keep-gated, never a silent `merge-pending`
default (act:350e7373); merge-pending is opt-in, filed BEFORE the merge only
when the merge is gated on a named external blocker (`evidence.merge_gate`,
required at the boundary; act:3d1ac2b7), in which case stage-2 dispatch
defers until `markHandoffMerged` records the verified flip — see the
watchtower-queue `merge_state` entry above; stages 1–2 of the QA-handoff
protocol, `.claude/plans/qa-handoff-protocol.md`). Stage 3 — the drain — is
enforced by the staff-QA recipient gate in `watchtower-queue.mjs`: qa-handoff
items cannot leave the queue silently (validated `qa_verdict` on resolve,
typed reasons on dismiss/supersede, never auto-expires); the playbook, tiers,
and verdict shape are single-sourced in the qa-handoff skill's "The recipient
gate" section, the producer emits `risk_surface` + `tier_hint` (consumer
re-validates), and `/briefing` re-surfaces `qa_verdict.filed_gaps` until
closed. The standing session advisors (historian, system-advocate,
user-advocate, anthropic-insider) are re-homed to the session boundary
(act:aded4fc9): new standing-mandate vocabulary `session-close` (Ring 3 Phase
2m — index-driven roster via `_index.json`, scoped by
`directives.session-close`, findings filed as `advisor-finding` inbox items,
max 2/member/session, deduped vs pending+resolution corpora, flag
`defaults.session_advisors`, reuses Ring 3's pinned-sonnet run) and `briefing`
(/briefing's live panel, spawned only on full-mode invocation); their
orient/debrief mandates were REMOVED — /briefing re-surfaces fresh findings
near the top of the owning project's chunk; contract in
`watchtower-contracts.md` ("Session Advisor Pass").
`mux qa drain` is inbox-synced (act:796fe6dc): descriptors are cross-checked
against inbox status (resolved ghosts skipped + removed), the offered
descriptor parks in `in-flight/` instead of being deleted (restored on next
drain if no verdict lands; gate exits clear it), and an empty dispatch queue
falls back to walking the inbox for pending qa-handoff items. Ring 3's
`threadCapture` mints right-sized, overlapping, concretely-named threads
(initiative + project-area + cross-cutting zoom levels; earned
multi-membership; rich `display_name`) and writes per-thread `cursor_history`
— an append-only array of timestamped cursor snapshots (current cursor =
`cursor_history[last].cursor`), so the evolution of understanding is
preserved rather than overwritten.
Project identity is keyed by the canonical resolver — `resolveProjectIdentity()`
in `templates/scripts/watchtower-lib.mjs` (git-aware: worktrees resolve to
their main repo's config project). Ring 3, `watchtower-build-context.mjs`, and
mux's `worktree-cleanup.sh` all route through it (Ring 3's six independent
`basename(cwd)` derivations were deleted — worktree sessions no longer file
under phantom keys). The queue item schema carries `project_unresolved`
(resolution failed at filing) and `desk` (mux desk at filing time; display
metadata only, never a grouping key); `/inbox` renders an "unresolved project"
group for flagged items. `watchtower-migrate-keys.mjs` re-keys pre-resolver
phantom-key data (dry-run default, `--apply` backs up first, idempotent;
wired as one-time install step 2b in the watchtower SKILL.md, whose post-copy
coherence assertion checks `resolveProjectIdentity` + `updateThreadFile` +
`preserveRing3LastSession` are importable). Thread files and the project-state
`## Last Session` section follow a disk-wins / section-ownership contract
(`updateThreadFile` + `preserveRing3LastSession` in watchtower-lib.mjs; the
model's `is_new` is advisory — disk existence wins; Ring 1 never clobbers
Ring 3's authored summary; contract in `watchtower-contracts.md`). Runtime at
`~/.claude-cabinet/watchtower/`, refreshed on reinstall by
`refreshWatchtowerRuntime()` in `lib/watchtower-setup.js` (act:2da80102,
content-aware, refresh-only — see the lib/CLAUDE.md entry). All ring
extractions (Ring 2 slow + Ring 3 + Ring 4's `doc-drift`) route through the
inbox (watchtower observes, user triages and routes). `watchtower-status` CLI
available at `~/.local/bin/`.

## mux (opt-in, off by default)

Multi-project terminal manager. Desks (tmux sessions), auto-worktrees with
shared project identity (copied `.claude/`, identity symlink, config —
workaround for Claude Code #34437; creation is fail-loud — never a silent
fallback to the main checkout, and `mux resume` reuses its existing
worktree), self-healing worktree health checks (4 checks: path isolation,
node_modules, identity slug, .claude/ freshness), `·wt` tab indicator with
health-aware coloring (green=healthy, red=issues), double-click popup for
health details, auto-cleanup on window close (dirty-detection via the single
shared fail-DIRTY `worktree-dirty-check.sh`; carve-out-aware `mux worktree
refresh` preserves uncommitted edits to ANY tracked `.claude/` file — tracked
= authored, only gitignored infra is disposable, classification
single-sourced in `worktree-session-health.sh` and guarded by the
`claude-carveout` test suite), worktree lane lifecycle (act:dc80380c:
per-project `.mux-worktree-provision` env-file provisioning at spawn,
`mux worktree preflight` pre-spawn source check, docker container reap
on every removal path — all single-sourced in
`worktree-session-health.sh`), live per-worktree excludes + skip-worktree
identity-churn suppression so a fully-merged worktree never reads
"MERGE OR LOSE" over mux/runtime residue (act:c008862c), trail logging,
sticky notes, global DX
captures, portal color-switching, durable tmux bindings (`mux.tmux.conf`
sourced via `~/.tmux.conf`), clipboard copy with hard-wrap removal
(`unwrap-copy.py`), screenshot-to-clipboard launchd watcher
(dual-representation: PNG data + file reference), and an MCP server for
Claude. Installs to user-level paths (`~/.local/bin/`, `~/.config/mux/`,
`~/.local/share/mux/`).

## engagement-server (opt-in, off by default)

Central multi-engagement API server deployed once to Railway/Fly. Raw
node:http + better-sqlite3, multi-tenant with token-scoped isolation,
management CLI, startup migrations, full security hardening. `requires:
['engagement', 'work-tracking']`. Runtime at
`~/.claude-cabinet/engagement-server/`.

## bash-compress (opt-in, off by default)

PostToolUse hook that compresses noisy Bash stdout (git status walls,
npm/yarn install output) to reclaim context in long sessions. stderr and
error/warning lines pass through verbatim; every rewrite carries a visible
`[compressed]` marker; fail-open on any error. `requires: ['hooks']`. Wired
via `mergeBashCompressHooks()`; template `templates/hooks/bash-output-compress.sh`.

## output-register (default-installed)

The Cabinet Register (act:9a0af01a): makes plain-English, decision-level
communication the shipped DEFAULT instead of guidance that decays. Two halves,
BOTH unconditional — dropping either reintroduces the decay the module exists
to fix.

**(a) The style.** `templates/output-styles/cabinet-register.md` →
`~/.claude/output-styles/` (user-level — where Claude Code reads custom styles
from), seeded by `lib/output-register.js` via the `output-register-setup`
postInstall handler. The register lands in the SYSTEM PROMPT: re-sent in full
every request, never decays, unaffected by compaction. That is the strongest
placement available — but it is still prompt context, NOT a move up the
compliance stack (`enforcement-pipeline.md`), and the module does not claim to
be. Every clause traces to an existing operator source (global `CLAUDE.md`,
`.claude/rules/plain-english-decisions.md`, `skill-output-conventions.md` §9,
`act:f87cef4f`, and named memories); the trailing traceability block is keyed
by exact section heading and audited BOTH directions by
`test/output-register/`.

**(b) The per-turn hook.** `templates/hooks/register-reinforce.sh`, a
`UserPromptSubmit` hook emitting ONE line pointing at the register, registered
by `mergeRegisterHooks()` (module-gated, exactly as `mergeBashCompressHooks()`
is). **Do not "simplify" this away as redundant with (a).** The docs claim "all
output styles trigger reminders … during the conversation" — **that is FALSE
for CUSTOM styles, and the docs are wrong.** Verified by controlled experiment
(two isolated temp projects, headless `claude -p`): built-in `Explanatory` gets
the reminder, a custom style gets NONE. The binary agrees — the reminder
generator indexes `Lle`, a BUILT-INS-ONLY registry
(`default`/`Proactive`/`Explanatory`/`Learning`), and `turn-reminder` does not
exist as frontmatter, so a custom style cannot supply one. Anthropic shipping a
per-turn reminder for its own styles IS the platform's concession that
placement alone doesn't hold a register; custom styles just don't get it, so we
supply it. A POINTER, not caveman's full restatement: our rules are already in
the system prompt every request, so the line's job is attention, not memory.
Silent unless `outputStyle` actually names our style; fail-open, always exit 0
(a `UserPromptSubmit` hook that errors is a spurious failure every turn).

**The v2.0.37 floor — the guard that matters, and why a file test is useless.**
`keep-coding-instructions` was ADDED in Claude Code v2.0.37 (v1.0.81 released
output styles; v2.0.30 deprecated them; v2.0.32 un-deprecated on community
feedback; v2.0.37 added the field; v2.1.94 added it for plugin styles). Below
v2.0.37 the field is **silently ignored**, its `false` default applies, and
Claude Code's software-engineering instructions are **STRIPPED from the system
prompt with no error**. A test that the field is present IN THE FILE passes on
every version ever shipped and detects this **never** — it guards the author
forgetting the field, not the platform refusing to honor it. Only
`claude --version` can, hence `versionGate()`, which REFUSES below the floor
and fails OPEN when the binary is absent or unparseable (absence is not an old
version). For the same reason every check also runs against the **INSTALLED**
copy: `templates/` is the one file that cannot cause the failure. CC's
coercion is strict `=== true`, so YAML-truthy `yes`/`on`/`1` read as undefined
and strip the instructions — `coerceKeepCoding()` mirrors it exactly.

**Identity.** Claude Code keys a custom style by its **frontmatter `name`,
verbatim** (the filename is only a fallback when `name` is absent) and selects
by exact lookup. So `REGISTER_STYLE_NAME` in `lib/output-register.js`, the
`STYLE_NAME` in the hook, and the style's frontmatter are ONE identity in three
spellings — any drift makes the register silently inert while every file looks
right. A test asserts all three agree.

**Ownership: seed-if-absent + validate-loudly.** A prose register is the most
edit-inviting artifact CC ships. `mux-setup` and `watchtower-setup` are
OPPOSITE engines and neither is right here (mux compares a cached manifest hash
so a hand edit is never re-examined — the footgun persists forever; watchtower
compares on-disk bytes and reconverges — hand edits reverted every reinstall).
This engine takes the third path CC already built: `isProjectOwnedSeed()`
semantics (`lib/copy.js`, act:2001bf54) — seed if absent, NEVER overwrite, omit
from the project manifest — then closes the gap that opens by validating the
installed copy on every install and FAILING LOUD rather than silently
rewriting it. The `/validate` `output-register` check is the standing detector
(warning, not a gate: user-level machine state, not repo structure).

**The knob is the platform's own.** `outputStyle`, no new config invented.
`.ccrc.json` is deliberately unused — Claude Code never reads it at runtime, so
a level stored there could not reach the system prompt. Precedence (verified in
the binary, whose own comment reads "Ordered low-to-high priority") is
user < project < local < CLI < managed, which gives the per-project override
for free. The installer seeds `outputStyle` into the PROJECT's
`.claude/settings.json` (a per-project module changing the project, not the
portfolio) — seed-if-absent, and an incumbent style at ANY layer is reported
and left alone, never clobbered. No dial: one style (operator ruling
2026-07-14); heads-down is a SITUATION the register handles via its contextual
valve, not a setting to flip.

**`--register` ENUMERATES; it cannot adjudicate.** It is blind to plugin
`force-for-plugin` (which overrides `outputStyle` entirely), CLI `--settings`,
and managed policy. So it prints what each readable settings file says, names
the layers it could not consult, and points at `/status` and `/context` (the
system prompt — the only ground truth that the style landed). Its contract is
three-valued `{level, ours, source}`: a foreign style reports its literal name
with `ours:false`, NEVER collapsing to `off` (two-valued would let the
never-clobber guard destroy an `Explanatory`), and an unreadable layer above
the winner reports `unknown` rather than a confident `off`.

**Not reaching subagents** (verified): "Subagents receive only this system
prompt plus basic environment details… not the full Claude Code system prompt"
— cabinet members' structured findings are unaffected. A verified containment,
not an intention. Full design + dismissed alternatives:
`.claude/plans/clear-and-concise-register.md`.

## audit

The **audit** module includes a deliberative workflow (`deliberative-audit.js`)
that runs audits in two stages: Stage-1 members investigate, Stage-2 critics
annotate findings (challenge/support/context/correction). Optional Stage-3
rebuttal lets challenged members respond. Falls back to prompt-driven parallel
spawning when the Workflow tool is absent. Triage UI renders annotations
inline with color-coded badges and status strips.

`merge-findings.js` UNIONS the two artifacts a run dir holds (act:8e1fa16f).
They are complementary and merging either alone loses real work: the
per-member `<member>.json` files are written by each Stage-1 agent the moment
it finishes — guaranteed to survive orchestrator death, but written BEFORE any
critic ran, so they carry no annotations and no status (they can also hold
findings the workflow itself dropped on a parse failure); `deliberation-report.json`
is the workflow's own ranked+annotated set, holding the entire Stage-2/3
outcome plus any finding whose per-member Write failed. So per-member files are
the base, the report overlays only its verdict fields (`annotations`, `status`,
`rebuttal`, `triageParked` — never member-authored prose or severity), and a
finding present on only one side is kept and named in the output. Merging the
base alone was the original defect: a production-readiness audit lost 85
annotations and 16 challenges and reported success, and the merge's
annotation-handling branch was unreachable through the documented flow, so the
code read as though deliberation were handled. Fail-loud now: an unreadable or
findings-less report ABORTS (a clean-looking summary with the deliberation
missing is the exact shape being fixed), a report carrying zero annotations
WARNS, and a post-condition refuses to write a summary carrying fewer annotated
findings than the report did — an assertion the union makes unfireable by
construction, kept because the defect was a silent loss reported as success.
12 tests in `test/deliberative-audit/deliberation-merge.test.js`.

## memory (v0.27.2+)

Provides a curated write/validate layer over Claude Code's built-in file
memory: `/cc-remember` writes indexed memories, `/memory` browses them,
`validate-memory.mjs` guards MEMORY.md integrity, and `memory-index-guard.sh`
(PostToolUse hook) flags writes the just-written file can't be reached from.
The index is a bounded **working set** (act:02d15a70, building on the
act:a71b4cea cold-tier work): MEMORY.md holds a pinned tier (load-bearing
constraints/identity, decay-exempt) + a recency block + **region pointers** (a
backtick-glob standing in for a whole class of files), and the optional
`MEMORY-archive.md` cold tier stays off the session-start budget (≤200 lines /
≤25KB hot; 50KB cold cap; warns if unlinked). `validate-memory.mjs` check #2
is **reachability, not enumeration** — every on-disk memory must be reachable
from MEMORY.md (a direct index line OR a covering region pointer; "present on
disk" is not reachable), it fails a stale region pointer (a glob matching
nothing on disk), and it samples working-set pressure to
`.memory-pressure.jsonl` (≥90% of budget, once/day) — the signal that gates
the Stage-2 lifecycle verbs (decay/consolidate/supersede). The model is
specified in `templates/cabinet/memory-lifecycle-contract.md`. This replaced
the retired omega-memory engine (Python venv) from the v0.27 wind-down. The
CLI still ships one-time migration tooling (`--migrate-memory`) for projects
upgrading off omega — see `MIGRATION-0.27.md`.

## planning (v0.31.0+)

Ships `/generate-plan-groups` and `/execute-group`. `/generate-plan-groups`
reads `grp:`-tagged plans from pib-db and groups them for parallel execution
(generator only — no execution). `/execute-group` runs one group as a 3-stage
composable pipeline with operator checkpoints between stages: (1) interactive
CP1 at the skill level — cabinet members pre-review, operator decides
proceed/drop/override; (2) `execute-group-implement.js` workflow — parallel
worktree implementation + sequential merge, purely mechanical; (3)
`execute-group-complete.js` workflow — advisory CP3 (never halts/reverts) +
integration check + Completion Report. The only hard automatic gate is
merge-delta `/validate`: Stage 2 captures a baseline on main before the first
merge, and only NEW failures vs that baseline gate anything (revert at merge,
gate at completion) — inherited main debt is reported under
`pre_existing_debt`, never gated. CP3 is advisory. The Completion Report is
required by the completion hook for grp:-tagged plans (the gate resolves the
MAIN checkout for breadcrumbs/report, so worktree invocations land artifacts
durably). `checkpoint-protocol.md` documents three CP modes (Interactive,
Advisory, Full) and is the shared contract between `/execute` and
`/execute-group`. `/execute` is worktree-aware (act:1067adee): its Step 0
reads the worktree-invocation contract and syncs main *into* the worktree
(the merge OUT happens once, at close-out); breadcrumb writes resolve to
`$MAIN/.claude/verification/`, closing the lost-on-cleanup gap
(act:d4767af6); and its Step 8 worktree close-out is MERGE-FIRST
(act:3d1ac2b7): re-sync + re-gate, merge + push via `/qa-handoff`'s
merge-now recipe (no prompt — merge-now is the default; every failure HALTs
loudly, the action stays open), then file a `merged` qa-handoff that
dispatches unconditionally. Done is marked only after merge + filing
succeed. `merge-pending` is reserved for an explicitly gated merge
(`evidence.merge_gate` required). Without watchtower the close-out still
merges, then degrades loudly (never silently) — the no-handoff note names
what to QA manually. On the main checkout all of this no-ops.
