create-claude-cabinet 0.45.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 (165) hide show
  1. package/README.md +6 -10
  2. package/lib/cli.js +374 -91
  3. package/lib/copy.js +108 -6
  4. package/lib/db-setup.js +122 -17
  5. package/lib/engagement-server-setup.js +34 -9
  6. package/lib/metadata.js +51 -2
  7. package/lib/migrate-from-omega.js +13 -1
  8. package/lib/mux-setup.js +33 -9
  9. package/lib/settings-merge.js +52 -10
  10. package/lib/watchtower-setup.js +230 -0
  11. package/package.json +5 -1
  12. package/templates/cabinet/_cabinet-member-template.md +8 -3
  13. package/templates/cabinet/advisories-state-schema.md +34 -7
  14. package/templates/cabinet/checklist-stats-schema.md +15 -1
  15. package/templates/cabinet/composition-patterns.md +4 -3
  16. package/templates/cabinet/memory-lifecycle-contract.md +135 -0
  17. package/templates/cabinet/pib-db-access.md +13 -0
  18. package/templates/cabinet/skill-output-conventions.md +35 -1
  19. package/templates/cabinet/watchtower-contracts.md +503 -1
  20. package/templates/cabinet/worktree-invocation-contract.md +87 -0
  21. package/templates/engagement/OVERVIEW.md +12 -0
  22. package/templates/engagement/__tests__/engagement.test.mjs +177 -2
  23. package/templates/engagement/__tests__/invoice-doc.test.mjs +195 -0
  24. package/templates/engagement/engagement-preview.mjs +100 -0
  25. package/templates/engagement/engagement-schema.md +240 -0
  26. package/templates/engagement/engagement.mjs +391 -11
  27. package/templates/engagement/pib-db-patches/pib-db-lib.mjs +10 -1
  28. package/templates/engagement/pib-db-patches/pib-db-mcp-server.mjs +5 -44
  29. package/templates/engagement/pib-db-patches/pib-db.mjs +5 -2
  30. package/templates/hooks/action-completion-gate.sh +9 -3
  31. package/templates/hooks/memory-index-guard.sh +17 -11
  32. package/templates/hooks/watchtower-session-start.sh +24 -2
  33. package/templates/mcp/pib-db.json +1 -4
  34. package/templates/mux/__tests__/mux-fail-loud.fixture.sh +44 -0
  35. package/templates/mux/__tests__/station-liveness.fixture.sh +234 -0
  36. package/templates/mux/__tests__/station-liveness.test.mjs +47 -0
  37. package/templates/mux/bin/mux +289 -56
  38. package/templates/mux/config/help.txt +1 -0
  39. package/templates/rules/acknowledge-when-corrected.md +33 -0
  40. package/templates/rules/maintainability.md +11 -0
  41. package/templates/rules/memory-capture.md +26 -4
  42. package/templates/rules/plan-before-bulk-or-irreversible-actions.md +48 -0
  43. package/templates/rules/verify-before-asserting.md +79 -0
  44. package/templates/scripts/__tests__/advisor-pass.test.mjs +238 -0
  45. package/templates/scripts/__tests__/advisories.test.mjs +262 -0
  46. package/templates/scripts/__tests__/ahead-check-origin.test.mjs +355 -0
  47. package/templates/scripts/__tests__/batch-disposition.test.mjs +235 -0
  48. package/templates/scripts/__tests__/claude-churn-authored.test.mjs +90 -0
  49. package/templates/scripts/__tests__/cross-ring-reader.test.mjs +505 -0
  50. package/templates/scripts/__tests__/dx-captures-briefing.test.mjs +169 -0
  51. package/templates/scripts/__tests__/feedback-outbox-flush.test.mjs +232 -0
  52. package/templates/scripts/__tests__/hook-runner.test.mjs +247 -0
  53. package/templates/scripts/__tests__/narrative-corpus.test.mjs +247 -0
  54. package/templates/scripts/__tests__/phase-shim.test.mjs +112 -0
  55. package/templates/scripts/__tests__/qa-handoff-gate.test.mjs +68 -0
  56. package/templates/scripts/__tests__/qa-handoff-merge-state.test.mjs +162 -0
  57. package/templates/scripts/__tests__/resolve-cli.test.mjs +314 -0
  58. package/templates/scripts/__tests__/ring-state-ownership.test.mjs +228 -3
  59. package/templates/scripts/__tests__/ring1-content-detector.test.mjs +168 -0
  60. package/templates/scripts/__tests__/ring1-flagged-actions.test.mjs +135 -0
  61. package/templates/scripts/__tests__/ring1-script-drift.test.mjs +126 -0
  62. package/templates/scripts/__tests__/ring2-queue-reader.test.mjs +80 -0
  63. package/templates/scripts/__tests__/ring2-recall-canary.test.mjs +150 -0
  64. package/templates/scripts/__tests__/ring2-roster-review.test.mjs +311 -0
  65. package/templates/scripts/__tests__/ring2-thread-context.test.mjs +189 -0
  66. package/templates/scripts/__tests__/ring3-chunk-merge.test.mjs +122 -0
  67. package/templates/scripts/__tests__/ring3-close-lenses.test.mjs +359 -0
  68. package/templates/scripts/__tests__/ring3-dedup.test.mjs +467 -0
  69. package/templates/scripts/__tests__/ring3-memory-titles.test.mjs +187 -0
  70. package/templates/scripts/__tests__/ring3-novelty-rescue.test.mjs +148 -0
  71. package/templates/scripts/__tests__/ring3-recent-slice.test.mjs +101 -0
  72. package/templates/scripts/__tests__/ring4-reconcile.test.mjs +393 -0
  73. package/templates/scripts/__tests__/routine-dispatch.test.mjs +312 -0
  74. package/templates/scripts/__tests__/shared-thread-reader.test.mjs +187 -0
  75. package/templates/scripts/__tests__/suppression-ledger.test.mjs +148 -0
  76. package/templates/scripts/__tests__/verify-backfill.test.mjs +103 -0
  77. package/templates/scripts/__tests__/verify-coverage.test.mjs +80 -0
  78. package/templates/scripts/__tests__/watchtower-snapshot.test.mjs +261 -0
  79. package/templates/scripts/__tests__/watchtower-sync.test.mjs +311 -0
  80. package/templates/scripts/load-triage-history.js +5 -2
  81. package/templates/scripts/pib-db-mcp-server.mjs +5 -44
  82. package/templates/scripts/pib-db-path.mjs +61 -0
  83. package/templates/scripts/pib-db.mjs +5 -2
  84. package/templates/scripts/validate-memory.mjs +214 -16
  85. package/templates/scripts/watchtower-advisories.mjs +309 -0
  86. package/templates/scripts/watchtower-build-context.mjs +301 -18
  87. package/templates/scripts/watchtower-cross-ring-reader.mjs +700 -0
  88. package/templates/scripts/watchtower-hook-runner.mjs +422 -0
  89. package/templates/scripts/watchtower-lib.mjs +447 -2
  90. package/templates/scripts/watchtower-narrative-corpus.mjs +385 -0
  91. package/templates/scripts/watchtower-phase-shim.mjs +171 -0
  92. package/templates/scripts/watchtower-queue.mjs +484 -1
  93. package/templates/scripts/watchtower-ring1.mjs +533 -53
  94. package/templates/scripts/watchtower-ring2.mjs +787 -48
  95. package/templates/scripts/watchtower-ring3-close.mjs +1357 -99
  96. package/templates/scripts/watchtower-ring4-runner.sh +85 -0
  97. package/templates/scripts/watchtower-ring4.mjs +753 -0
  98. package/templates/scripts/watchtower-routines.mjs +358 -0
  99. package/templates/scripts/watchtower-snapshot.mjs +452 -0
  100. package/templates/scripts/watchtower-status.sh +1 -1
  101. package/templates/scripts/watchtower-sync.mjs +393 -0
  102. package/templates/skills/audit/SKILL.md +5 -1
  103. package/templates/skills/briefing/SKILL.md +584 -236
  104. package/templates/skills/cabinet-anthropic-insider/SKILL.md +14 -6
  105. package/templates/skills/cabinet-historian/SKILL.md +14 -11
  106. package/templates/skills/cabinet-process-therapist/SKILL.md +28 -0
  107. package/templates/skills/cabinet-security/SKILL.md +11 -0
  108. package/templates/skills/cabinet-system-advocate/SKILL.md +22 -21
  109. package/templates/skills/cabinet-user-advocate/SKILL.md +13 -7
  110. package/templates/skills/catch-up/SKILL.md +113 -0
  111. package/templates/skills/cc-publish/SKILL.md +148 -25
  112. package/templates/skills/cc-remember/SKILL.md +45 -0
  113. package/templates/skills/close/SKILL.md +107 -0
  114. package/templates/skills/collab-client/SKILL.md +22 -5
  115. package/templates/skills/collab-consultant/SKILL.md +110 -2
  116. package/templates/skills/debrief/SKILL.md +148 -16
  117. package/templates/skills/debrief-classic/SKILL.md +696 -0
  118. package/templates/skills/debrief-classic/calibration.md +44 -0
  119. package/templates/skills/debrief-classic/phases/audit-pattern-capture.md +78 -0
  120. package/templates/skills/debrief-classic/phases/auto-maintenance.md +48 -0
  121. package/templates/skills/debrief-classic/phases/checklist-feedback.md +123 -0
  122. package/templates/skills/debrief-classic/phases/close-work.md +163 -0
  123. package/templates/skills/debrief-classic/phases/health-checks.md +54 -0
  124. package/templates/skills/debrief-classic/phases/inventory.md +40 -0
  125. package/templates/skills/debrief-classic/phases/loose-ends.md +52 -0
  126. package/templates/skills/debrief-classic/phases/methodology-capture.md +223 -0
  127. package/templates/skills/debrief-classic/phases/qa-handoff-sweep.md +78 -0
  128. package/templates/skills/debrief-classic/phases/record-lessons.md +177 -0
  129. package/templates/skills/debrief-classic/phases/report.md +59 -0
  130. package/templates/skills/debrief-classic/phases/update-state.md +48 -0
  131. package/templates/skills/debrief-classic/phases/upstream-feedback.md +185 -0
  132. package/templates/skills/debrief-classic/phases/verify-coverage.md +101 -0
  133. package/templates/skills/execute/SKILL.md +77 -6
  134. package/templates/skills/execute/phases/commit-and-deploy.md +8 -0
  135. package/templates/skills/execute-group/SKILL.md +23 -26
  136. package/templates/skills/generate-plan-groups/SKILL.md +20 -0
  137. package/templates/skills/inbox/SKILL.md +124 -7
  138. package/templates/skills/memory/SKILL.md +22 -6
  139. package/templates/skills/orient/SKILL.md +140 -52
  140. package/templates/skills/orient-classic/SKILL.md +770 -0
  141. package/templates/skills/orient-classic/phases/auto-maintenance.md +52 -0
  142. package/templates/skills/orient-classic/phases/briefing.md +53 -0
  143. package/templates/skills/orient-classic/phases/cabinet.md +46 -0
  144. package/templates/skills/orient-classic/phases/checklist-status.md +54 -0
  145. package/templates/skills/orient-classic/phases/context.md +88 -0
  146. package/templates/skills/orient-classic/phases/data-sync.md +35 -0
  147. package/templates/skills/orient-classic/phases/deferred-check.md +55 -0
  148. package/templates/skills/orient-classic/phases/dx-captures.md +53 -0
  149. package/templates/skills/orient-classic/phases/health-checks.md +50 -0
  150. package/templates/skills/orient-classic/phases/verify-backfill.md +109 -0
  151. package/templates/skills/orient-classic/phases/work-scan.md +69 -0
  152. package/templates/skills/plan/SKILL.md +8 -0
  153. package/templates/skills/qa-drain/SKILL.md +157 -0
  154. package/templates/skills/qa-handoff/SKILL.md +287 -46
  155. package/templates/skills/session-handoff/SKILL.md +175 -6
  156. package/templates/skills/setup-accounts/SKILL.md +38 -16
  157. package/templates/skills/threads/SKILL.md +4 -0
  158. package/templates/skills/triage-audit/SKILL.md +6 -0
  159. package/templates/skills/validate/phases/validators.md +41 -0
  160. package/templates/skills/watchtower/SKILL.md +199 -2
  161. package/templates/verify-runtime/CONVENTIONS.md +9 -0
  162. package/templates/verify-runtime/README.md +37 -0
  163. package/templates/watchtower/config.json.template +10 -2
  164. package/templates/watchtower/queue/items/item.json.schema +1 -1
  165. package/templates/workflows/execute-group-complete.js +10 -1
@@ -1,11 +1,14 @@
1
1
  ---
2
2
  name: session-handoff
3
3
  description: |
4
- The default session-close verb. Two phases: (1) run /debrief in full,
4
+ The default session-close verb. Three phases: (1) run /debrief in full,
5
5
  which sweeps and performs a /qa-handoff for every worktree→main merge
6
6
  this session still needs one for; (2) propose a seed prompt for the
7
7
  next session — you discuss/revise/approve/reject — and ONLY on approve
8
- spawn a new window seeded with it. Distinct from /qa-handoff: that
8
+ spawn a new window seeded with it; (3) a non-blocking epilogue that
9
+ in the dying tab, while the next session is already running — surfaces
10
+ this project's work threads in plain language and offers at most one
11
+ well-explained inbox decision. Distinct from /qa-handoff: that
9
12
  hands a single merge BACKWARD to the live QA station (window 1); this
10
13
  closes the session and hands FORWARD to the next session (a new
11
14
  window). Plain /debrief is for shutting down with nothing queued.
@@ -21,10 +24,12 @@ This is the **default close verb**. The verb is the signal of intent:
21
24
  plain `/debrief` means "I'm done, nothing queued."
22
25
 
23
26
  It is a **thin wrapper** over `/debrief` (the `debrief-quick` pattern: a
24
- runtime redirect, not a re-implementation). It adds exactly one original
25
- responsibility — propose-and-launch the next session and inherits
26
- everything else from debrief by reference. Do **not** re-document or
27
- paraphrase debrief's phases here; run them.
27
+ runtime redirect, not a re-implementation). It adds two original
28
+ responsibilities — propose-and-launch the next session (Phase 2) and a
29
+ non-blocking epilogue that makes thread state visible and drains one
30
+ inbox decision (Phase 3) and inherits everything else from debrief by
31
+ reference. Do **not** re-document or paraphrase debrief's phases here;
32
+ run them.
28
33
 
29
34
  ## Phase 1 — Close & sweep (mechanical, no gate)
30
35
 
@@ -132,6 +137,163 @@ fully complete before phase 2.
132
137
  4. **On reject — clean close.** Debrief already ran; nothing is spawned.
133
138
  Say so plainly and stop.
134
139
 
140
+ ## Phase 3 — Epilogue (non-blocking, skippable)
141
+
142
+ Phase 2 has resolved — a window launched, nothing was launched, or the
143
+ operator rejected. The session is closing and the operator is here only
144
+ to close the tab. That is the one moment with the session's full
145
+ context, zero blocking, and guaranteed attention; spend it on
146
+ **visibility**, not ceremony. The whole point of the watchtower thread
147
+ machinery is that long-running lines of work carry a living cursor — and
148
+ those cursors are normally invisible under the surface. The epilogue is
149
+ where they surface.
150
+
151
+ **This phase NEVER blocks the close.** The handoff already happened in
152
+ Phase 2; the epilogue runs *after* it, so there is no blocking-close
153
+ friction to recreate. If any step below fails or finds nothing, drop it
154
+ and close cleanly. Always lead with the permission line, verbatim:
155
+
156
+ > Reply to act on any of this, or just close the tab — nothing here is
157
+ > lost; everything resurfaces in /briefing.
158
+
159
+ ### 1. Setup — resolve the MAIN checkout first
160
+
161
+ The installed watchtower libraries (`scripts/watchtower-*.mjs`) live only
162
+ in the **main checkout**; a worktree — where this skill usually runs —
163
+ does not have them. Resolve the main repo root before touching anything:
164
+
165
+ ```bash
166
+ COMMON=$(git rev-parse --git-common-dir 2>/dev/null) || COMMON=""
167
+ if [ -n "$COMMON" ]; then COMMON=$(cd "$COMMON" && pwd); MAIN_ROOT=$(dirname "$COMMON"); fi
168
+ ```
169
+
170
+ `--git-common-dir` returns the main repo's `.git` from a worktree (and
171
+ `.git` from the main checkout); `cd … && pwd` makes it absolute and its
172
+ parent is the repo root — correct from either place.
173
+
174
+ - **No watchtower (expected on non-watchtower installs):** if `MAIN_ROOT`
175
+ is empty, or `$MAIN_ROOT/scripts/watchtower-lib.mjs` or
176
+ `~/.claude-cabinet/watchtower/state` does not exist, print
177
+ `Epilogue skipped (no watchtower).` and close. This is a clean
178
+ structural self-skip — report it, don't swallow it.
179
+ - **Installed but a read blows up later:** that is a real fault, not an
180
+ expected skip — surface ONE loud line naming what failed, then close.
181
+ Never let a failure masquerade as "nothing to show."
182
+
183
+ Resolve project identity (which fields to match on differs between the
184
+ two stores — getting this wrong silently returns zero rows):
185
+
186
+ ```bash
187
+ node "$MAIN_ROOT/scripts/watchtower-lib.mjs" resolve "$PWD"
188
+ ```
189
+
190
+ Prints `{ "name", "slug", "path", … }` and exits 0; exit 2 means
191
+ unresolvable — skip the epilogue loudly. **Thread files key on
192
+ `identity.slug`** (`sessions[].project` carries the slug); **the inbox
193
+ keys on `identity.name`** (queue items carry the config project name).
194
+
195
+ ### 2. Thread visibility (read-only — never write thread files)
196
+
197
+ Ring 3 owns the thread store under the disk-wins contract; the epilogue
198
+ **only reads** it. Read thread files from
199
+ `~/.claude-cabinet/watchtower/state/threads/*.json` (ignore any
200
+ `threads-test/`). Use the **`/threads` read contract** for every field
201
+ shape — `display_name` (falls back to slug), the current cursor (last
202
+ `cursor_history[]` entry's `cursor`, or a legacy lone `cursor` object),
203
+ `sessions[].project`, `status` — rather than re-deriving it here; if a
204
+ file is unparseable, skip that file.
205
+
206
+ Keep `status: active` threads whose `sessions[].project` includes
207
+ `identity.slug`. **Selection (hard caps — repeat them at point of use):**
208
+ the thread(s) this session actually moved (from debrief's session
209
+ inventory — usually 1–2), plus at most **one** other active thread by
210
+ recency. **Cap 3 total.** Ring 3 deliberately mints overlapping threads
211
+ at different zoom levels (initiative / project-area / cross-cutting) over
212
+ the same work — collapse those to one per work area; never show three
213
+ views of one thing.
214
+
215
+ Render **one plain-prose paragraph per thread** (~80–100 words, a soft
216
+ budget — readability over completeness; cryptic bullets are the failure
217
+ mode this phase exists to avoid). Each paragraph:
218
+
219
+ - the `display_name`, and a one-sentence what/why;
220
+ - **`On record (before this session):`** — `where_left_off` and
221
+ `next_steps` near-verbatim (paraphrase drift is how cursors go vague),
222
+ plus the top 1–2 `open_questions` when present (that field IS "what
223
+ hangs on it");
224
+ - **`This session:`** — one sentence on how *this* session moved the
225
+ cursor, sourced from debrief's session inventory.
226
+
227
+ The two temporal labels are load-bearing: Ring 3 processes this
228
+ session's transcript *after* close, so the on-disk cursor still
229
+ describes the world *before* this session. Without the labels it reads
230
+ as confidently wrong ("next: build X" when X was just built); with them
231
+ it reads as a progress narrative. Disk = the arc; this session = the
232
+ delta.
233
+
234
+ ### 3. Inbox drain — at most one well-explained decision
235
+
236
+ It is an **inbox** (signals awaiting triage), not a decision queue —
237
+ present it that way. Import from the resolved main checkout:
238
+
239
+ ```js
240
+ import { listPending, applyBatch, GATED_CATEGORIES, DISPATCHED_CATEGORIES }
241
+ from '<MAIN_ROOT>/scripts/watchtower-queue.mjs';
242
+ ```
243
+
244
+ Load `listPending({ project: identity.name })` and **exclude
245
+ `GATED_CATEGORIES ∪ DISPATCHED_CATEGORIES`** (import the sets — never
246
+ hand-list categories; the membership drifts the moment a category is
247
+ added). Gated/dispatched items (qa-handoff, routine) belong to window
248
+ 1's standing station — a closing session must never dispose of them
249
+ (a terminal exit on a dispatched item reaches into the station's mux
250
+ dispatch queue). If any are pending, show ONE visibility line and move
251
+ on: `N qa-handoff item(s) pending — dispatched to window 1.`
252
+
253
+ From what remains, **prefer fresh items** (filed during or near this
254
+ session) — re-surfacing of older skipped items is `/briefing`'s job, not
255
+ the epilogue's; this keeps the epilogue from becoming a static affordance
256
+ the operator learns to skim past. Then:
257
+
258
+ - If one category has **≥3 items disposable by a single judgment** (e.g.
259
+ stale knowledge-extraction drafts), present **one category-batch
260
+ decision** in full prose.
261
+ - Otherwise present at most **3 singletons**, only the **first** in full
262
+ prose; the rest as one-liners the operator can ask about.
263
+
264
+ **Hard cap: one full-prose decision per epilogue.** Every decision
265
+ carries, in plain English: what this is, why it's in front of you now,
266
+ what each choice means, and what hangs on it — a decision the operator
267
+ can make without holding system internals in their head. **Prose only —
268
+ no AskUserQuestion** (a dialog eclipses the prose it asks about, and the
269
+ decision content is exactly what the operator must read; see
270
+ `cabinet/skill-output-conventions.md`).
271
+
272
+ On the operator's reply, apply approvals with **one `applyBatch` call per
273
+ disposition group** (resolve and dismiss can't share a call) — never
274
+ per-item `resolveItem`/`dismissItem` loops:
275
+
276
+ ```js
277
+ const r = applyBatch(ids, { disposition: 'dismiss', resolution_type: '<typed>', notes: '<reason>' });
278
+ ```
279
+
280
+ `applyBatch` pre-validates the whole group and throws before writing if
281
+ anything is wrong (unknown id, a gated item slipping in) — a throw is a
282
+ **stop-and-show**, not a retry: name what it refused and stop. Anything
283
+ the operator doesn't act on stays pending — nothing is lost. The write is
284
+ safe even though the seeded session is already orienting (each item is an
285
+ atomic temp+rename; there is no shared-file race to "fix").
286
+
287
+ ### Anti-accretion invariant (frozen)
288
+
289
+ The epilogue has **exactly two sections — threads, then one decision —
290
+ and that count never grows.** Every future "the epilogue should also show
291
+ X" idea either displaces something within the existing caps or routes to
292
+ `/briefing` (the portfolio curator that owns persistent surfacing). No
293
+ new sections, ever. This is the same discipline /briefing v2 holds, for
294
+ the same reason: a close-of-session surface that accretes becomes the
295
+ noise it was built to replace.
296
+
135
297
  ## Degradation (no silent failure)
136
298
 
137
299
  - **mux absent / desk gone / `mux handoff` fails:** the seed was
@@ -141,6 +303,10 @@ fully complete before phase 2.
141
303
  the seed survives via the file. Nothing is lost.
142
304
  - **No merges / no QA handoffs to sweep:** debrief's sweep phase
143
305
  self-skips silently; phase 2 still runs.
306
+ - **Epilogue, no watchtower installed:** Phase 3 reports
307
+ `Epilogue skipped (no watchtower).` and closes — a structural
308
+ self-skip, never a swallowed error. A read that fails *with*
309
+ watchtower present surfaces one loud line instead.
144
310
 
145
311
  ## Scope boundary
146
312
 
@@ -150,6 +316,9 @@ fully complete before phase 2.
150
316
  - It spawns the window **only on approve**, as the last act of phase 2.
151
317
  - It does not run the next session's work — it hands it the seed and
152
318
  stops.
319
+ - Phase 3 makes **no mux calls** and **no thread-file writes** — it
320
+ reads watchtower state and disposes inbox items through `applyBatch`.
321
+ It never blocks the close.
153
322
 
154
323
  ## Follow-ups (tracked, not in this skill yet)
155
324
 
@@ -72,8 +72,19 @@ Apply both filters in order:
72
72
  is completed). Show a note in the overview table: "Waiting on
73
73
  [dependency item prompt]".
74
74
 
75
- The skill operates on items of any `kind` — the walkthrough UX applies
76
- to `credential`, `provide`, `confirm`, and `decide` items alike.
75
+ **Decisions are answered in `/collab-client`, not here (§5.1).** This
76
+ skill walks the client through *account setup* — items of kind
77
+ `credential`, `provide`, and `confirm`. `decide` items are now rendered
78
+ as answerable decisions in the client packet (the engine's internalized
79
+ projection), so **exclude `decide` items from this walkthrough.** If the
80
+ filtered set contains `decide` items (or the section is decide-only),
81
+ do not walk them — surface a one-line redirect:
82
+ > N decision(s) in this engagement are answered in `/collab-client`
83
+ > under "Needs your attention" — run `/collab-client updates`.
84
+
85
+ Setup-accounts records only account-setup answers
86
+ (`credential` / `provide` / `confirm`); it never records a `decide`
87
+ answer.
77
88
 
78
89
  **Empty result guard:** If the filtered item set is empty (no items
79
90
  match the section or all are gated by dependencies), tell the user and
@@ -119,17 +130,25 @@ the overview table.
119
130
 
120
131
  ### Item Selection (for lists > 5 items)
121
132
 
122
- If there are more than 5 incomplete items, present the overview table
123
- first, then ask which items the user wants to tackle this session
124
- rather than walking through all of them sequentially:
125
-
126
- Use `AskUserQuestion`:
127
- - header: "This session"
128
- - question: "Which items do you want to work on now? You can always
129
- come back for the rest."
130
- - multiSelect: true
131
- - options: one per incomplete item (label: item prompt, description:
132
- cost/time summary)
133
+ `AskUserQuestion` allows **at most 4 options per call**, so never emit
134
+ one option per item when there are many it would overflow the dialog.
135
+ For more than 5 incomplete items, present the overview table first, then
136
+ narrow in **grouped steps**, each a question with ≤4 options:
137
+
138
+ 1. **Pick a group.** Offer up to 4 groups — by `section` (when items
139
+ span sections), else by `tier` within the section (e.g.
140
+ blocks-launch, recommended, pre-ad-spend). Each option label is the
141
+ group name + its remaining count; if there are more than 4 groups,
142
+ collapse the remainder into a final "Everything else" option.
143
+ 2. **Pick items within the chosen group.** If the group has ≤4
144
+ incomplete items, ask one `multiSelect` question (one option per
145
+ item). If it has more, walk them in order, or batch into successive
146
+ ≤4-option `multiSelect` questions — never a single >4-option call.
147
+
148
+ **Long prompts:** an option `label` must stay short — summarize a long
149
+ item `prompt` to a few words (the service / account name) for the label,
150
+ and put the full prompt in the option `description` and the per-item
151
+ detail view. Never paste a multi-sentence prompt into a label.
133
152
 
134
153
  For 5 or fewer incomplete items, proceed directly to the per-item
135
154
  walkthrough without asking.
@@ -356,7 +375,10 @@ node "/absolute/path/to/engagement/capture-and-encrypt.mjs" \
356
375
  For non-credential items, record completion:
357
376
  - `kind: confirm` → `recordAnswer(state, key, { mode: 'confirmed', confirmed_at: new Date().toISOString() })`
358
377
  - `kind: provide` → `recordAnswer(state, key, { mode: 'provided', value: '<user input>', provided_at: new Date().toISOString() })`
359
- - `kind: decide` → `recordAnswer(state, key, { mode: 'decided', value: '<selected option>', decided_at: new Date().toISOString() })`
378
+
379
+ (`kind: decide` items are NOT walked or recorded here — they are answered
380
+ in `/collab-client`; see "Decisions are answered in `/collab-client`"
381
+ under Select Items.)
360
382
 
361
383
  ### Path B: "Someone else will handle this"
362
384
 
@@ -411,8 +433,8 @@ For non-credential items, record completion:
411
433
  3. If `kind: provide`: ask for the value directly.
412
434
  Record: `recordAnswer(state, key, { mode: 'provided', value: '<input>', provided_at: new Date().toISOString() })`
413
435
 
414
- 3b. If `kind: decide`: ask the user to choose from the item's `options`.
415
- Record: `recordAnswer(state, key, { mode: 'decided', value: '<selected option>', decided_at: new Date().toISOString() })`
436
+ (`kind: decide` does not occur here decisions are answered in
437
+ `/collab-client`, not in this walkthrough; see Select Items.)
416
438
 
417
439
  4. If `kind: confirm`: mark as confirmed.
418
440
  Record: `recordAnswer(state, key, { mode: 'confirmed', confirmed_at: ... })`
@@ -97,6 +97,10 @@ inferring the arc — the turning points, where it stands now, and its
97
97
  `lineage` if present (what it grew out of, what it merged into). End
98
98
  with the current cursor's open questions and next steps.
99
99
 
100
+ > This is **one work thread's** evolution. For the whole project's
101
+ > **decision causality** — what past decisions still constrain it, what
102
+ > superseded what — use the `/briefing lineage` drill-down instead.
103
+
100
104
  ### Connections mode
101
105
 
102
106
  Across all threads, surface: shared concepts (overlapping cursor
@@ -135,6 +135,12 @@ visible in the triage UI with its full context — title, description,
135
135
  assumption, evidence, question, and your commentary. The user needs to
136
136
  see everything to make good decisions.
137
137
 
138
+ **Register:** the commentary you write for each finding is operator-facing
139
+ — plain English, concept-first, framing what the finding means and what
140
+ the fix would cost (see `cabinet/skill-output-conventions.md` §9). Keep
141
+ exact paths and symbols in the finding's evidence field where they
142
+ belong; the commentary explains, it doesn't re-dump the location.
143
+
138
144
  **Fallback (no browser available):** Present findings in the conversation
139
145
  grouped by cabinet member, severity-ordered within each group. For each
140
146
  finding, show its full context (title, description, assumption, evidence,
@@ -181,6 +181,47 @@ committed ones survive, masking the problem). `README.md` indexes are
181
181
  tracked, so they don't flag. Skips silently outside a git work tree or
182
182
  when neither directory exists.
183
183
 
184
+ ### qa-handoff-verdict
185
+
186
+ ```bash
187
+ node -e '
188
+ const fs=require("fs"),os=require("os"),path=require("path");
189
+ const dir=path.join(os.homedir(),".claude-cabinet","watchtower","queue","items");
190
+ if(!fs.existsSync(dir)){console.log("no watchtower queue — skipping");process.exit(0)}
191
+ let bad=0;
192
+ for(const f of fs.readdirSync(dir)){
193
+ if(!f.endsWith(".json"))continue;
194
+ let j;try{j=JSON.parse(fs.readFileSync(path.join(dir,f),"utf8"))}catch{continue}
195
+ if(j.category==="qa-handoff"&&j.status==="resolved"){
196
+ const v=j.qa_verdict;
197
+ if(!v||typeof v!=="object"||Array.isArray(v)){
198
+ console.log("WARN bypass: "+j.id+" — resolved qa-handoff with no validated qa_verdict ("+(j.resolved_at||"").slice(0,10)+", "+(j.project||"?")+")");
199
+ bad++;
200
+ }
201
+ }
202
+ }
203
+ if(bad>0){
204
+ console.log("");
205
+ console.log(bad+" resolved qa-handoff item(s) lack a validated qa_verdict — resolved by a");
206
+ console.log("direct file write that bypassed the recipient gate (validateQaVerdict fires only");
207
+ console.log("through resolveItem). Pre-gate items are legacy; post-gate ones mean QA was");
208
+ console.log("recorded without a verdict. Review, and re-resolve through the gate if live.");
209
+ }else{
210
+ console.log("All resolved qa-handoff items carry a validated qa_verdict.");
211
+ }
212
+ '
213
+ ```
214
+
215
+ Surfaces the one hole in the staff-QA recipient gate: the gate guards
216
+ `resolveItem`, not the file. A session that writes queue-item JSON
217
+ directly can stamp a resolved `qa-handoff` with no (or an unvalidated)
218
+ `qa_verdict` — the bypass demonstrated 4× within hours of the gate
219
+ shipping. This is a **warning, not a gate** (exit 0): pre-gate handoffs
220
+ legitimately lack a verdict and can't be dated per-consumer, so failing
221
+ `/validate` on them would cry wolf. It still names every offender so a
222
+ post-gate bypass is visible. Skips silently when no watchtower queue
223
+ exists.
224
+
184
225
  ## Example Validators (commented — enable for your project)
185
226
 
186
227
  <!--
@@ -6,7 +6,7 @@ description: >
6
6
  queue depth), configure (add/remove projects, adjust frequencies).
7
7
  Use when: "/watchtower", "watchtower status", "install watchtower",
8
8
  "configure watchtower".
9
- argument-hint: "install | status | configure"
9
+ argument-hint: "install | status | configure | sync"
10
10
  ---
11
11
 
12
12
  # /watchtower
@@ -21,6 +21,7 @@ Parse `$ARGUMENTS` to determine subcommand:
21
21
  |-------|------------|
22
22
  | (empty), "status" | **status** |
23
23
  | "install" | **install** |
24
+ | "sync" | **sync** |
24
25
  | "configure" | **configure** |
25
26
  | "uninstall" | **uninstall** |
26
27
 
@@ -30,6 +31,26 @@ Parse `$ARGUMENTS` to determine subcommand:
30
31
 
31
32
  Set up the Watchtower daemon and register projects.
32
33
 
34
+ > **Reinstall keeps the runtime fresh automatically.** Once a runtime
35
+ > exists at `~/.claude-cabinet/watchtower/`, every `create-claude-cabinet`
36
+ > reinstall content-hash-refreshes the runtime scripts, hooks, and cabinet
37
+ > docs (changed-or-new files only — including a brand-new `watchtower-*.mjs`
38
+ > that wasn't in the runtime yet) via `lib/watchtower-setup.js`. The refresh
39
+ > is **content-aware**: it compares each template against the runtime file's
40
+ > ACTUAL on-disk bytes (not a cached manifest), so a hand-edited or stale
41
+ > runtime file self-heals back to the template on reinstall — it can't be
42
+ > silently skipped. No hand-copy of `templates/scripts/watchtower-*.mjs` is
43
+ > needed after a template edit. This `install` step still owns the ONE-TIME
44
+ > fresh setup below that the refresh never touches: directory tree creation,
45
+ > `config.json`, the launchd/systemd daemons, the project-key migration, and
46
+ > the coherence assertion. (The refresh is refresh-only — it no-ops when no
47
+ > runtime exists yet.)
48
+ >
49
+ > **For a single-file fix between full reinstalls, use `/watchtower sync`**
50
+ > (below) — it hash-diffs the three script tiers (template → tracked → live
51
+ > runtime) and heals the divergence without re-running daemon registration.
52
+ > `/watchtower status` reports the same diff read-only.
53
+
33
54
  ### Steps
34
55
 
35
56
  1. **Create directory tree**
@@ -45,6 +66,8 @@ Set up the Watchtower daemon and register projects.
45
66
  watchtower-ring3-close.mjs
46
67
  watchtower-build-context.mjs
47
68
  watchtower-migrate-keys.mjs
69
+ watchtower-hook-runner.mjs
70
+ watchtower-phase-shim.mjs
48
71
  hooks/
49
72
  watchtower-session-end.sh
50
73
  state/
@@ -66,13 +89,18 @@ Set up the Watchtower daemon and register projects.
66
89
  - `watchtower-ring2-runner.sh`
67
90
  - `watchtower-validate.mjs`
68
91
  - `watchtower-queue.mjs` (if present — ships with Plan 2)
92
+ - `watchtower-routines.mjs` (routine dispatch engine)
69
93
  - `watchtower-ring3-close.mjs`
70
94
  - `watchtower-build-context.mjs` (REQUIRED by the SessionStart hook)
71
95
  - `watchtower-migrate-keys.mjs` (project-key migration tool)
96
+ - `watchtower-hook-runner.mjs` (consumer hook runner — Plan 9)
97
+ - `watchtower-phase-shim.mjs` (consumer phase-migration adapter — Plan 9)
72
98
 
73
99
  After copying, assert the set is coherent before any ring runs:
74
100
  ```bash
75
- node -e "import(process.env.HOME + '/.claude-cabinet/watchtower/scripts/watchtower-lib.mjs').then(m => process.exit(m.resolveProjectIdentity && m.updateThreadFile && m.preserveRing3LastSession ? 0 : 1))"
101
+ node -e "import(process.env.HOME + '/.claude-cabinet/watchtower/scripts/watchtower-lib.mjs').then(m => process.exit(m.resolveProjectIdentity && m.updateThreadFile && m.preserveRing3LastSession && m.writeProjectStatePreservingRing3 && m.flushFeedbackOutbox ? 0 : 1))"
102
+ node -e "import(process.env.HOME + '/.claude-cabinet/watchtower/scripts/watchtower-routines.mjs').then(m => process.exit(m.runRoutinePass && m.triggerDue && m.validateRoutine ? 0 : 1))"
103
+ node -e "import(process.env.HOME + '/.claude-cabinet/watchtower/scripts/watchtower-hook-runner.mjs').then(m => process.exit(m.runSeam && m.ensureRegistered && m.discoverHooks ? 0 : 1))"
76
104
  ```
77
105
 
78
106
  2b. **One-time project-key migration** (upgrades from pre-resolver versions): older Ring 3 builds filed inbox items, session dirs, and thread memberships under worktree-basename phantom keys. Run the migration after copying scripts — dry-run first, review the per-key counts, then apply:
@@ -92,6 +120,17 @@ Set up the Watchtower daemon and register projects.
92
120
  - Ask user which projects to track (default: all registered)
93
121
  - Set default ring frequencies (ring1: 300s)
94
122
 
123
+ 4b. **Register the consumer hook runner** (Plan 9 — wires the ring seams to
124
+ the runner without editing the rings). Idempotent and append-only: it adds
125
+ the runner command to each of the three `config.hooks` seam arrays only if
126
+ absent, preserving any raw consumer commands already there.
127
+ ```bash
128
+ node ~/.claude-cabinet/watchtower/scripts/watchtower-hook-runner.mjs --register
129
+ ```
130
+ After this, a consumer activates project-specific background behavior by
131
+ dropping executable scripts into `<project>/.claude/watchtower/hooks/<seam>/`
132
+ — no further config editing. See "Consumer hooks" below.
133
+
95
134
  5. **Install platform daemons**:
96
135
  - **macOS**: Copy plist files to `~/Library/LaunchAgents/`, then `launchctl load` each:
97
136
  - `com.claude-cabinet.watchtower-ring1.plist`
@@ -133,6 +172,68 @@ Watchtower installed.
133
172
 
134
173
  ---
135
174
 
175
+ ## Subcommand: sync
176
+
177
+ Propagate a watchtower script/hook/doc fix across the three tiers — template
178
+ (`templates/`), tracked (the project's committed `scripts/` + `.claude/`), and
179
+ the live runtime (`~/.claude-cabinet/watchtower/`) — WITHOUT re-running daemon
180
+ registration, `config.json`, or project re-registration. This is the surgical
181
+ path the maintainability rules ask for: a one-file fix no longer needs a full
182
+ `create-claude-cabinet` reinstall or a `/watchtower install` re-run, and no
183
+ hand-copy to three locations.
184
+
185
+ `watchtower-sync.mjs` reads the ACTUAL on-disk bytes of every tier (never a
186
+ cached manifest, which can itself go stale), classifies each watchtower file,
187
+ and propagates the authoritative tier downward. Authority is by contract:
188
+ **template wins when present, else the tracked copy; the runtime is always a
189
+ heal target and never a source, and the template tier is never written.** The
190
+ managed file set is DISCOVERED by scanning the tiers, so the sync tool cannot
191
+ itself accumulate a stale hardcoded list.
192
+
193
+ ### Steps
194
+
195
+ 1. **Preview the divergence** (read-only):
196
+ ```bash
197
+ node ~/.claude-cabinet/watchtower/scripts/watchtower-sync.mjs --check
198
+ ```
199
+ Show the operator the per-file report (drift / missing / orphan) before
200
+ touching anything.
201
+
202
+ 2. **Heal** — preview the writes first, then apply:
203
+ ```bash
204
+ node ~/.claude-cabinet/watchtower/scripts/watchtower-sync.mjs --heal --dry-run
205
+ node ~/.claude-cabinet/watchtower/scripts/watchtower-sync.mjs --heal
206
+ ```
207
+
208
+ 3. **Commit any TRACKED-tier heals.** The report flags when tracked-tier files
209
+ changed — those are committed project copies, so an uncommitted heal reverts
210
+ on the next clean checkout. The runtime tier heals in place (regenerated
211
+ infra — no commit needed).
212
+
213
+ 4. **Orphans are reported, never auto-deleted.** A script that lingers only in
214
+ the runtime (removed upstream) is surfaced for deliberate manual removal.
215
+
216
+ 5. **No daemon restart** is needed for a pure script/doc refresh — the rings
217
+ re-read their scripts on the next cron tick. Restart a ring only if you
218
+ changed its runner or cadence (see configure).
219
+
220
+ If the runtime does not exist yet (`watchtower-sync.mjs` not found under
221
+ `~/.claude-cabinet/watchtower/scripts/`), there is nothing to sync to — run
222
+ `/watchtower install` first.
223
+
224
+ ### Output
225
+
226
+ ```
227
+ Watchtower sync
228
+ template: <path or (absent)>
229
+ tracked: <path or (absent)>
230
+ runtime: <path>
231
+ <N>/<total> in sync · <N> drift · <N> missing · <N> orphan
232
+ healed: <list, or "nothing to heal">
233
+ ```
234
+
235
+ ---
236
+
136
237
  ## Subcommand: status
137
238
 
138
239
  Show Watchtower health and current state.
@@ -168,6 +269,24 @@ Show Watchtower health and current state.
168
269
 
169
270
  6. **Read config for project list**
170
271
 
272
+ 7. **Read frontier-watchdog state**: read `~/.claude/cc-registry.json`. If
273
+ `frontierModel` is a non-empty string, report the designated key. If it
274
+ is absent or empty, report the watchdog is inert (a never-configured
275
+ watchdog must not read the same as a passing one — act:77f7b7b2).
276
+
277
+ 8. **Script-tier sync check** (read-only): hash-diff the three watchtower
278
+ script tiers — template → tracked → live runtime — so a missed copy
279
+ surfaces here instead of as a silently-not-running fix. Run from the
280
+ project root (cwd resolves the tracked tier; template is detected only in
281
+ the CC source repo):
282
+ ```bash
283
+ node ~/.claude-cabinet/watchtower/scripts/watchtower-sync.mjs --check
284
+ ```
285
+ Exit 0 = all tiers in sync; exit 2 = drift/missing/orphan detected. On
286
+ drift, report the count and point the operator at `/watchtower sync` to
287
+ heal it. This step is read-only — it never writes. If the runtime script is
288
+ absent (no runtime yet), skip and note `/watchtower install` is needed.
289
+
171
290
  ### Output
172
291
 
173
292
  ```
@@ -179,6 +298,8 @@ Watchtower Status
179
298
  Inbox: <N> pending items (oldest: <age>)
180
299
  Summary: updated <time> ago
181
300
  Memory: <pass/violations> (see state/memory-health.md)
301
+ Frontier: <designated key> — OR — none designated; watchdog inert (set: npx create-claude-cabinet --frontier-model <value>)
302
+ Scripts: in sync across tiers — OR — <N> drifted/missing (run /watchtower sync to heal)
182
303
  Projects:
183
304
  <name>: state updated <time> ago
184
305
  ...
@@ -209,6 +330,8 @@ Interactively add/remove projects, adjust frequencies, toggle defaults.
209
330
  - Change Ring 2 slow frequency (default 1800s)
210
331
  - Toggle Ring 3 features (session_auto_naming, etc.)
211
332
  - Toggle defaults (decision_routing, stale_work_detection, etc.)
333
+ - Manage routines (declare/edit/remove a project's interactive
334
+ routines — see "Routines" below)
212
335
  - View current configuration
213
336
 
214
337
  3. **Write updated config.json** atomically
@@ -221,6 +344,80 @@ Interactively add/remove projects, adjust frequencies, toggle defaults.
221
344
 
222
345
  Show the updated configuration summary.
223
346
 
347
+ ### Routines
348
+
349
+ Declared interactive routines are the push mechanism for a project's
350
+ recurring conversations (morning briefing, command-queue processing,
351
+ articulation sweeps): watchtower watches the trigger and dispatches the
352
+ routine to the desk's main session the way QA handoffs are dispatched —
353
+ nothing depends on the operator remembering a verb. The declaration
354
+ schema, trigger types, and ownership contract live in
355
+ `watchtower-contracts.md` ("Routine Dispatch"). When declaring one:
356
+
357
+ 1. Ask for name (kebab-case), trigger (time-of-day / interval /
358
+ path-nonempty / session-close + its parameter), and the script path.
359
+ 2. The script is a project-authored phase file (e.g.
360
+ `.claude/routines/<name>.md`) holding the conversation script. If it
361
+ does not exist yet, offer to draft it with the user — a routine
362
+ pointing at a missing script dispatches a prompt that immediately
363
+ dead-ends.
364
+ 3. Validate against `validateRoutine` before writing:
365
+ ```bash
366
+ node -e "import(process.env.HOME + '/.claude-cabinet/watchtower/scripts/watchtower-routines.mjs').then(m => { const e = m.validateRoutine(JSON.parse(process.argv[1])); if (e.length) { console.error(e.join('; ')); process.exit(1); } })" '<routine-json>'
367
+ ```
368
+ 4. Write the routine into `config.json` under
369
+ `projects.<name>.routines` (atomic write).
370
+
371
+ Routine firings appear as `routine` inbox items and in the desk's ·N
372
+ badge; they resolve normally (`resolution_type: 'acted-on'`) — no QA
373
+ gate, normal expiry. `defaults.routine_dispatch: false` turns the whole
374
+ mechanism off.
375
+
376
+ ### Consumer hooks (non-interactive customs)
377
+
378
+ Routines are the INTERACTIVE extension model (they talk to the operator).
379
+ Consumer hooks are the NON-INTERACTIVE counterpart: unattended scripts that
380
+ run in the background at the ring lifecycle seams — Flow's Railway DB pull and
381
+ machine-drift check, article-rewriter's reMarkable sync and Sentry check,
382
+ maginnis's timelog collection. They never prompt; anything that needs the
383
+ operator is a routine, not a hook.
384
+
385
+ A consumer adds a hook by dropping an executable script into the project:
386
+
387
+ ```
388
+ <project-root>/.claude/watchtower/hooks/<seam>/<NN>-<name>.{sh,mjs,js,py}
389
+ ```
390
+
391
+ The three seams, and what each hook receives on stdin:
392
+
393
+ | Seam | Fires | stdin state |
394
+ |------|-------|-------------|
395
+ | `ring1-post-collect` | Ring 1, per project, after state collection (every ~5m) | the project's collected state (`name`, `path`, `git`, `pib`, …) |
396
+ | `ring2-slow-post` | Ring 2 slow tier, portfolio-wide (every ~30m) | the slow-tier state |
397
+ | `ring3-close-post` | Ring 3, at each session close | `{ session_id, project, project_path, summary, stats }` |
398
+
399
+ `watchtower-hook-runner.mjs` (registered once per seam by install step 4b)
400
+ discovers the scripts in name order, runs each in the project root with the
401
+ ring state on stdin and `WATCHTOWER_SEAM` / `WATCHTOWER_PROJECT` /
402
+ `WATCHTOWER_PROJECT_PATH` / `WATCHTOWER_SESSION_ID` exported, isolates each
403
+ failure, and budgets the batch to finish inside the ring's timeout. A hook
404
+ that prints `{"additional_checks":[…]}` has those surfaced to Ring 1. Files
405
+ prefixed `_` or `.` are skipped (the disable convention). Full I/O contract:
406
+ `watchtower-contracts.md` → "Consumer Hook Contract (Plan 9)".
407
+
408
+ **Migrating an existing script** (the orient/debrief phase → ring hook path):
409
+ if the logic already lives in a repo script that expects the project root as
410
+ cwd rather than reading stdin, register it through the shim instead of moving
411
+ it — one line in `config.hooks[<seam>]`:
412
+
413
+ ```
414
+ node ~/.claude-cabinet/watchtower/scripts/watchtower-phase-shim.mjs --phase scripts/<existing>.sh
415
+ ```
416
+
417
+ The shim runs it with the same cwd/env/stdin normalization, so the port is
418
+ mechanical. New hooks should prefer the drop-in directory; the shim is for
419
+ scripts that already exist.
420
+
224
421
  ---
225
422
 
226
423
  ## Subcommand: uninstall