create-claude-cabinet 0.46.0 → 0.48.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 (189) hide show
  1. package/README.md +3 -6
  2. package/lib/CLAUDE.md +218 -0
  3. package/lib/cli.js +397 -382
  4. package/lib/copy.js +182 -6
  5. package/lib/db-setup.js +122 -17
  6. package/lib/engagement-server-setup.js +1 -17
  7. package/lib/engagement-setup.js +1 -1
  8. package/lib/installer-gate.js +135 -0
  9. package/lib/metadata.js +51 -2
  10. package/lib/modules.js +292 -0
  11. package/lib/mux-setup.js +1 -17
  12. package/lib/project-context.js +16 -96
  13. package/lib/settings-merge.js +194 -13
  14. package/lib/site-audit-setup.js +23 -7
  15. package/lib/verify-setup.js +20 -9
  16. package/lib/watchtower-setup.js +23 -7
  17. package/package.json +1 -1
  18. package/templates/CLAUDE.md +959 -0
  19. package/templates/briefing/_briefing-template.md +6 -11
  20. package/templates/cabinet/advisories-state-schema.md +1 -1
  21. package/templates/cabinet/checklist-stats-schema.md +15 -1
  22. package/templates/cabinet/eval-protocol.md +27 -10
  23. package/templates/cabinet/memory-lifecycle-contract.md +135 -0
  24. package/templates/cabinet/pib-db-access.md +13 -0
  25. package/templates/cabinet/watchtower-contracts.md +453 -0
  26. package/templates/cabinet/worktree-invocation-contract.md +111 -0
  27. package/templates/engagement/OVERVIEW.md +12 -0
  28. package/templates/engagement/__tests__/checklist-visibility.test.mjs +76 -0
  29. package/templates/engagement/__tests__/engagement.test.mjs +177 -2
  30. package/templates/engagement/__tests__/invoice-doc.test.mjs +195 -0
  31. package/templates/engagement/engagement-checklist.mjs +26 -2
  32. package/templates/engagement/engagement-preview.mjs +100 -0
  33. package/templates/engagement/engagement-schema.md +248 -6
  34. package/templates/engagement/engagement.mjs +391 -11
  35. package/templates/engagement/pib-db-patches/pib-db-lib.mjs +49 -9
  36. package/templates/engagement/pib-db-patches/pib-db-mcp-server.mjs +5 -44
  37. package/templates/engagement/pib-db-patches/pib-db-schema.sql +1 -1
  38. package/templates/engagement/pib-db-patches/pib-db.mjs +9 -3
  39. package/templates/engagement/sql-constants.mjs +9 -1
  40. package/templates/engagement-server/__tests__/cross-tenant-auth.test.mjs +87 -0
  41. package/templates/engagement-server/server.mjs +65 -40
  42. package/templates/hooks/action-completion-gate.sh +19 -3
  43. package/templates/hooks/action-quality-gate.sh +10 -0
  44. package/templates/hooks/memory-index-guard.sh +17 -11
  45. package/templates/hooks/skill-telemetry.sh +10 -5
  46. package/templates/hooks/skill-tool-telemetry.sh +11 -5
  47. package/templates/hooks/watchtower-session-start.sh +24 -2
  48. package/templates/mcp/pib-db.json +1 -4
  49. package/templates/mux/bin/mux +8 -1
  50. package/templates/mux/config/help.txt +1 -0
  51. package/templates/rules/acknowledge-when-corrected.md +33 -0
  52. package/templates/rules/enforcement-pipeline.md +0 -12
  53. package/templates/rules/maintainability.md +11 -0
  54. package/templates/rules/memory-capture.md +26 -4
  55. package/templates/rules/plan-before-bulk-or-irreversible-actions.md +48 -0
  56. package/templates/rules/verify-before-asserting.md +79 -0
  57. package/templates/scripts/__tests__/ahead-check-origin.test.mjs +355 -0
  58. package/templates/scripts/__tests__/api-usage-idle-gate.test.mjs +81 -0
  59. package/templates/scripts/__tests__/batch-disposition.test.mjs +98 -0
  60. package/templates/scripts/__tests__/bsql-loader-verify.test.mjs +47 -0
  61. package/templates/scripts/__tests__/claude-churn-authored.test.mjs +90 -0
  62. package/templates/scripts/__tests__/cross-ring-reader.test.mjs +505 -0
  63. package/templates/scripts/__tests__/dx-captures-briefing.test.mjs +169 -0
  64. package/templates/scripts/__tests__/hook-runner.test.mjs +247 -0
  65. package/templates/scripts/__tests__/inbox-assessment.test.mjs +341 -0
  66. package/templates/scripts/__tests__/memory-reachability.test.mjs +150 -0
  67. package/templates/scripts/__tests__/narrative-corpus.test.mjs +247 -0
  68. package/templates/scripts/__tests__/phase-shim.test.mjs +112 -0
  69. package/templates/scripts/__tests__/qa-handoff-merge-state.test.mjs +162 -0
  70. package/templates/scripts/__tests__/resolve-cli.test.mjs +314 -0
  71. package/templates/scripts/__tests__/ring-state-ownership.test.mjs +120 -0
  72. package/templates/scripts/__tests__/ring1-content-detector.test.mjs +168 -0
  73. package/templates/scripts/__tests__/ring1-flagged-actions.test.mjs +135 -0
  74. package/templates/scripts/__tests__/ring1-pib-error-surfacing.test.mjs +69 -0
  75. package/templates/scripts/__tests__/ring1-script-drift.test.mjs +126 -0
  76. package/templates/scripts/__tests__/ring1-stale-open-counts.test.mjs +143 -0
  77. package/templates/scripts/__tests__/ring2-queue-reader.test.mjs +80 -0
  78. package/templates/scripts/__tests__/ring2-recall-canary.test.mjs +150 -0
  79. package/templates/scripts/__tests__/ring2-roster-review.test.mjs +311 -0
  80. package/templates/scripts/__tests__/ring3-chunk-merge.test.mjs +122 -0
  81. package/templates/scripts/__tests__/ring3-close-lenses.test.mjs +359 -0
  82. package/templates/scripts/__tests__/ring3-dedup.test.mjs +92 -12
  83. package/templates/scripts/__tests__/ring3-memory-titles.test.mjs +187 -0
  84. package/templates/scripts/__tests__/ring3-novelty-rescue.test.mjs +148 -0
  85. package/templates/scripts/__tests__/ring3-recent-slice.test.mjs +101 -0
  86. package/templates/scripts/__tests__/ring3-thread-capture.test.mjs +213 -0
  87. package/templates/scripts/__tests__/ring4-reconcile.test.mjs +393 -0
  88. package/templates/scripts/__tests__/shared-thread-reader.test.mjs +187 -0
  89. package/templates/scripts/__tests__/suppression-ledger.test.mjs +148 -0
  90. package/templates/scripts/__tests__/verify-backfill.test.mjs +103 -0
  91. package/templates/scripts/__tests__/verify-coverage.test.mjs +80 -0
  92. package/templates/scripts/__tests__/watchtower-snapshot.test.mjs +261 -0
  93. package/templates/scripts/__tests__/watchtower-status-ring4.test.mjs +89 -0
  94. package/templates/scripts/__tests__/watchtower-sync.test.mjs +311 -0
  95. package/templates/scripts/audit-coherence-check.mjs +123 -0
  96. package/templates/scripts/audit-synth.mjs +117 -0
  97. package/templates/scripts/load-triage-history.js +5 -2
  98. package/templates/scripts/merge-findings.js +4 -1
  99. package/templates/scripts/patterns-scope-check.mjs +122 -0
  100. package/templates/scripts/pib-db-lib.mjs +61 -11
  101. package/templates/scripts/pib-db-mcp-server.mjs +7 -45
  102. package/templates/scripts/pib-db-path.mjs +61 -0
  103. package/templates/scripts/pib-db.mjs +7 -3
  104. package/templates/scripts/review-server.mjs +7 -12
  105. package/templates/scripts/triage-server.mjs +7 -8
  106. package/templates/scripts/validate-memory.mjs +214 -16
  107. package/templates/scripts/watchtower-advisories.mjs +7 -3
  108. package/templates/scripts/watchtower-build-context.mjs +192 -8
  109. package/templates/scripts/watchtower-cross-ring-reader.mjs +714 -0
  110. package/templates/scripts/watchtower-hook-runner.mjs +422 -0
  111. package/templates/scripts/watchtower-inbox-assessment.mjs +706 -0
  112. package/templates/scripts/watchtower-lib.mjs +555 -8
  113. package/templates/scripts/watchtower-narrative-corpus.mjs +385 -0
  114. package/templates/scripts/watchtower-phase-shim.mjs +171 -0
  115. package/templates/scripts/watchtower-queue.mjs +341 -3
  116. package/templates/scripts/watchtower-ring1.mjs +481 -81
  117. package/templates/scripts/watchtower-ring2.mjs +748 -80
  118. package/templates/scripts/watchtower-ring3-close.mjs +1147 -167
  119. package/templates/scripts/watchtower-ring4-runner.sh +85 -0
  120. package/templates/scripts/watchtower-ring4.mjs +753 -0
  121. package/templates/scripts/watchtower-routines.mjs +1 -1
  122. package/templates/scripts/watchtower-snapshot.mjs +452 -0
  123. package/templates/scripts/watchtower-status.sh +17 -0
  124. package/templates/scripts/watchtower-sync.mjs +393 -0
  125. package/templates/scripts/work-tracker-server.mjs +26 -22
  126. package/templates/skills/audit/SKILL.md +18 -5
  127. package/templates/skills/audit/phases/structural-checks.md +22 -0
  128. package/templates/skills/briefing/SKILL.md +289 -41
  129. package/templates/skills/cabinet-accessibility/SKILL.md +1 -1
  130. package/templates/skills/cabinet-process-therapist/SKILL.md +28 -0
  131. package/templates/skills/cabinet-security/SKILL.md +11 -0
  132. package/templates/skills/catch-up/SKILL.md +113 -0
  133. package/templates/skills/cc-publish/SKILL.md +89 -18
  134. package/templates/skills/cc-remember/SKILL.md +45 -0
  135. package/templates/skills/close/SKILL.md +107 -0
  136. package/templates/skills/collab-client/SKILL.md +22 -5
  137. package/templates/skills/collab-consultant/SKILL.md +110 -2
  138. package/templates/skills/debrief/SKILL.md +21 -4
  139. package/templates/skills/debrief/phases/audit-pattern-capture.md +13 -0
  140. package/templates/skills/debrief-classic/SKILL.md +696 -0
  141. package/templates/skills/debrief-classic/calibration.md +44 -0
  142. package/templates/skills/debrief-classic/phases/audit-pattern-capture.md +78 -0
  143. package/templates/skills/debrief-classic/phases/auto-maintenance.md +48 -0
  144. package/templates/skills/debrief-classic/phases/checklist-feedback.md +123 -0
  145. package/templates/skills/debrief-classic/phases/close-work.md +163 -0
  146. package/templates/skills/debrief-classic/phases/health-checks.md +54 -0
  147. package/templates/skills/debrief-classic/phases/inventory.md +40 -0
  148. package/templates/skills/debrief-classic/phases/loose-ends.md +52 -0
  149. package/templates/skills/debrief-classic/phases/methodology-capture.md +223 -0
  150. package/templates/skills/debrief-classic/phases/qa-handoff-sweep.md +78 -0
  151. package/templates/skills/debrief-classic/phases/record-lessons.md +177 -0
  152. package/templates/skills/debrief-classic/phases/report.md +59 -0
  153. package/templates/skills/debrief-classic/phases/update-state.md +48 -0
  154. package/templates/skills/debrief-classic/phases/upstream-feedback.md +185 -0
  155. package/templates/skills/debrief-classic/phases/verify-coverage.md +101 -0
  156. package/templates/skills/execute/SKILL.md +71 -6
  157. package/templates/skills/execute/phases/commit-and-deploy.md +8 -0
  158. package/templates/skills/execute-group/SKILL.md +23 -26
  159. package/templates/skills/generate-plan-groups/SKILL.md +20 -0
  160. package/templates/skills/inbox/SKILL.md +177 -31
  161. package/templates/skills/memory/SKILL.md +22 -6
  162. package/templates/skills/orient/SKILL.md +75 -5
  163. package/templates/skills/orient-classic/SKILL.md +770 -0
  164. package/templates/skills/orient-classic/phases/auto-maintenance.md +52 -0
  165. package/templates/skills/orient-classic/phases/briefing.md +53 -0
  166. package/templates/skills/orient-classic/phases/cabinet.md +46 -0
  167. package/templates/skills/orient-classic/phases/checklist-status.md +54 -0
  168. package/templates/skills/orient-classic/phases/context.md +88 -0
  169. package/templates/skills/orient-classic/phases/data-sync.md +35 -0
  170. package/templates/skills/orient-classic/phases/deferred-check.md +55 -0
  171. package/templates/skills/orient-classic/phases/dx-captures.md +53 -0
  172. package/templates/skills/orient-classic/phases/health-checks.md +50 -0
  173. package/templates/skills/orient-classic/phases/verify-backfill.md +109 -0
  174. package/templates/skills/orient-classic/phases/work-scan.md +69 -0
  175. package/templates/skills/pulse/SKILL.md +10 -8
  176. package/templates/skills/qa-drain/SKILL.md +38 -0
  177. package/templates/skills/qa-handoff/SKILL.md +287 -46
  178. package/templates/skills/setup-accounts/SKILL.md +38 -16
  179. package/templates/skills/spring-clean/SKILL.md +1 -1
  180. package/templates/skills/spring-clean/phases/execute-decisions.md +1 -1
  181. package/templates/skills/threads/SKILL.md +4 -0
  182. package/templates/skills/validate/phases/validators.md +127 -0
  183. package/templates/skills/watchtower/SKILL.md +174 -30
  184. package/templates/verify-runtime/CONVENTIONS.md +9 -0
  185. package/templates/verify-runtime/README.md +37 -0
  186. package/templates/watchtower/config.json.template +8 -2
  187. package/templates/workflows/deliberative-audit.js +258 -87
  188. package/templates/workflows/execute-group-complete.js +26 -3
  189. package/templates/workflows/execute-group-implement.js +16 -2
@@ -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
@@ -99,6 +112,39 @@ backed up aside as `<slug>.json.corrupt-<ts>`, a fresh file is written,
99
112
  and the recovery is logged loudly. Per-thread writes are isolated — one
100
113
  bad thread file must not abort writes for the remaining threads.
101
114
 
115
+ **Earned, bounded membership (act:e8793574).** A single session close writes
116
+ at most `MAX_THREADS_PER_SESSION` (5) threads, and each membership must carry
117
+ a DISTINCT contribution. `selectEarnedThreads()` (in `watchtower-ring3-close.mjs`)
118
+ enforces this in code, not just in the prompt: a blank/one-word contribution
119
+ is dropped, a contribution that restates an already-accepted one (spray) is
120
+ dropped, and the accepted set is capped. Overlap is healthy — a session
121
+ genuinely advancing several zoom levels earns each — but a focused session
122
+ can no longer be sprayed across 6-9 threads on loose topical match.
123
+
124
+ **Relationship fields (act:e8793574, additive at `schema_version: 2`).** Two
125
+ optional union-deduped arrays let a cold session trace a thread:
126
+
127
+ - `related_fids` — pib-db work-item fids (`act:<8hex>`) the sessions that
128
+ advanced this thread referenced. Populated DETERMINISTICALLY by
129
+ `extractRelatedFids()` scanning the transcript — never model-authored, so no
130
+ hallucinated fid can enter. Unioned across sessions on disk.
131
+ - `lineage` — sibling threads CO-ADVANCED in the same session (the only thread
132
+ relation cheap enough to assert as ground truth: co-occurrence in one
133
+ capture). Entry shape `{ slug, relation: "co-occurrence", session_id, date }`,
134
+ deduped by `slug`. Empty when the session earned only one thread.
135
+
136
+ Both default to `[]` when the 7th `updateThreadFile()` argument is omitted, so
137
+ older callers and the corrupt/create paths are unchanged.
138
+
139
+ **No silent success on systemic API failure (act:e8793574).** When the
140
+ Anthropic API rejects every call for an account-level reason (billing
141
+ exhausted, bad key, quota), every knowledge-extraction phase produces nothing.
142
+ `ring3-health.json` then reports `status: "degraded"` with an `api_error`
143
+ `{ type, message }` instead of `"success"` (`classifyApiError()` /
144
+ `buildHealth()`), and the run logs a loud `SYSTEMIC API FAILURE` line. This
145
+ exists because a credit-balance outage silently froze thread capture for a
146
+ week (2026-07-05 → 2026-07-12) while health kept reporting success.
147
+
102
148
  ## Enrichment Directory
103
149
 
104
150
  Per-item enrichment lives in `queue/items/<id>/enrichment/`. Four
@@ -199,6 +245,97 @@ top of the owning project's chunk — the re-surfacing the close pass
199
245
  promises. A `session-close`/`briefing` mandate without the matching
200
246
  directive key is a data error: consumers skip the member and log it.
201
247
 
248
+ ## Session-Close Extraction Lenses — Phase 2n/2o/2p (act:4ff2cfb3)
249
+
250
+ Ring 3's session close runs three extraction lenses beyond the knowledge
251
+ pass (2d) and advisor pass (2m), each modeled on `advisorPass` with an
252
+ injectable `callFn` (testable; never folded into the hardened Phase 2d —
253
+ see `lesson_ring3_lens_injectable_phase_pattern`):
254
+
255
+ - **Phase 2n — Raised-but-unhandled.** Loose ends the session RAISED
256
+ (promises, side-issues, open questions) but neither completed nor
257
+ filed → an inbox item under the `raised-unhandled` category. Capped at
258
+ `RAISED_UNHANDLED_MAX = 5` per session.
259
+ - **Phase 2o — Skill-candidate.** A manual procedure repeated by hand
260
+ that a skill could absorb → a `skill-candidate` inbox item ("make a
261
+ skill?"). Capped at `SKILL_CANDIDATE_MAX = 3` per session.
262
+ - **Phase 2p — Checklist-catch.** A surfaced change-impact check that
263
+ caught a real bug → recorded to `checklist-stats.json` (the
264
+ catch-recording side; consumed by `/debrief`'s checklist-feedback and
265
+ the audit `checklist-pruning` phase). Not an inbox item — a stats write.
266
+
267
+ The two new inbox categories (`raised-unhandled`, `skill-candidate`)
268
+ follow the standard item schema and the same dedup-vs-corpora discipline
269
+ as the other Ring 3 extractions.
270
+
271
+ ## Suppression Ledger (Ring 3 dedup observability)
272
+
273
+ The recall-fix program (audit act:7d6a4b3b) makes Ring 3's
274
+ knowledge-capture dedup **auditable**. Every time `isDuplicate`
275
+ suppresses an extraction, the suppression site appends one structured
276
+ record to a shared sidecar so over-suppression is visible instead of
277
+ vanishing into a write-only cron.
278
+
279
+ **File:** `state/suppression-ledger.jsonl` (under `WATCHTOWER_DIR`).
280
+ Append-only JSON-lines. NOT shipped as a template — generated at
281
+ runtime, like `checklist-stats.json` and `.memory-pressure.jsonl`.
282
+
283
+ **Producer:** `recordSuppression(record, opts)` in `watchtower-lib.mjs`
284
+ (shared home; reachable by every ring). Called from every Ring 3
285
+ suppression site — Phase 2d (extraction), Phase 2m (advisor close
286
+ pass), Phase 2n (raised-unhandled lens), Phase 2o (skill-candidate
287
+ lens). The human `log()` line stays beside it; the ledger is the
288
+ machine-readable twin, never a parse of the prose.
289
+
290
+ **Consumer:** the Ring 2 slow over-suppression canary (M5) — the ONLY
291
+ reader. It windows records by `ts`, computes a per-project
292
+ suppression-rate (suppressed / (suppressed + filed)) against a captured
293
+ post-M1a baseline, samples actual suppressed titles + what they matched
294
+ for human eyeball, tracks net-durably-saved (filed × approval-rate), and
295
+ writes `state/recall-canary.json`. The canary also prunes the ledger to
296
+ a retention window on each run (growth is bounded at canary cadence, not
297
+ on the suppression hot path). It is over-suppression-only and states so
298
+ in its own `note` field. Guards: `total===0 ⇒ no alert`; a minimum
299
+ denominator before a rate can fire; baseline captured on first run (else
300
+ it would alert on its own improvement).
301
+
302
+ **The surfacing path** (no write-only telemetry): the canary writes
303
+ `state/recall-canary.json` (per-project `{suppressed, filed, rate,
304
+ baseline, by_corpus, approval_rate, net_durably_saved, sample, alert}`);
305
+ **Ring 1** reads it and renders an alerting project's entry into that
306
+ project's **Standing Issues** (Ring 1 owns the per-project state file);
307
+ **/briefing**'s State-file-flags reader surfaces it. Three named hops,
308
+ each owning its tier. Flag: `defaults.recall_canary` (opt-out).
309
+
310
+ **Record shape (the contract):**
311
+
312
+ ```json
313
+ {
314
+ "ts": "ISO-8601 timestamp of the suppression",
315
+ "project": "resolved project name (or null)",
316
+ "corpus": "memory | thread-cursor | pending | resolved | dismissed",
317
+ "suppressed_title": "the extraction title that was killed",
318
+ "matched_against": "the corpus entry it collided with",
319
+ "session_id": "the closing session's id (or null)"
320
+ }
321
+ ```
322
+
323
+ **Invariants:**
324
+
325
+ - **Fail-open.** `recordSuppression` never throws. A failed append logs
326
+ one line and continues — a dropped ledger record must NEVER block an
327
+ extraction from filing. The ledger is observability, not the decision.
328
+ - **Additive.** New fields ride alongside; readers default missing
329
+ fields. The `corpus` enum grows when a new dedup corpus lands (M1a
330
+ added `thread-cursor` as a first-class corpus split off from
331
+ `memory`).
332
+ - **Baseline before behavior.** The ledger lands BEFORE any matcher
333
+ change so it records the OLD (pre-M1a) suppression behavior as the
334
+ before-baseline the canary compares against.
335
+ - **Over-suppression only.** The ledger sees what dedup KILLED, not what
336
+ extraction never proposed (M2 under-extraction). The canary states
337
+ this blindness in its own output.
338
+
202
339
  ## Deferred Schemas
203
340
 
204
341
  The following are specified in project notes but NOT formalized until
@@ -214,6 +351,12 @@ their owning plan ships:
214
351
 
215
352
  ## Ring 4 — Periodic Truth Reconciliation (design phase)
216
353
 
354
+ > **Superseded — stage 1 BUILT (act:36dae795).** This is the original design
355
+ > note, kept for its problem framing. The shipped implementation is documented
356
+ > below under "Ring 4 — Truth Reconciliation (built, stage 1)"; read that
357
+ > section for current behavior. The "concept" status line below describes the
358
+ > pre-build state only.
359
+
217
360
  **Status: concept. Needs design session before implementation.**
218
361
 
219
362
  Rings 1–3 operate on individual signals: individual files (R1),
@@ -283,3 +426,313 @@ They're complementary, not redundant.
283
426
  staleness detection (stale work, memory hygiene). Where's the line
284
427
  between R2's "is this work item stale?" and R4's "is this document
285
428
  still true?" Is R4 an extension of R2 slow, or genuinely distinct?
429
+
430
+ ## Ring 4 — Truth Reconciliation (built, stage 1)
431
+
432
+ **Status: BUILT (act:36dae795). Supersedes the design-phase section
433
+ above** — the open design questions there are now resolved here.
434
+ Implemented in `templates/scripts/watchtower-ring4.mjs` (the ring) +
435
+ `templates/scripts/watchtower-ring4-runner.sh` (PID-locked runner,
436
+ modeled on the Ring 2 runner). Hermetic suite:
437
+ `templates/scripts/__tests__/ring4-reconcile.test.mjs`. Cron/launchd
438
+ registration is a `/watchtower install` follow-up, not part of this
439
+ build.
440
+
441
+ ### What it does
442
+
443
+ Periodically compares documentary CLAIMS against codebase REALITY and
444
+ files drift as `doc-drift` inbox items. Reconciliation is COMPARISON,
445
+ NOT generation — Ring 4 never rewrites a document; it flags drift with
446
+ enough context to fix, and the operator triages it. Stage 1 is PURELY
447
+ MECHANICAL (no Claude API call), which keeps the cron cheap and the
448
+ ring hermetically testable.
449
+
450
+ It owns two ledger duties at orient/debrief retirement
451
+ (`orient-debrief-coverage-ledger.md` §C/§D): documentation staleness
452
+ (CLAUDE.md key-file claims, briefing files, system-status) and the
453
+ dropped embedded-pulse description-accuracy spot checks (counts). The
454
+ standalone `/pulse` stays invocable on demand.
455
+
456
+ ### Resolved design questions
457
+
458
+ - **Scope per run → rotation, not full sweep.** Each run reconciles at
459
+ most `RING4_PROJECT_CAP` (3) projects, least-recently-reconciled
460
+ first, BUT a project with commits since its last reconcile jumps the
461
+ queue. This is the nervous-system principle in mechanical form: R4
462
+ consumes R1's signal (git recency) to prioritize, rather than
463
+ blind-sweeping the portfolio. State in `state/ring4-reconcile.json`
464
+ (`last_run`, `projects_checked`).
465
+ - **Drift threshold → mechanical existence/counts, high precision.**
466
+ Stage 1 flags only (a) a backtick-fenced repo-relative path that is
467
+ missing on disk, gated so the path's TOP-LEVEL directory still
468
+ exists (kills illustrative/foreign-path noise — `webapp/...` in a
469
+ CC memory file is skipped because `webapp/` isn't a repo dir) and
470
+ not gitignored (a gitignored absent path is a generated/local
471
+ artifact — its absence is expected, not drift); and (b) a count
472
+ claim that disagrees with a mechanical file count, but ONLY for
473
+ config-declared count rules (zero-noise default). A "slightly
474
+ outdated line count" is deliberately NOT flagged.
475
+ - **Trigger vs schedule → weekly cadence + git-recency priority.** A
476
+ weekly gate (`RECONCILE_INTERVAL_DAYS`, mirrors Ring 2's roster
477
+ review) plus git-recency-driven project order, rather than coupling
478
+ to R1/R2 internal trigger files.
479
+ - **Relationship to Ring 2 → genuinely distinct ring.** R2 slow asks
480
+ "is this WORK ITEM stale?" (pib-db state); R4 asks "is this DOCUMENT
481
+ still true?" (doc claims vs codebase). Distinct cadence (weekly vs
482
+ 30-min), distinct runner/cron, distinct produced category.
483
+ - **Cross-project → per-project only, stage 1.** A CC template
484
+ claiming something about consumer behavior is a documented future
485
+ extension, not built.
486
+
487
+ ### The document set (current-reality docs only)
488
+
489
+ `collectDocuments()` scans docs consumed AS TRUTH, capped at
490
+ `RING4_DOC_CAP` (40): root `CLAUDE.md`, `system-status.md` (root and
491
+ `.claude/`), `.claude/cabinet/_briefing*.md`, and the project's
492
+ built-in memory entries (`MEMORY.md` + curated `*.md`). It
493
+ deliberately EXCLUDES `.claude/plans/` and `.claude/methodology/`:
494
+ those are HISTORICAL records — a plan for retired work SHOULD
495
+ reference now-deleted files, so path-existence over them is mostly
496
+ noise (the CC dogfood produced ~90 false claims from historical plans;
497
+ filtering to current-reality docs + gitignore-awareness cut it to 2
498
+ real findings). Whether a plan is actually DONE — plan-to-reality
499
+ drift — is a distinct SEMANTIC check, deferred.
500
+
501
+ ### Memory is DRIFT-SUPERSEDE only
502
+
503
+ The memory pass is the missing-path reconciler applied to memory files
504
+ ("is what we wrote still true?" — entries referencing renamed/removed
505
+ files). Paths in a memory file resolve against the PROJECT root, not
506
+ the memory dir. Memory HYGIENE (budget / decay / consolidate) stays
507
+ Ring 2 slow's `runMemoryHygiene` / `surfacePersistentViolations` —
508
+ Ring 4 does NOT fork a second memory-health producer (act:36dae795
509
+ 2026-06-16 scope note; design in `cross-store-memory-redesign.md`).
510
+
511
+ ### Inbox contract
512
+
513
+ Findings file `doc-drift` items (`filed_by: 'ring4'`, urgency `low`,
514
+ three triage options: fix-doc / fix-code / dismiss). Dedup is ONE
515
+ pending item per `evidence.drift_key` per project — `path:<doc>` for
516
+ missing-path, `count:<doc>:<label>` for count-mismatch (mirrors Ring
517
+ 2's `roster_kind` convention: a still-open drift item is a reference,
518
+ not a duplicate). At most `RING4_ITEM_CAP` (8) items filed per run.
519
+ `doc-drift` is NOT a dispatched or gated category — it is a plain
520
+ inbox item the operator triages. The weekly cadence + per-doc dedup
521
+ keep re-nagging bounded.
522
+
523
+ ### Config
524
+
525
+ Feature flag `defaults.truth_reconciliation` (default true) gates the
526
+ whole ring; `ring_frequencies.ring4` (604800 = weekly) carries the
527
+ intended cron interval. Count rules are per-project and opt-in:
528
+ `projects[<name>].reconcile.count_rules = [{ label, doc, pattern,
529
+ glob }]` where `pattern` is a regex source with one capture group
530
+ around the number and `glob` uses the small dependency-free grammar in
531
+ `countGlob` (`dir/*`, `dir/prefix-*/`, `dir/*.ext`, `dir/**/*.ext`).
532
+ Default empty → the count pass no-ops (no false drift).
533
+
534
+ ### Deferred (future stages)
535
+
536
+ Semantic comparison (does this prose paragraph still describe the
537
+ code?), plan-to-reality / plan-status reconciliation, dependency-
538
+ version drift beyond the count-rule mechanism, gitignored-directory
539
+ patterns that don't match a non-existent path, and cross-project
540
+ template-vs-consumer reconciliation.
541
+
542
+ ## Consumer Hook Contract (Plan 9)
543
+
544
+ The formalized extension model for **non-interactive** consumer customs at
545
+ the ring lifecycle seams (act:4a6e907c). Supersedes the deferred-schema
546
+ placeholder `hooks/<ring>-<phase>.d/` above — the real location is
547
+ per-project, not a global runtime dir. Interactive customs are owned by
548
+ routine dispatch (act:c2a55c08, "Routine Dispatch" above), never by hooks.
549
+
550
+ ### Seams and invocation
551
+
552
+ The rings already invoke consumer hooks by running each command string in
553
+ `config.hooks[<seam>]` with the ring's state JSON on stdin. Three seams:
554
+
555
+ | Seam | Ring / scope | Outer timeout |
556
+ |------|--------------|---------------|
557
+ | `ring1-post-collect` | Ring 1, per project, post state-collect | 30s |
558
+ | `ring2-slow-post` | Ring 2 slow tier, portfolio-wide | 60s |
559
+ | `ring3-close-post` | Ring 3, per session close | 120s |
560
+
561
+ `watchtower-hook-runner.mjs --seam <seam>` is registered ONCE per seam (via
562
+ `--register`, called by `/watchtower install`). The rings invoke it like any
563
+ hook command — the rings are NOT modified. The runner then resolves the
564
+ target project(s) and runs that project's hook scripts.
565
+
566
+ ### Registration (drop-a-file)
567
+
568
+ A consumer registers a hook per-project by placing an executable script in
569
+ `<project-root>/.claude/watchtower/hooks/<seam>/`. Presence is registration —
570
+ no config editing. Discovery is name-sorted (numeric-prefix convention:
571
+ `10-foo.sh` before `20-bar.sh`). A file is runnable if it has the executable
572
+ bit (run directly, shebang decides the interpreter) or a known extension
573
+ (`.sh`/`.bash` → bash, `.mjs`/`.js`/`.cjs` → node, `.py` → python3); other
574
+ files (`.json`, `.md`, …) are ignored. Files prefixed `_` or `.` are skipped
575
+ (disable convention).
576
+
577
+ ### Project resolution
578
+
579
+ - Single-project seams (`ring1-post-collect`, `ring3-close-post`): the project
580
+ is taken from the ring's stdin state (`.path`/`.name`, or `.project_path`/
581
+ `.project`).
582
+ - Portfolio seam (`ring2-slow-post`): the slow state carries no project, so the
583
+ runner enumerates `config.projects` and runs each project's seam dir.
584
+
585
+ ### Hook I/O contract
586
+
587
+ A hook script receives:
588
+ - **stdin:** the ring's state JSON, verbatim (see the seam table for shape).
589
+ - **cwd:** the project root.
590
+ - **env:** `WATCHTOWER_SEAM`, `WATCHTOWER_PROJECT`, `WATCHTOWER_PROJECT_PATH`,
591
+ `WATCHTOWER_SESSION_ID` (ring3), `WATCHTOWER_HOOK_DIR`.
592
+
593
+ A hook script returns:
594
+ - **exit 0** = success; non-zero = failure (isolated, recorded, never fatal).
595
+ - **stdout:** optional JSON. `{"additional_checks":[…]}` is the Ring 1
596
+ passthrough — the runner surfaces those at the envelope top level so Ring
597
+ 1's existing `parsed.additional_checks` consumption works. Other JSON is
598
+ captured as structured `output`; non-JSON is captured verbatim (truncated).
599
+ - **stderr:** captured for diagnostics; non-fatal.
600
+
601
+ ### Isolation and budget
602
+
603
+ The runner is bulletproof: any single hook's failure (non-zero exit, timeout,
604
+ spawn error) becomes a typed status (`failed` / `timeout`) and never aborts
605
+ the loop, and the runner always prints a valid envelope and exits 0 — one bad
606
+ consumer hook can never break a ring tick. Because the runner is a single
607
+ command sharing the ring's one timeout envelope, it runs hooks sequentially
608
+ against a deadline (the seam timeout minus headroom) and marks any hook it
609
+ can't reach in time `skipped` (reason `budget-exhausted`) — no silent
610
+ truncation. `WATCHTOWER_HOOK_BUDGET_MS` overrides the per-seam budget.
611
+
612
+ ### Output envelope
613
+
614
+ ```json
615
+ { "schema_version": 1, "seam": "<seam>",
616
+ "targets": [{ "name": "...", "path": "..." }],
617
+ "hooks": [{ "hook": "<path>", "project": "...", "status": "success|failed|timeout|skipped", "output"?: ..., "exit"?: N, "stderr"?: "...", "reason"?: "budget-exhausted" }],
618
+ "additional_checks"?: [ ... ] }
619
+ ```
620
+
621
+ ### Migration shim
622
+
623
+ `watchtower-phase-shim.mjs --phase <path> [--cwd <dir>] [--seam <seam>]` is the
624
+ adapter for an EXISTING repo script (a former orient/debrief phase) that
625
+ expects the project root as cwd rather than reading stdin. Registered as a one
626
+ -line `config.hooks[<seam>]` entry, it runs the target with the same cwd/env/
627
+ stdin normalization and passes JSON stdout through transparently (preserving
628
+ `additional_checks`), propagating the target's exit code. New hooks prefer the
629
+ drop-in directory; the shim is for scripts that already exist.
630
+
631
+ ## Decision Lineage (read-only projection — Plan 12, act:a2efc0ce)
632
+
633
+ The `/briefing lineage` drill-down answers "what past decisions still
634
+ constrain THIS project, and what's reversible?" It is a **projection** over
635
+ existing stores, never a new store, and it **writes nothing** — no state file,
636
+ no inbox item, no memory write. This is the structural invariant: the engine
637
+ holds no persisted copy, so it cannot drift from its sources.
638
+
639
+ `watchtower-narrative-corpus.mjs` is the read-only substrate helper. It covers
640
+ the two watchtower-NATIVE stores so it needs no auto-memory-dir resolver:
641
+
642
+ - `assembleNarrativeCorpus({cwd})` — current project ONLY (resolved
643
+ worktree-aware via `resolveProjectIdentity`; no free `project` arg, so the
644
+ five-namespace keying footgun can't arise). Returns `inbox_decisions`
645
+ (knowledge-extraction items; `resolution_type: 'captured-to-memory'`
646
+ duplicates dropped so a decision that became a memory file isn't
647
+ double-counted; `superseded` status KEPT — it is the supersession view's
648
+ own subject), `thread_cursors` (full `cursor_history` timelines),
649
+ `per_store_counts`, `skipped_unresolved` (phantom-key items repo-wide that
650
+ the resolver couldn't attribute — surfaced, never silently dropped),
651
+ `time_span`. CLI: `--corpus --project-path <cwd>`.
652
+ - `parseDecisionEdges(markdown)` (pure) + `readMemoryDecisions(dir)` — the
653
+ memory/methodology/pib stores are read by the `/briefing` CONSUMER (which
654
+ resolves their paths), but the EDGE RULE lives here, tested: a
655
+ `**Supersedes:**` block is a RECORDED directional edge; a `[[wikilink]]` is
656
+ an undirected see-also and is **never** rendered as a supersession. CLI:
657
+ `--memory <consumer-resolved-dir>`.
658
+
659
+ Provenance is load-bearing because a narrative is a trust surface: the render
660
+ labels *recorded* edges (parsed `Supersedes:` / pib `absorbs act:`) distinctly
661
+ from *inferred* ones (model-read from prose), and never presents an inferred
662
+ link as recorded history. Deliberately a SIBLING of `watchtower-snapshot.mjs`
663
+ (same reader family, same hermetic-test shape), touching zero ring code. Tests:
664
+ `__tests__/narrative-corpus.test.mjs`. Staged: the machine-readable
665
+ supersede-edge discipline (making edges consistent + live) is deferred and
666
+ MERGES INTO the cross-store `supersede` verb
667
+ (`.claude/plans/cross-store-memory-redesign.md`, Ring 2-slow), not a fork.
668
+
669
+ ## Cross-Ring Reader (read-only projection — Stage 1, act:f5814d6c)
670
+
671
+ `watchtower-cross-ring-reader.mjs` is the synthesis SUBSTRATE for `/briefing`:
672
+ one call assembles, per configured project, what every ring produced —
673
+ Ring 1's parsed state-file sections + git-attention facts, Ring 3's recent
674
+ session summaries + thread cursor timelines, and the inbox's pending counts +
675
+ resolution history (`resolution_mix`: what the operator's dispositions
676
+ actually engaged vs discarded). The script GATHERS; the consumer SYNTHESIZES —
677
+ no convergence scoring or cross-store "insight" is computed here (a
678
+ mechanical projection on a trust surface must not fabricate synthesis). Like
679
+ its siblings it is a **projection** over existing stores, never a new store,
680
+ and it **writes nothing** — no write path exists in the module; the hermetic
681
+ test asserts the filesystem is untouched.
682
+
683
+ **Per-store join keys** (name = `config.projects` key, slug = `slugify(name)`;
684
+ the reader derives both exactly as `resolveProjectIdentity` does, parity-tested):
685
+
686
+ | Store | Located by | Key |
687
+ |-------|-----------|-----|
688
+ | Ring 1 state file | `state/projects/<slug>.md` | slug |
689
+ | Ring 3 sessions | `state/projects/<slug>/sessions/*.md` | slug |
690
+ | Threads | `sessions[].project` via `threadMatchesProject` | slug |
691
+ | git-attention facts | `fact.project` | name |
692
+ | Inbox items | `item.project` via `listItems` | name |
693
+
694
+ **Ring 2 has no dedicated block by design.** Its durable outputs already
695
+ arrive through two covered channels — pattern-promotion items ride the inbox,
696
+ and the recall canary is projected into Ring 1's Standing Issues — so a
697
+ dedicated block would double-report inside one output. Enrichment BODIES stay
698
+ behind the Enrichment Directory contract (`/inbox` reads them per-item).
699
+
700
+ **Honesty fields** (the complement of the config-driven walk — nothing falls
701
+ silently between views): `unattributed_items` (partitioned on config-key
702
+ MEMBERSHIP, not the `project_unresolved` flag), `orphan_threads`,
703
+ `orphaned_state_files`, `duplicate_path_warning` (realpath-compared),
704
+ per-view `path_exists` + `attributed_but_flagged`, and windowing counts
705
+ (`sessions_total` / `sessions_in_window` / `sessions_included`). Sessions
706
+ order by each file's content `Date:` line (same-day filenames sort randomly
707
+ by uuid). `superseded`/`expired` items never stamp `resolved_at`, so they are
708
+ reported as filed-date-window counts, never blended into the disposition-time
709
+ `resolution_mix`; untyped `resolution_type` lands in `untyped_or_other`,
710
+ counted, never dropped.
711
+
712
+ **Output-size caps, always with totals** (CP3: an uncapped live portfolio
713
+ measured 1.37 MB — no consumer channel carries that): thread histories keep
714
+ the newest `THREAD_HISTORY_CAP` (3) snapshots with per-thread `history_total`
715
+ (the full evolution is lineage mode's job — `projectCursorTimeline` itself is
716
+ uncapped); the resolution-event list keeps the newest `RESOLUTION_EVENTS_CAP`
717
+ (20) with `resolution_events_total` while `resolution_mix` counts ALL events.
718
+ The consumer sees "3 of 85", never a silently complete-looking 3. The
719
+ briefing consumes by writing `--portfolio` to a temp file and Reading slices —
720
+ never by echoing JSON through a truncating shell channel. `--project` accepts
721
+ the config-key name or a UNIQUE slug match (the /briefing scope filter is
722
+ slug-shaped).
723
+
724
+ **Consumer:** `/briefing` Step 1d — the reader is the REPLACEMENT gather path
725
+ for the old per-store state/thread reads (with a raw-read fallback for
726
+ consumers that haven't reinstalled), and its output feeds Step 3a chunk
727
+ synthesis and Step 4 cross-cutting synthesis. Pending counts are a narration
728
+ snapshot; the briefing's live `listPending()` load remains the disposition
729
+ source of truth. The 4-field cursor projection (`projectCursorTimeline`) has
730
+ its ONE definition here; `watchtower-narrative-corpus.mjs` imports it
731
+ (watchtower-lib is ring-loaded and soak-frozen, so the shared helper lives in
732
+ the non-ring reader). CLI: `--portfolio | --project <name> | --project-path
733
+ <cwd>`, `--since <ISO|Ndays>` (default 14d, UTC). Tests:
734
+ `__tests__/cross-ring-reader.test.mjs`.
735
+
736
+ **Staged:** precompute-and-cache (a Ring 2 slow-tier phase persisting the
737
+ view) is Stage 2, deferred until the recall-canary soak lifts; consolidating
738
+ the cursor projection into watchtower-lib waits for the same gate.
@@ -0,0 +1,111 @@
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
+ ## 4. Main-only files (cabinet briefings) are read through `$MAIN` too
77
+
78
+ The same worktree-invisibility that governs *writes* (§3) also governs
79
+ *reads* of files that live only in the main checkout. `.claude/cabinet/`
80
+ briefing and context files (`_briefing*.md`, member briefings) are
81
+ typically gitignored — generated per-project by `/onboard`, never
82
+ committed — so §2's `git merge main` never brings them into a worktree
83
+ (gitignored content is in no commit), and a cwd-relative read from a
84
+ worktree fails or reads a stale copy. Skills that assemble member
85
+ briefings (the `/execute` and `/execute-group` checkpoint protocol) hit
86
+ this at CP time.
87
+
88
+ Read cabinet/briefing files through the same `$MAIN` from §3:
89
+
90
+ ```bash
91
+ # member briefings, cabinet context → "$MAIN/.claude/cabinet/…" (never cwd-relative)
92
+ cat "$MAIN/.claude/cabinet/_briefing.md"
93
+ ```
94
+
95
+ On the main checkout `$MAIN` resolves to the cwd, so the same line is
96
+ correct in both places — no on-main special case. One rule, both
97
+ directions: worktree-invisible `.claude/` state is always addressed
98
+ through `$MAIN`, whether you are writing it (§3) or reading it (here).
99
+
100
+ ## What this contract does NOT cover
101
+
102
+ Caller-specific orchestration stays in the caller's own Step 0, never
103
+ here:
104
+
105
+ - **`/execute-group`** keeps inline: that its `isolation: worktree`
106
+ agents branch from and merge into MAIN regardless of the invoking cwd,
107
+ and its inline-build escape hatch. These describe the workflow's merge
108
+ model and would contradict `/execute`'s no-merge model — they are not
109
+ universal.
110
+ - **`/execute`** keeps inline: that it does NOT merge (the worktree→main
111
+ bridge is `/qa-handoff`), and how its close-out files the handoff.
@@ -117,6 +117,17 @@ timelog. The running total appears only in a principal's update — never
117
117
  in a delegate's. Billing can be scoped by period: the full engagement
118
118
  total, the current month, or only hours since the last sync.
119
119
 
120
+ **Invoicing.** `/collab-consultant invoice [YYYY-MM]` renders a clean
121
+ invoice document from a month's tracked hours (defaults to the last full
122
+ calendar month; `--itemized` for one line per timelog row instead of a
123
+ single consolidated line). The document is built for upload to the
124
+ operator's bank (Relay), whose "Create Invoice → Autofill from document"
125
+ extracts the customer, line items, and total — so the document carries
126
+ no invoice number and no From/bank block (Relay supplies those). It's
127
+ written to `invoices/<YYYY-MM>.html` with a best-effort PDF conversion.
128
+ Relay defaults the due date to today, so the skill reminds you to set the
129
+ Net-30 due date by hand after autofill.
130
+
120
131
  ### Credential handoff
121
132
 
122
133
  When a client needs to provide a secret (API key, token, password):
@@ -142,6 +153,7 @@ When a client needs to provide a secret (API key, token, password):
142
153
  | `/engagement-message` | Consultant | Send a free-text note to a recipient |
143
154
  | `/engagement-sync` | Consultant | Push updates to recipients (with preview + confirm) + pull feedback |
144
155
  | `/engagement-status` | Consultant | Dashboard: previews, feedback inbox, billing, tag warnings |
156
+ | `/collab-consultant invoice` | Consultant | Render a month's invoice document for upload to Relay's autofill |
145
157
  | `/engagement` | Client | Review the latest update and respond |
146
158
  | `/engagement-progress` | Client | Quick glance — what's new, counts, read-only |
147
159