create-claude-cabinet 0.46.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +2 -6
  2. package/lib/cli.js +348 -91
  3. package/lib/copy.js +108 -6
  4. package/lib/db-setup.js +122 -17
  5. package/lib/metadata.js +51 -2
  6. package/lib/settings-merge.js +52 -10
  7. package/lib/watchtower-setup.js +22 -2
  8. package/package.json +1 -1
  9. package/templates/cabinet/advisories-state-schema.md +1 -1
  10. package/templates/cabinet/checklist-stats-schema.md +15 -1
  11. package/templates/cabinet/memory-lifecycle-contract.md +135 -0
  12. package/templates/cabinet/pib-db-access.md +13 -0
  13. package/templates/cabinet/watchtower-contracts.md +414 -0
  14. package/templates/cabinet/worktree-invocation-contract.md +87 -0
  15. package/templates/engagement/OVERVIEW.md +12 -0
  16. package/templates/engagement/__tests__/engagement.test.mjs +177 -2
  17. package/templates/engagement/__tests__/invoice-doc.test.mjs +195 -0
  18. package/templates/engagement/engagement-preview.mjs +100 -0
  19. package/templates/engagement/engagement-schema.md +240 -0
  20. package/templates/engagement/engagement.mjs +391 -11
  21. package/templates/engagement/pib-db-patches/pib-db-mcp-server.mjs +5 -44
  22. package/templates/engagement/pib-db-patches/pib-db.mjs +5 -2
  23. package/templates/hooks/action-completion-gate.sh +9 -3
  24. package/templates/hooks/memory-index-guard.sh +17 -11
  25. package/templates/hooks/watchtower-session-start.sh +24 -2
  26. package/templates/mcp/pib-db.json +1 -4
  27. package/templates/mux/bin/mux +8 -1
  28. package/templates/mux/config/help.txt +1 -0
  29. package/templates/rules/acknowledge-when-corrected.md +33 -0
  30. package/templates/rules/maintainability.md +11 -0
  31. package/templates/rules/memory-capture.md +26 -4
  32. package/templates/rules/plan-before-bulk-or-irreversible-actions.md +48 -0
  33. package/templates/rules/verify-before-asserting.md +79 -0
  34. package/templates/scripts/__tests__/ahead-check-origin.test.mjs +355 -0
  35. package/templates/scripts/__tests__/batch-disposition.test.mjs +98 -0
  36. package/templates/scripts/__tests__/claude-churn-authored.test.mjs +90 -0
  37. package/templates/scripts/__tests__/cross-ring-reader.test.mjs +505 -0
  38. package/templates/scripts/__tests__/dx-captures-briefing.test.mjs +169 -0
  39. package/templates/scripts/__tests__/hook-runner.test.mjs +247 -0
  40. package/templates/scripts/__tests__/narrative-corpus.test.mjs +247 -0
  41. package/templates/scripts/__tests__/phase-shim.test.mjs +112 -0
  42. package/templates/scripts/__tests__/qa-handoff-merge-state.test.mjs +162 -0
  43. package/templates/scripts/__tests__/resolve-cli.test.mjs +314 -0
  44. package/templates/scripts/__tests__/ring-state-ownership.test.mjs +120 -0
  45. package/templates/scripts/__tests__/ring1-content-detector.test.mjs +168 -0
  46. package/templates/scripts/__tests__/ring1-flagged-actions.test.mjs +135 -0
  47. package/templates/scripts/__tests__/ring1-script-drift.test.mjs +126 -0
  48. package/templates/scripts/__tests__/ring2-queue-reader.test.mjs +80 -0
  49. package/templates/scripts/__tests__/ring2-recall-canary.test.mjs +150 -0
  50. package/templates/scripts/__tests__/ring2-roster-review.test.mjs +311 -0
  51. package/templates/scripts/__tests__/ring3-chunk-merge.test.mjs +122 -0
  52. package/templates/scripts/__tests__/ring3-close-lenses.test.mjs +359 -0
  53. package/templates/scripts/__tests__/ring3-dedup.test.mjs +92 -12
  54. package/templates/scripts/__tests__/ring3-memory-titles.test.mjs +187 -0
  55. package/templates/scripts/__tests__/ring3-novelty-rescue.test.mjs +148 -0
  56. package/templates/scripts/__tests__/ring3-recent-slice.test.mjs +101 -0
  57. package/templates/scripts/__tests__/ring4-reconcile.test.mjs +393 -0
  58. package/templates/scripts/__tests__/shared-thread-reader.test.mjs +187 -0
  59. package/templates/scripts/__tests__/suppression-ledger.test.mjs +148 -0
  60. package/templates/scripts/__tests__/verify-backfill.test.mjs +103 -0
  61. package/templates/scripts/__tests__/verify-coverage.test.mjs +80 -0
  62. package/templates/scripts/__tests__/watchtower-snapshot.test.mjs +261 -0
  63. package/templates/scripts/__tests__/watchtower-sync.test.mjs +311 -0
  64. package/templates/scripts/load-triage-history.js +5 -2
  65. package/templates/scripts/pib-db-mcp-server.mjs +5 -44
  66. package/templates/scripts/pib-db-path.mjs +61 -0
  67. package/templates/scripts/pib-db.mjs +5 -2
  68. package/templates/scripts/validate-memory.mjs +214 -16
  69. package/templates/scripts/watchtower-advisories.mjs +7 -3
  70. package/templates/scripts/watchtower-build-context.mjs +192 -8
  71. package/templates/scripts/watchtower-cross-ring-reader.mjs +700 -0
  72. package/templates/scripts/watchtower-hook-runner.mjs +422 -0
  73. package/templates/scripts/watchtower-lib.mjs +273 -4
  74. package/templates/scripts/watchtower-narrative-corpus.mjs +385 -0
  75. package/templates/scripts/watchtower-phase-shim.mjs +171 -0
  76. package/templates/scripts/watchtower-queue.mjs +341 -3
  77. package/templates/scripts/watchtower-ring1.mjs +405 -45
  78. package/templates/scripts/watchtower-ring2.mjs +710 -68
  79. package/templates/scripts/watchtower-ring3-close.mjs +988 -147
  80. package/templates/scripts/watchtower-ring4-runner.sh +85 -0
  81. package/templates/scripts/watchtower-ring4.mjs +753 -0
  82. package/templates/scripts/watchtower-routines.mjs +1 -1
  83. package/templates/scripts/watchtower-snapshot.mjs +452 -0
  84. package/templates/scripts/watchtower-sync.mjs +393 -0
  85. package/templates/skills/briefing/SKILL.md +281 -41
  86. package/templates/skills/cabinet-process-therapist/SKILL.md +28 -0
  87. package/templates/skills/cabinet-security/SKILL.md +11 -0
  88. package/templates/skills/catch-up/SKILL.md +113 -0
  89. package/templates/skills/cc-publish/SKILL.md +53 -16
  90. package/templates/skills/cc-remember/SKILL.md +45 -0
  91. package/templates/skills/close/SKILL.md +107 -0
  92. package/templates/skills/collab-client/SKILL.md +22 -5
  93. package/templates/skills/collab-consultant/SKILL.md +110 -2
  94. package/templates/skills/debrief/SKILL.md +21 -4
  95. package/templates/skills/debrief-classic/SKILL.md +696 -0
  96. package/templates/skills/debrief-classic/calibration.md +44 -0
  97. package/templates/skills/debrief-classic/phases/audit-pattern-capture.md +78 -0
  98. package/templates/skills/debrief-classic/phases/auto-maintenance.md +48 -0
  99. package/templates/skills/debrief-classic/phases/checklist-feedback.md +123 -0
  100. package/templates/skills/debrief-classic/phases/close-work.md +163 -0
  101. package/templates/skills/debrief-classic/phases/health-checks.md +54 -0
  102. package/templates/skills/debrief-classic/phases/inventory.md +40 -0
  103. package/templates/skills/debrief-classic/phases/loose-ends.md +52 -0
  104. package/templates/skills/debrief-classic/phases/methodology-capture.md +223 -0
  105. package/templates/skills/debrief-classic/phases/qa-handoff-sweep.md +78 -0
  106. package/templates/skills/debrief-classic/phases/record-lessons.md +177 -0
  107. package/templates/skills/debrief-classic/phases/report.md +59 -0
  108. package/templates/skills/debrief-classic/phases/update-state.md +48 -0
  109. package/templates/skills/debrief-classic/phases/upstream-feedback.md +185 -0
  110. package/templates/skills/debrief-classic/phases/verify-coverage.md +101 -0
  111. package/templates/skills/execute/SKILL.md +71 -6
  112. package/templates/skills/execute/phases/commit-and-deploy.md +8 -0
  113. package/templates/skills/execute-group/SKILL.md +23 -26
  114. package/templates/skills/generate-plan-groups/SKILL.md +20 -0
  115. package/templates/skills/inbox/SKILL.md +84 -28
  116. package/templates/skills/memory/SKILL.md +22 -6
  117. package/templates/skills/orient/SKILL.md +71 -5
  118. package/templates/skills/orient-classic/SKILL.md +770 -0
  119. package/templates/skills/orient-classic/phases/auto-maintenance.md +52 -0
  120. package/templates/skills/orient-classic/phases/briefing.md +53 -0
  121. package/templates/skills/orient-classic/phases/cabinet.md +46 -0
  122. package/templates/skills/orient-classic/phases/checklist-status.md +54 -0
  123. package/templates/skills/orient-classic/phases/context.md +88 -0
  124. package/templates/skills/orient-classic/phases/data-sync.md +35 -0
  125. package/templates/skills/orient-classic/phases/deferred-check.md +55 -0
  126. package/templates/skills/orient-classic/phases/dx-captures.md +53 -0
  127. package/templates/skills/orient-classic/phases/health-checks.md +50 -0
  128. package/templates/skills/orient-classic/phases/verify-backfill.md +109 -0
  129. package/templates/skills/orient-classic/phases/work-scan.md +69 -0
  130. package/templates/skills/qa-drain/SKILL.md +38 -0
  131. package/templates/skills/qa-handoff/SKILL.md +287 -46
  132. package/templates/skills/setup-accounts/SKILL.md +38 -16
  133. package/templates/skills/threads/SKILL.md +4 -0
  134. package/templates/skills/validate/phases/validators.md +41 -0
  135. package/templates/skills/watchtower/SKILL.md +160 -8
  136. package/templates/verify-runtime/CONVENTIONS.md +9 -0
  137. package/templates/verify-runtime/README.md +37 -0
  138. package/templates/watchtower/config.json.template +8 -2
  139. package/templates/workflows/execute-group-complete.js +10 -1
@@ -0,0 +1,135 @@
1
+ # Memory Lifecycle Contract
2
+
3
+ The shared contract for how built-in memory stays bounded as it grows.
4
+ Every component that reads or writes the memory dir follows this:
5
+ `validate-memory.mjs`, `write-memory-file.mjs`, the `/cc-remember` and
6
+ `/memory` skills, the `memory-index-guard` hook, and (Stage 2) the
7
+ lifecycle verbs. Full design + cabinet critique: see
8
+ `.claude/plans/cross-store-memory-redesign.md` (act:02d15a70 = Stage 1,
9
+ this contract; act:45fb9169 = Stage 2, gated).
10
+
11
+ ## The core principle
12
+
13
+ `MEMORY.md` is loaded **in full at every session start** — it competes
14
+ for the model's context window, so it has a hard budget (≤200 lines /
15
+ ≤25KB). The disease this contract cures: the old rule required the
16
+ eagerly-loaded index to enumerate EVERY memory file (one line each), so
17
+ its size tracked total memory count and it overflowed forever — the
18
+ prune→overflow→prune treadmill.
19
+
20
+ The fix decouples the eager index from total volume: `MEMORY.md` is a
21
+ bounded **working set**, not a complete catalogue. Total memory can grow
22
+ without bound (disk and search both scale for decades); only what loads
23
+ eagerly is bounded. This is the same shape watchtower threads already
24
+ use — surface the current cursor, leave the history on disk.
25
+
26
+ ## The three blocks of MEMORY.md
27
+
28
+ A well-formed `MEMORY.md` working set has three kinds of content:
29
+
30
+ 1. **Pinned tier (decay-exempt).** Load-bearing knowledge a fresh
31
+ session needs surfaced *unbidden* — hard constraints, identity, hard
32
+ preferences, active-project pointers. These are valuable precisely
33
+ because they are stable and often OLD; they must never decay out on
34
+ recency. The defining test: *would a session that has never seen this
35
+ fail without it, and would it not know to go looking?* (e.g. "CC
36
+ hooks read tool input from stdin, not `$CLAUDE_TOOL_INPUT`".) Mark a
37
+ pinned entry with `pinned: true` in its file frontmatter (the Stage-1
38
+ default authority; revisit in Stage 2).
39
+
40
+ 2. **Recency block.** The recent window of curated entries, newest
41
+ first, filling the remaining budget to roughly 80% of the cap.
42
+ Recency is a fine proxy for "likely relevant next session" for
43
+ episodic material — and a BAD proxy for load-bearing constraints,
44
+ which is exactly why they go in the pinned tier instead.
45
+
46
+ 3. **Region pointers.** The cue-based-retrieval tier (see below) that
47
+ lets everything outside the working set stay reachable without a line
48
+ each.
49
+
50
+ ## Reachability replaces enumeration
51
+
52
+ The validator invariant is no longer "every file has its own index
53
+ line." It is: **every on-disk memory file is reachable from
54
+ `MEMORY.md`** — by one of:
55
+
56
+ - a direct working-set or `MEMORY-archive.md` index line, OR
57
+ - a **region pointer** whose glob matches the file.
58
+
59
+ A file that is none of these is an orphan and fails validation.
60
+ "Present on disk" alone is **not** reachable — that test would be
61
+ vacuous (every file is grep-able), which would silently delete the
62
+ guarantee rather than relax it. The point of relaxing enumeration is to
63
+ allow *bounded* indexing (a region covers N files with one line), not to
64
+ allow invisible files.
65
+
66
+ ## Region pointers
67
+
68
+ A region pointer declares that a whole class of files is reachable
69
+ without enumerating them. Format — the **backtick-quoted glob is the
70
+ only part the validator reads**; the rest is free-text guidance:
71
+
72
+ ```
73
+ - region `lesson_*.md` → older lessons; grep the memory dir
74
+ - region `session_summary_*.md` → past session records; grep by date
75
+ ```
76
+
77
+ Rules:
78
+
79
+ - **Globs use `*` and `?` only**, matched against the bare filename.
80
+ - **Region pointers are DERIVED, never authoritative.** They are
81
+ regenerated from what's actually on disk (by hand in Stage 1, by the
82
+ consolidation pass in Stage 2). A pointer whose glob matches **zero**
83
+ files on disk is a stale, confidently-wrong retrieval cue — *worse
84
+ than no map* — and **fails validation**. Fix the glob or remove it;
85
+ never hand-edit a pointer to claim a region that isn't there.
86
+ - **Region pointers are for recoverable history you'd think to search**
87
+ (old session narratives, cooled lesson archives). They are the WRONG
88
+ home for latent constraints — those stay in the pinned tier, eagerly
89
+ present, because their value is that you retrieve them *without*
90
+ knowing to look.
91
+
92
+ ## Spill-rate instrumentation (the Stage-2 gate)
93
+
94
+ `validate-memory.mjs` appends a working-set pressure sample to
95
+ `<memoryDir>/.memory-pressure.jsonl` (a dotfile — invisible to the
96
+ orphan scan) at most once per day, only when utilization is ≥90% of the
97
+ budget. This measures how often the working set actually runs near its
98
+ limit, so Stage 2 (the lifecycle verbs + a background forcing function)
99
+ is built only if the data shows recurring pressure on authored files —
100
+ not on a felt "it keeps overflowing" metaphor. Stage 1 alone stops the
101
+ acute treadmill; the sampler decides whether Stage 2 is warranted.
102
+
103
+ ## Stage 2 (designed, gated — NOT yet built)
104
+
105
+ The lifecycle verbs and their forcing function are specified in the plan
106
+ but gated on the spill-rate data. When built they will carry these
107
+ already-decided constraints (recorded here so Stage 1 doesn't paint them
108
+ into a corner):
109
+
110
+ - **decay** — drop an entry from the working set; the file stays
111
+ reachable (via a region pointer or the archive). Reversible.
112
+ - **consolidate** — merge a cluster into one gist that ALWAYS keeps a
113
+ lossless `consolidated_from:` provenance pointer (lossy = dropped from
114
+ the working set, never erased from disk). A cluster containing a
115
+ pinned/load-bearing entry files to the inbox; it never auto-applies,
116
+ because the *gist* becomes the retrieved truth and a wrong gist erases
117
+ a load-bearing detail.
118
+ - **supersede** — mark an entry false/obsolete so retrieval stops
119
+ surfacing it as live truth (kept on disk for audit, with a reverse
120
+ breadcrumb in its successor). Deferred until the frontmatter-parsing
121
+ question is resolved (line-scan vs. a deliberately-added dependency —
122
+ the repo currently refuses a YAML parser; see
123
+ `lesson_parsefrontmatter_flow_sequences`). supersede is a reference
124
+ graph: dangling targets, cycles, and re-pointing working-set/region
125
+ references to the superseded slug must all be validated.
126
+ - **Cross-store fold** — for session-summaries carrying design
127
+ reasoning, the durable target is a committed `.claude/methodology/`
128
+ gist, NEVER the thread cursor (which holds understanding, not
129
+ narration) and NEVER a delete-in-the-same-pass (the memory dir and
130
+ threads are both local-only/ungit'd; append-verify-then-mark
131
+ `folded_into:`, reclaim in a separate idempotent sweep).
132
+ - **Forcing function** — memory hygiene (decay/consolidate) extends
133
+ Ring 2-slow's existing `runMemoryHygiene`; only drift-supersede is the
134
+ Ring 4 memory pass. No new ring for hygiene. First passes run
135
+ dry-run-with-report.
@@ -23,6 +23,19 @@ Check: are pib_* MCP tools available?
23
23
  NO → use node scripts/pib-db.mjs list-projects, etc.
24
24
  ```
25
25
 
26
+ ### MCP invocation specifics (read before first use, not after a failure)
27
+
28
+ Two details that otherwise cost a failed call + a `ToolSearch` round-trip
29
+ every session:
30
+
31
+ - **Full tool names are prefixed `mcp__pib-db__`** (e.g.
32
+ `mcp__pib-db__pib_query`). When loading deferred tools via `ToolSearch`,
33
+ query `select:mcp__pib-db__pib_query,mcp__pib-db__pib_list_actions` —
34
+ the bare `pib_*` names will not match.
35
+ - **`pib_query`'s parameter is `sql`, not `query`.** Calling it with a
36
+ `query` argument fails (and can fail quietly). The CLI form is
37
+ `query "SQL"`; the MCP form is `{ "sql": "SELECT ..." }`.
38
+
26
39
  ## Available Operations
27
40
 
28
41
  | MCP Tool | CLI Equivalent | Description |
@@ -85,6 +85,19 @@ within one cron tick (~5 minutes). Rebuild writes must go through
85
85
  verify with retry), never a raw read→merge→write — the raw form has a
86
86
  read-then-write race that drops a Last Session Ring 3 authors mid-merge.
87
87
 
88
+ The inline `## Last Session` block holds the COMPLETE session bullet set
89
+ — byte-identical to the bullets in the per-session record at
90
+ `state/projects/<slug>/sessions/<date>-<session-id>.md`. It is NOT a
91
+ pointer, a digest, or a truncated subset. Both bodies derive from the
92
+ ONE model bullet string via `buildLastSessionBlock()`, and Ring 3 writes
93
+ the inline block UNCONDITIONALLY — creating `<slug>.md` if it does not
94
+ yet exist — so the inline section is never silently skipped (the prior
95
+ `existsSync` gate dropped it on fresh projects, leaving readers to render
96
+ Ring 1's truncated one-line fallback while the per-session file held the
97
+ full set; act:ac119994). `upsertLastSessionSection()` performs the
98
+ in-place splice/append with the same line-anchored header search
99
+ `preserveRing3LastSession()` reads with — one splice convention, not two.
100
+
88
101
  ### Thread File Durability (disk wins over model)
89
102
 
90
103
  `state/threads/<slug>.json` carries the sibling rule: **disk wins over
@@ -199,6 +212,97 @@ top of the owning project's chunk — the re-surfacing the close pass
199
212
  promises. A `session-close`/`briefing` mandate without the matching
200
213
  directive key is a data error: consumers skip the member and log it.
201
214
 
215
+ ## Session-Close Extraction Lenses — Phase 2n/2o/2p (act:4ff2cfb3)
216
+
217
+ Ring 3's session close runs three extraction lenses beyond the knowledge
218
+ pass (2d) and advisor pass (2m), each modeled on `advisorPass` with an
219
+ injectable `callFn` (testable; never folded into the hardened Phase 2d —
220
+ see `lesson_ring3_lens_injectable_phase_pattern`):
221
+
222
+ - **Phase 2n — Raised-but-unhandled.** Loose ends the session RAISED
223
+ (promises, side-issues, open questions) but neither completed nor
224
+ filed → an inbox item under the `raised-unhandled` category. Capped at
225
+ `RAISED_UNHANDLED_MAX = 5` per session.
226
+ - **Phase 2o — Skill-candidate.** A manual procedure repeated by hand
227
+ that a skill could absorb → a `skill-candidate` inbox item ("make a
228
+ skill?"). Capped at `SKILL_CANDIDATE_MAX = 3` per session.
229
+ - **Phase 2p — Checklist-catch.** A surfaced change-impact check that
230
+ caught a real bug → recorded to `checklist-stats.json` (the
231
+ catch-recording side; consumed by `/debrief`'s checklist-feedback and
232
+ the audit `checklist-pruning` phase). Not an inbox item — a stats write.
233
+
234
+ The two new inbox categories (`raised-unhandled`, `skill-candidate`)
235
+ follow the standard item schema and the same dedup-vs-corpora discipline
236
+ as the other Ring 3 extractions.
237
+
238
+ ## Suppression Ledger (Ring 3 dedup observability)
239
+
240
+ The recall-fix program (audit act:7d6a4b3b) makes Ring 3's
241
+ knowledge-capture dedup **auditable**. Every time `isDuplicate`
242
+ suppresses an extraction, the suppression site appends one structured
243
+ record to a shared sidecar so over-suppression is visible instead of
244
+ vanishing into a write-only cron.
245
+
246
+ **File:** `state/suppression-ledger.jsonl` (under `WATCHTOWER_DIR`).
247
+ Append-only JSON-lines. NOT shipped as a template — generated at
248
+ runtime, like `checklist-stats.json` and `.memory-pressure.jsonl`.
249
+
250
+ **Producer:** `recordSuppression(record, opts)` in `watchtower-lib.mjs`
251
+ (shared home; reachable by every ring). Called from every Ring 3
252
+ suppression site — Phase 2d (extraction), Phase 2m (advisor close
253
+ pass), Phase 2n (raised-unhandled lens), Phase 2o (skill-candidate
254
+ lens). The human `log()` line stays beside it; the ledger is the
255
+ machine-readable twin, never a parse of the prose.
256
+
257
+ **Consumer:** the Ring 2 slow over-suppression canary (M5) — the ONLY
258
+ reader. It windows records by `ts`, computes a per-project
259
+ suppression-rate (suppressed / (suppressed + filed)) against a captured
260
+ post-M1a baseline, samples actual suppressed titles + what they matched
261
+ for human eyeball, tracks net-durably-saved (filed × approval-rate), and
262
+ writes `state/recall-canary.json`. The canary also prunes the ledger to
263
+ a retention window on each run (growth is bounded at canary cadence, not
264
+ on the suppression hot path). It is over-suppression-only and states so
265
+ in its own `note` field. Guards: `total===0 ⇒ no alert`; a minimum
266
+ denominator before a rate can fire; baseline captured on first run (else
267
+ it would alert on its own improvement).
268
+
269
+ **The surfacing path** (no write-only telemetry): the canary writes
270
+ `state/recall-canary.json` (per-project `{suppressed, filed, rate,
271
+ baseline, by_corpus, approval_rate, net_durably_saved, sample, alert}`);
272
+ **Ring 1** reads it and renders an alerting project's entry into that
273
+ project's **Standing Issues** (Ring 1 owns the per-project state file);
274
+ **/briefing**'s State-file-flags reader surfaces it. Three named hops,
275
+ each owning its tier. Flag: `defaults.recall_canary` (opt-out).
276
+
277
+ **Record shape (the contract):**
278
+
279
+ ```json
280
+ {
281
+ "ts": "ISO-8601 timestamp of the suppression",
282
+ "project": "resolved project name (or null)",
283
+ "corpus": "memory | thread-cursor | pending | resolved | dismissed",
284
+ "suppressed_title": "the extraction title that was killed",
285
+ "matched_against": "the corpus entry it collided with",
286
+ "session_id": "the closing session's id (or null)"
287
+ }
288
+ ```
289
+
290
+ **Invariants:**
291
+
292
+ - **Fail-open.** `recordSuppression` never throws. A failed append logs
293
+ one line and continues — a dropped ledger record must NEVER block an
294
+ extraction from filing. The ledger is observability, not the decision.
295
+ - **Additive.** New fields ride alongside; readers default missing
296
+ fields. The `corpus` enum grows when a new dedup corpus lands (M1a
297
+ added `thread-cursor` as a first-class corpus split off from
298
+ `memory`).
299
+ - **Baseline before behavior.** The ledger lands BEFORE any matcher
300
+ change so it records the OLD (pre-M1a) suppression behavior as the
301
+ before-baseline the canary compares against.
302
+ - **Over-suppression only.** The ledger sees what dedup KILLED, not what
303
+ extraction never proposed (M2 under-extraction). The canary states
304
+ this blindness in its own output.
305
+
202
306
  ## Deferred Schemas
203
307
 
204
308
  The following are specified in project notes but NOT formalized until
@@ -283,3 +387,313 @@ They're complementary, not redundant.
283
387
  staleness detection (stale work, memory hygiene). Where's the line
284
388
  between R2's "is this work item stale?" and R4's "is this document
285
389
  still true?" Is R4 an extension of R2 slow, or genuinely distinct?
390
+
391
+ ## Ring 4 — Truth Reconciliation (built, stage 1)
392
+
393
+ **Status: BUILT (act:36dae795). Supersedes the design-phase section
394
+ above** — the open design questions there are now resolved here.
395
+ Implemented in `templates/scripts/watchtower-ring4.mjs` (the ring) +
396
+ `templates/scripts/watchtower-ring4-runner.sh` (PID-locked runner,
397
+ modeled on the Ring 2 runner). Hermetic suite:
398
+ `templates/scripts/__tests__/ring4-reconcile.test.mjs`. Cron/launchd
399
+ registration is a `/watchtower install` follow-up, not part of this
400
+ build.
401
+
402
+ ### What it does
403
+
404
+ Periodically compares documentary CLAIMS against codebase REALITY and
405
+ files drift as `doc-drift` inbox items. Reconciliation is COMPARISON,
406
+ NOT generation — Ring 4 never rewrites a document; it flags drift with
407
+ enough context to fix, and the operator triages it. Stage 1 is PURELY
408
+ MECHANICAL (no Claude API call), which keeps the cron cheap and the
409
+ ring hermetically testable.
410
+
411
+ It owns two ledger duties at orient/debrief retirement
412
+ (`orient-debrief-coverage-ledger.md` §C/§D): documentation staleness
413
+ (CLAUDE.md key-file claims, briefing files, system-status) and the
414
+ dropped embedded-pulse description-accuracy spot checks (counts). The
415
+ standalone `/pulse` stays invocable on demand.
416
+
417
+ ### Resolved design questions
418
+
419
+ - **Scope per run → rotation, not full sweep.** Each run reconciles at
420
+ most `RING4_PROJECT_CAP` (3) projects, least-recently-reconciled
421
+ first, BUT a project with commits since its last reconcile jumps the
422
+ queue. This is the nervous-system principle in mechanical form: R4
423
+ consumes R1's signal (git recency) to prioritize, rather than
424
+ blind-sweeping the portfolio. State in `state/ring4-reconcile.json`
425
+ (`last_run`, `projects_checked`).
426
+ - **Drift threshold → mechanical existence/counts, high precision.**
427
+ Stage 1 flags only (a) a backtick-fenced repo-relative path that is
428
+ missing on disk, gated so the path's TOP-LEVEL directory still
429
+ exists (kills illustrative/foreign-path noise — `webapp/...` in a
430
+ CC memory file is skipped because `webapp/` isn't a repo dir) and
431
+ not gitignored (a gitignored absent path is a generated/local
432
+ artifact — its absence is expected, not drift); and (b) a count
433
+ claim that disagrees with a mechanical file count, but ONLY for
434
+ config-declared count rules (zero-noise default). A "slightly
435
+ outdated line count" is deliberately NOT flagged.
436
+ - **Trigger vs schedule → weekly cadence + git-recency priority.** A
437
+ weekly gate (`RECONCILE_INTERVAL_DAYS`, mirrors Ring 2's roster
438
+ review) plus git-recency-driven project order, rather than coupling
439
+ to R1/R2 internal trigger files.
440
+ - **Relationship to Ring 2 → genuinely distinct ring.** R2 slow asks
441
+ "is this WORK ITEM stale?" (pib-db state); R4 asks "is this DOCUMENT
442
+ still true?" (doc claims vs codebase). Distinct cadence (weekly vs
443
+ 30-min), distinct runner/cron, distinct produced category.
444
+ - **Cross-project → per-project only, stage 1.** A CC template
445
+ claiming something about consumer behavior is a documented future
446
+ extension, not built.
447
+
448
+ ### The document set (current-reality docs only)
449
+
450
+ `collectDocuments()` scans docs consumed AS TRUTH, capped at
451
+ `RING4_DOC_CAP` (40): root `CLAUDE.md`, `system-status.md` (root and
452
+ `.claude/`), `.claude/cabinet/_briefing*.md`, and the project's
453
+ built-in memory entries (`MEMORY.md` + curated `*.md`). It
454
+ deliberately EXCLUDES `.claude/plans/` and `.claude/methodology/`:
455
+ those are HISTORICAL records — a plan for retired work SHOULD
456
+ reference now-deleted files, so path-existence over them is mostly
457
+ noise (the CC dogfood produced ~90 false claims from historical plans;
458
+ filtering to current-reality docs + gitignore-awareness cut it to 2
459
+ real findings). Whether a plan is actually DONE — plan-to-reality
460
+ drift — is a distinct SEMANTIC check, deferred.
461
+
462
+ ### Memory is DRIFT-SUPERSEDE only
463
+
464
+ The memory pass is the missing-path reconciler applied to memory files
465
+ ("is what we wrote still true?" — entries referencing renamed/removed
466
+ files). Paths in a memory file resolve against the PROJECT root, not
467
+ the memory dir. Memory HYGIENE (budget / decay / consolidate) stays
468
+ Ring 2 slow's `runMemoryHygiene` / `surfacePersistentViolations` —
469
+ Ring 4 does NOT fork a second memory-health producer (act:36dae795
470
+ 2026-06-16 scope note; design in `cross-store-memory-redesign.md`).
471
+
472
+ ### Inbox contract
473
+
474
+ Findings file `doc-drift` items (`filed_by: 'ring4'`, urgency `low`,
475
+ three triage options: fix-doc / fix-code / dismiss). Dedup is ONE
476
+ pending item per `evidence.drift_key` per project — `path:<doc>` for
477
+ missing-path, `count:<doc>:<label>` for count-mismatch (mirrors Ring
478
+ 2's `roster_kind` convention: a still-open drift item is a reference,
479
+ not a duplicate). At most `RING4_ITEM_CAP` (8) items filed per run.
480
+ `doc-drift` is NOT a dispatched or gated category — it is a plain
481
+ inbox item the operator triages. The weekly cadence + per-doc dedup
482
+ keep re-nagging bounded.
483
+
484
+ ### Config
485
+
486
+ Feature flag `defaults.truth_reconciliation` (default true) gates the
487
+ whole ring; `ring_frequencies.ring4` (604800 = weekly) carries the
488
+ intended cron interval. Count rules are per-project and opt-in:
489
+ `projects[<name>].reconcile.count_rules = [{ label, doc, pattern,
490
+ glob }]` where `pattern` is a regex source with one capture group
491
+ around the number and `glob` uses the small dependency-free grammar in
492
+ `countGlob` (`dir/*`, `dir/prefix-*/`, `dir/*.ext`, `dir/**/*.ext`).
493
+ Default empty → the count pass no-ops (no false drift).
494
+
495
+ ### Deferred (future stages)
496
+
497
+ Semantic comparison (does this prose paragraph still describe the
498
+ code?), plan-to-reality / plan-status reconciliation, dependency-
499
+ version drift beyond the count-rule mechanism, gitignored-directory
500
+ patterns that don't match a non-existent path, and cross-project
501
+ template-vs-consumer reconciliation.
502
+
503
+ ## Consumer Hook Contract (Plan 9)
504
+
505
+ The formalized extension model for **non-interactive** consumer customs at
506
+ the ring lifecycle seams (act:4a6e907c). Supersedes the deferred-schema
507
+ placeholder `hooks/<ring>-<phase>.d/` above — the real location is
508
+ per-project, not a global runtime dir. Interactive customs are owned by
509
+ routine dispatch (act:c2a55c08, "Routine Dispatch" above), never by hooks.
510
+
511
+ ### Seams and invocation
512
+
513
+ The rings already invoke consumer hooks by running each command string in
514
+ `config.hooks[<seam>]` with the ring's state JSON on stdin. Three seams:
515
+
516
+ | Seam | Ring / scope | Outer timeout |
517
+ |------|--------------|---------------|
518
+ | `ring1-post-collect` | Ring 1, per project, post state-collect | 30s |
519
+ | `ring2-slow-post` | Ring 2 slow tier, portfolio-wide | 60s |
520
+ | `ring3-close-post` | Ring 3, per session close | 120s |
521
+
522
+ `watchtower-hook-runner.mjs --seam <seam>` is registered ONCE per seam (via
523
+ `--register`, called by `/watchtower install`). The rings invoke it like any
524
+ hook command — the rings are NOT modified. The runner then resolves the
525
+ target project(s) and runs that project's hook scripts.
526
+
527
+ ### Registration (drop-a-file)
528
+
529
+ A consumer registers a hook per-project by placing an executable script in
530
+ `<project-root>/.claude/watchtower/hooks/<seam>/`. Presence is registration —
531
+ no config editing. Discovery is name-sorted (numeric-prefix convention:
532
+ `10-foo.sh` before `20-bar.sh`). A file is runnable if it has the executable
533
+ bit (run directly, shebang decides the interpreter) or a known extension
534
+ (`.sh`/`.bash` → bash, `.mjs`/`.js`/`.cjs` → node, `.py` → python3); other
535
+ files (`.json`, `.md`, …) are ignored. Files prefixed `_` or `.` are skipped
536
+ (disable convention).
537
+
538
+ ### Project resolution
539
+
540
+ - Single-project seams (`ring1-post-collect`, `ring3-close-post`): the project
541
+ is taken from the ring's stdin state (`.path`/`.name`, or `.project_path`/
542
+ `.project`).
543
+ - Portfolio seam (`ring2-slow-post`): the slow state carries no project, so the
544
+ runner enumerates `config.projects` and runs each project's seam dir.
545
+
546
+ ### Hook I/O contract
547
+
548
+ A hook script receives:
549
+ - **stdin:** the ring's state JSON, verbatim (see the seam table for shape).
550
+ - **cwd:** the project root.
551
+ - **env:** `WATCHTOWER_SEAM`, `WATCHTOWER_PROJECT`, `WATCHTOWER_PROJECT_PATH`,
552
+ `WATCHTOWER_SESSION_ID` (ring3), `WATCHTOWER_HOOK_DIR`.
553
+
554
+ A hook script returns:
555
+ - **exit 0** = success; non-zero = failure (isolated, recorded, never fatal).
556
+ - **stdout:** optional JSON. `{"additional_checks":[…]}` is the Ring 1
557
+ passthrough — the runner surfaces those at the envelope top level so Ring
558
+ 1's existing `parsed.additional_checks` consumption works. Other JSON is
559
+ captured as structured `output`; non-JSON is captured verbatim (truncated).
560
+ - **stderr:** captured for diagnostics; non-fatal.
561
+
562
+ ### Isolation and budget
563
+
564
+ The runner is bulletproof: any single hook's failure (non-zero exit, timeout,
565
+ spawn error) becomes a typed status (`failed` / `timeout`) and never aborts
566
+ the loop, and the runner always prints a valid envelope and exits 0 — one bad
567
+ consumer hook can never break a ring tick. Because the runner is a single
568
+ command sharing the ring's one timeout envelope, it runs hooks sequentially
569
+ against a deadline (the seam timeout minus headroom) and marks any hook it
570
+ can't reach in time `skipped` (reason `budget-exhausted`) — no silent
571
+ truncation. `WATCHTOWER_HOOK_BUDGET_MS` overrides the per-seam budget.
572
+
573
+ ### Output envelope
574
+
575
+ ```json
576
+ { "schema_version": 1, "seam": "<seam>",
577
+ "targets": [{ "name": "...", "path": "..." }],
578
+ "hooks": [{ "hook": "<path>", "project": "...", "status": "success|failed|timeout|skipped", "output"?: ..., "exit"?: N, "stderr"?: "...", "reason"?: "budget-exhausted" }],
579
+ "additional_checks"?: [ ... ] }
580
+ ```
581
+
582
+ ### Migration shim
583
+
584
+ `watchtower-phase-shim.mjs --phase <path> [--cwd <dir>] [--seam <seam>]` is the
585
+ adapter for an EXISTING repo script (a former orient/debrief phase) that
586
+ expects the project root as cwd rather than reading stdin. Registered as a one
587
+ -line `config.hooks[<seam>]` entry, it runs the target with the same cwd/env/
588
+ stdin normalization and passes JSON stdout through transparently (preserving
589
+ `additional_checks`), propagating the target's exit code. New hooks prefer the
590
+ drop-in directory; the shim is for scripts that already exist.
591
+
592
+ ## Decision Lineage (read-only projection — Plan 12, act:a2efc0ce)
593
+
594
+ The `/briefing lineage` drill-down answers "what past decisions still
595
+ constrain THIS project, and what's reversible?" It is a **projection** over
596
+ existing stores, never a new store, and it **writes nothing** — no state file,
597
+ no inbox item, no memory write. This is the structural invariant: the engine
598
+ holds no persisted copy, so it cannot drift from its sources.
599
+
600
+ `watchtower-narrative-corpus.mjs` is the read-only substrate helper. It covers
601
+ the two watchtower-NATIVE stores so it needs no auto-memory-dir resolver:
602
+
603
+ - `assembleNarrativeCorpus({cwd})` — current project ONLY (resolved
604
+ worktree-aware via `resolveProjectIdentity`; no free `project` arg, so the
605
+ five-namespace keying footgun can't arise). Returns `inbox_decisions`
606
+ (knowledge-extraction items; `resolution_type: 'captured-to-memory'`
607
+ duplicates dropped so a decision that became a memory file isn't
608
+ double-counted; `superseded` status KEPT — it is the supersession view's
609
+ own subject), `thread_cursors` (full `cursor_history` timelines),
610
+ `per_store_counts`, `skipped_unresolved` (phantom-key items repo-wide that
611
+ the resolver couldn't attribute — surfaced, never silently dropped),
612
+ `time_span`. CLI: `--corpus --project-path <cwd>`.
613
+ - `parseDecisionEdges(markdown)` (pure) + `readMemoryDecisions(dir)` — the
614
+ memory/methodology/pib stores are read by the `/briefing` CONSUMER (which
615
+ resolves their paths), but the EDGE RULE lives here, tested: a
616
+ `**Supersedes:**` block is a RECORDED directional edge; a `[[wikilink]]` is
617
+ an undirected see-also and is **never** rendered as a supersession. CLI:
618
+ `--memory <consumer-resolved-dir>`.
619
+
620
+ Provenance is load-bearing because a narrative is a trust surface: the render
621
+ labels *recorded* edges (parsed `Supersedes:` / pib `absorbs act:`) distinctly
622
+ from *inferred* ones (model-read from prose), and never presents an inferred
623
+ link as recorded history. Deliberately a SIBLING of `watchtower-snapshot.mjs`
624
+ (same reader family, same hermetic-test shape), touching zero ring code. Tests:
625
+ `__tests__/narrative-corpus.test.mjs`. Staged: the machine-readable
626
+ supersede-edge discipline (making edges consistent + live) is deferred and
627
+ MERGES INTO the cross-store `supersede` verb
628
+ (`.claude/plans/cross-store-memory-redesign.md`, Ring 2-slow), not a fork.
629
+
630
+ ## Cross-Ring Reader (read-only projection — Stage 1, act:f5814d6c)
631
+
632
+ `watchtower-cross-ring-reader.mjs` is the synthesis SUBSTRATE for `/briefing`:
633
+ one call assembles, per configured project, what every ring produced —
634
+ Ring 1's parsed state-file sections + git-attention facts, Ring 3's recent
635
+ session summaries + thread cursor timelines, and the inbox's pending counts +
636
+ resolution history (`resolution_mix`: what the operator's dispositions
637
+ actually engaged vs discarded). The script GATHERS; the consumer SYNTHESIZES —
638
+ no convergence scoring or cross-store "insight" is computed here (a
639
+ mechanical projection on a trust surface must not fabricate synthesis). Like
640
+ its siblings it is a **projection** over existing stores, never a new store,
641
+ and it **writes nothing** — no write path exists in the module; the hermetic
642
+ test asserts the filesystem is untouched.
643
+
644
+ **Per-store join keys** (name = `config.projects` key, slug = `slugify(name)`;
645
+ the reader derives both exactly as `resolveProjectIdentity` does, parity-tested):
646
+
647
+ | Store | Located by | Key |
648
+ |-------|-----------|-----|
649
+ | Ring 1 state file | `state/projects/<slug>.md` | slug |
650
+ | Ring 3 sessions | `state/projects/<slug>/sessions/*.md` | slug |
651
+ | Threads | `sessions[].project` via `threadMatchesProject` | slug |
652
+ | git-attention facts | `fact.project` | name |
653
+ | Inbox items | `item.project` via `listItems` | name |
654
+
655
+ **Ring 2 has no dedicated block by design.** Its durable outputs already
656
+ arrive through two covered channels — pattern-promotion items ride the inbox,
657
+ and the recall canary is projected into Ring 1's Standing Issues — so a
658
+ dedicated block would double-report inside one output. Enrichment BODIES stay
659
+ behind the Enrichment Directory contract (`/inbox` reads them per-item).
660
+
661
+ **Honesty fields** (the complement of the config-driven walk — nothing falls
662
+ silently between views): `unattributed_items` (partitioned on config-key
663
+ MEMBERSHIP, not the `project_unresolved` flag), `orphan_threads`,
664
+ `orphaned_state_files`, `duplicate_path_warning` (realpath-compared),
665
+ per-view `path_exists` + `attributed_but_flagged`, and windowing counts
666
+ (`sessions_total` / `sessions_in_window` / `sessions_included`). Sessions
667
+ order by each file's content `Date:` line (same-day filenames sort randomly
668
+ by uuid). `superseded`/`expired` items never stamp `resolved_at`, so they are
669
+ reported as filed-date-window counts, never blended into the disposition-time
670
+ `resolution_mix`; untyped `resolution_type` lands in `untyped_or_other`,
671
+ counted, never dropped.
672
+
673
+ **Output-size caps, always with totals** (CP3: an uncapped live portfolio
674
+ measured 1.37 MB — no consumer channel carries that): thread histories keep
675
+ the newest `THREAD_HISTORY_CAP` (3) snapshots with per-thread `history_total`
676
+ (the full evolution is lineage mode's job — `projectCursorTimeline` itself is
677
+ uncapped); the resolution-event list keeps the newest `RESOLUTION_EVENTS_CAP`
678
+ (20) with `resolution_events_total` while `resolution_mix` counts ALL events.
679
+ The consumer sees "3 of 85", never a silently complete-looking 3. The
680
+ briefing consumes by writing `--portfolio` to a temp file and Reading slices —
681
+ never by echoing JSON through a truncating shell channel. `--project` accepts
682
+ the config-key name or a UNIQUE slug match (the /briefing scope filter is
683
+ slug-shaped).
684
+
685
+ **Consumer:** `/briefing` Step 1d — the reader is the REPLACEMENT gather path
686
+ for the old per-store state/thread reads (with a raw-read fallback for
687
+ consumers that haven't reinstalled), and its output feeds Step 3a chunk
688
+ synthesis and Step 4 cross-cutting synthesis. Pending counts are a narration
689
+ snapshot; the briefing's live `listPending()` load remains the disposition
690
+ source of truth. The 4-field cursor projection (`projectCursorTimeline`) has
691
+ its ONE definition here; `watchtower-narrative-corpus.mjs` imports it
692
+ (watchtower-lib is ring-loaded and soak-frozen, so the shared helper lives in
693
+ the non-ring reader). CLI: `--portfolio | --project <name> | --project-path
694
+ <cwd>`, `--since <ISO|Ndays>` (default 14d, UTC). Tests:
695
+ `__tests__/cross-ring-reader.test.mjs`.
696
+
697
+ **Staged:** precompute-and-cache (a Ring 2 slow-tier phase persisting the
698
+ view) is Stage 2, deferred until the recall-canary soak lifts; consolidating
699
+ the cursor projection into watchtower-lib waits for the same gate.
@@ -0,0 +1,87 @@
1
+ # Worktree-Invocation Contract
2
+
3
+ The shared, universal preamble for any skill that may run from a
4
+ mux/linked git worktree. `/execute` and `/execute-group` both **read
5
+ this file and follow it** at their Step 0 rather than each spelling the
6
+ mechanism — so the detect/sync/artifact rules change in one place.
7
+
8
+ Running a skill from a linked worktree is the **normal case**, not an
9
+ error — never refuse. Establish these three facts first; everything
10
+ caller-specific (how a particular skill merges, whether it spawns
11
+ worktree agents) lives in the caller's own Step 0, AFTER it reads this.
12
+
13
+ ## 1. Detect where you are
14
+
15
+ ```bash
16
+ COMMON="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"
17
+ TOP="$(git rev-parse --show-toplevel 2>/dev/null)"
18
+ # In a linked worktree the common .git lives under the MAIN checkout, so
19
+ # dirname(COMMON) != TOP. On the main checkout they are equal.
20
+ if [ -n "$COMMON" ] && [ "$(dirname "$COMMON")" != "$TOP" ]; then
21
+ : # linked worktree — sync + artifact rules below apply
22
+ fi
23
+ ```
24
+
25
+ `--path-format=absolute` is load-bearing: from the main checkout
26
+ `--git-common-dir` is a bare relative `.git`, and a string compare /
27
+ `dirname` on the relative form misclassifies. Force absolute on the
28
+ read. (Same trap the pib-db path resolver and the completion-gate hook
29
+ both handle.)
30
+
31
+ ## 2. Sync with main before anything else (worktree only)
32
+
33
+ A worktree starts behind — plan text, code, and the staleness/CP guards
34
+ all read state that may have moved on main since the worktree was cut.
35
+ If the branch is behind main:
36
+
37
+ ```bash
38
+ git merge main --no-edit
39
+ ```
40
+
41
+ **On conflict, HALT the whole skill — do not auto-resolve and do not
42
+ proceed to implement against a half-synced tree.** This is an
43
+ interactive skill; resolution is the operator's call. Leave the worktree
44
+ exactly as the conflicted merge left it (conflict markers, an in-progress
45
+ merge) and surface, in plain English:
46
+
47
+ > Sync with main hit a conflict in `<files>`. The worktree is mid-merge.
48
+ > Resolve the conflicts and `git commit` the merge — **or** `git merge
49
+ > --abort` to back out — then re-run the skill. I stopped before
50
+ > implementing so nothing lands on a half-synced tree.
51
+
52
+ ## 3. Completion artifacts land in the MAIN checkout
53
+
54
+ A worktree's `.claude/verification/` is gitignored and disposable —
55
+ anything written there is lost on worktree cleanup (the lost-breadcrumb
56
+ failure: `lesson_workflow_report_worktree_cwd`). Resolve the MAIN
57
+ checkout once and write every verification artifact under it:
58
+
59
+ ```bash
60
+ MAIN="$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")"
61
+ # breadcrumbs, reports → "$MAIN/.claude/verification/<fid>.json" (never cwd-relative)
62
+ mkdir -p "$MAIN/.claude/verification"
63
+ ```
64
+
65
+ On the main checkout `$MAIN` resolves to the cwd, so the same line is
66
+ correct in both places — no on-main special case. The completion-gate
67
+ hook already READS breadcrumbs from `$MAIN/.claude/verification/`; this
68
+ makes the WRITE side agree.
69
+
70
+ **Sibling copies of this one git-truth — keep them in step:** the
71
+ `MAIN_RESOLVE` string constant in
72
+ `templates/workflows/execute-group-implement.js`, and the shell spelling
73
+ in `templates/hooks/action-completion-gate.sh` (a hook can't read this
74
+ doc). Three copies, one resolution; change one, change all three.
75
+
76
+ ## What this contract does NOT cover
77
+
78
+ Caller-specific orchestration stays in the caller's own Step 0, never
79
+ here:
80
+
81
+ - **`/execute-group`** keeps inline: that its `isolation: worktree`
82
+ agents branch from and merge into MAIN regardless of the invoking cwd,
83
+ and its inline-build escape hatch. These describe the workflow's merge
84
+ model and would contradict `/execute`'s no-merge model — they are not
85
+ universal.
86
+ - **`/execute`** keeps inline: that it does NOT merge (the worktree→main
87
+ bridge is `/qa-handoff`), and how its close-out files the handoff.