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.
- package/README.md +6 -10
- package/lib/cli.js +374 -91
- package/lib/copy.js +108 -6
- package/lib/db-setup.js +122 -17
- package/lib/engagement-server-setup.js +34 -9
- package/lib/metadata.js +51 -2
- package/lib/migrate-from-omega.js +13 -1
- package/lib/mux-setup.js +33 -9
- package/lib/settings-merge.js +52 -10
- package/lib/watchtower-setup.js +230 -0
- package/package.json +5 -1
- package/templates/cabinet/_cabinet-member-template.md +8 -3
- package/templates/cabinet/advisories-state-schema.md +34 -7
- package/templates/cabinet/checklist-stats-schema.md +15 -1
- package/templates/cabinet/composition-patterns.md +4 -3
- package/templates/cabinet/memory-lifecycle-contract.md +135 -0
- package/templates/cabinet/pib-db-access.md +13 -0
- package/templates/cabinet/skill-output-conventions.md +35 -1
- package/templates/cabinet/watchtower-contracts.md +503 -1
- package/templates/cabinet/worktree-invocation-contract.md +87 -0
- package/templates/engagement/OVERVIEW.md +12 -0
- package/templates/engagement/__tests__/engagement.test.mjs +177 -2
- package/templates/engagement/__tests__/invoice-doc.test.mjs +195 -0
- package/templates/engagement/engagement-preview.mjs +100 -0
- package/templates/engagement/engagement-schema.md +240 -0
- package/templates/engagement/engagement.mjs +391 -11
- package/templates/engagement/pib-db-patches/pib-db-lib.mjs +10 -1
- package/templates/engagement/pib-db-patches/pib-db-mcp-server.mjs +5 -44
- package/templates/engagement/pib-db-patches/pib-db.mjs +5 -2
- package/templates/hooks/action-completion-gate.sh +9 -3
- package/templates/hooks/memory-index-guard.sh +17 -11
- package/templates/hooks/watchtower-session-start.sh +24 -2
- package/templates/mcp/pib-db.json +1 -4
- package/templates/mux/__tests__/mux-fail-loud.fixture.sh +44 -0
- package/templates/mux/__tests__/station-liveness.fixture.sh +234 -0
- package/templates/mux/__tests__/station-liveness.test.mjs +47 -0
- package/templates/mux/bin/mux +289 -56
- package/templates/mux/config/help.txt +1 -0
- package/templates/rules/acknowledge-when-corrected.md +33 -0
- package/templates/rules/maintainability.md +11 -0
- package/templates/rules/memory-capture.md +26 -4
- package/templates/rules/plan-before-bulk-or-irreversible-actions.md +48 -0
- package/templates/rules/verify-before-asserting.md +79 -0
- package/templates/scripts/__tests__/advisor-pass.test.mjs +238 -0
- package/templates/scripts/__tests__/advisories.test.mjs +262 -0
- package/templates/scripts/__tests__/ahead-check-origin.test.mjs +355 -0
- package/templates/scripts/__tests__/batch-disposition.test.mjs +235 -0
- package/templates/scripts/__tests__/claude-churn-authored.test.mjs +90 -0
- package/templates/scripts/__tests__/cross-ring-reader.test.mjs +505 -0
- package/templates/scripts/__tests__/dx-captures-briefing.test.mjs +169 -0
- package/templates/scripts/__tests__/feedback-outbox-flush.test.mjs +232 -0
- package/templates/scripts/__tests__/hook-runner.test.mjs +247 -0
- package/templates/scripts/__tests__/narrative-corpus.test.mjs +247 -0
- package/templates/scripts/__tests__/phase-shim.test.mjs +112 -0
- package/templates/scripts/__tests__/qa-handoff-gate.test.mjs +68 -0
- package/templates/scripts/__tests__/qa-handoff-merge-state.test.mjs +162 -0
- package/templates/scripts/__tests__/resolve-cli.test.mjs +314 -0
- package/templates/scripts/__tests__/ring-state-ownership.test.mjs +228 -3
- package/templates/scripts/__tests__/ring1-content-detector.test.mjs +168 -0
- package/templates/scripts/__tests__/ring1-flagged-actions.test.mjs +135 -0
- package/templates/scripts/__tests__/ring1-script-drift.test.mjs +126 -0
- package/templates/scripts/__tests__/ring2-queue-reader.test.mjs +80 -0
- package/templates/scripts/__tests__/ring2-recall-canary.test.mjs +150 -0
- package/templates/scripts/__tests__/ring2-roster-review.test.mjs +311 -0
- package/templates/scripts/__tests__/ring2-thread-context.test.mjs +189 -0
- package/templates/scripts/__tests__/ring3-chunk-merge.test.mjs +122 -0
- package/templates/scripts/__tests__/ring3-close-lenses.test.mjs +359 -0
- package/templates/scripts/__tests__/ring3-dedup.test.mjs +467 -0
- package/templates/scripts/__tests__/ring3-memory-titles.test.mjs +187 -0
- package/templates/scripts/__tests__/ring3-novelty-rescue.test.mjs +148 -0
- package/templates/scripts/__tests__/ring3-recent-slice.test.mjs +101 -0
- package/templates/scripts/__tests__/ring4-reconcile.test.mjs +393 -0
- package/templates/scripts/__tests__/routine-dispatch.test.mjs +312 -0
- package/templates/scripts/__tests__/shared-thread-reader.test.mjs +187 -0
- package/templates/scripts/__tests__/suppression-ledger.test.mjs +148 -0
- package/templates/scripts/__tests__/verify-backfill.test.mjs +103 -0
- package/templates/scripts/__tests__/verify-coverage.test.mjs +80 -0
- package/templates/scripts/__tests__/watchtower-snapshot.test.mjs +261 -0
- package/templates/scripts/__tests__/watchtower-sync.test.mjs +311 -0
- package/templates/scripts/load-triage-history.js +5 -2
- package/templates/scripts/pib-db-mcp-server.mjs +5 -44
- package/templates/scripts/pib-db-path.mjs +61 -0
- package/templates/scripts/pib-db.mjs +5 -2
- package/templates/scripts/validate-memory.mjs +214 -16
- package/templates/scripts/watchtower-advisories.mjs +309 -0
- package/templates/scripts/watchtower-build-context.mjs +301 -18
- package/templates/scripts/watchtower-cross-ring-reader.mjs +700 -0
- package/templates/scripts/watchtower-hook-runner.mjs +422 -0
- package/templates/scripts/watchtower-lib.mjs +447 -2
- package/templates/scripts/watchtower-narrative-corpus.mjs +385 -0
- package/templates/scripts/watchtower-phase-shim.mjs +171 -0
- package/templates/scripts/watchtower-queue.mjs +484 -1
- package/templates/scripts/watchtower-ring1.mjs +533 -53
- package/templates/scripts/watchtower-ring2.mjs +787 -48
- package/templates/scripts/watchtower-ring3-close.mjs +1357 -99
- package/templates/scripts/watchtower-ring4-runner.sh +85 -0
- package/templates/scripts/watchtower-ring4.mjs +753 -0
- package/templates/scripts/watchtower-routines.mjs +358 -0
- package/templates/scripts/watchtower-snapshot.mjs +452 -0
- package/templates/scripts/watchtower-status.sh +1 -1
- package/templates/scripts/watchtower-sync.mjs +393 -0
- package/templates/skills/audit/SKILL.md +5 -1
- package/templates/skills/briefing/SKILL.md +584 -236
- package/templates/skills/cabinet-anthropic-insider/SKILL.md +14 -6
- package/templates/skills/cabinet-historian/SKILL.md +14 -11
- package/templates/skills/cabinet-process-therapist/SKILL.md +28 -0
- package/templates/skills/cabinet-security/SKILL.md +11 -0
- package/templates/skills/cabinet-system-advocate/SKILL.md +22 -21
- package/templates/skills/cabinet-user-advocate/SKILL.md +13 -7
- package/templates/skills/catch-up/SKILL.md +113 -0
- package/templates/skills/cc-publish/SKILL.md +148 -25
- package/templates/skills/cc-remember/SKILL.md +45 -0
- package/templates/skills/close/SKILL.md +107 -0
- package/templates/skills/collab-client/SKILL.md +22 -5
- package/templates/skills/collab-consultant/SKILL.md +110 -2
- package/templates/skills/debrief/SKILL.md +148 -16
- package/templates/skills/debrief-classic/SKILL.md +696 -0
- package/templates/skills/debrief-classic/calibration.md +44 -0
- package/templates/skills/debrief-classic/phases/audit-pattern-capture.md +78 -0
- package/templates/skills/debrief-classic/phases/auto-maintenance.md +48 -0
- package/templates/skills/debrief-classic/phases/checklist-feedback.md +123 -0
- package/templates/skills/debrief-classic/phases/close-work.md +163 -0
- package/templates/skills/debrief-classic/phases/health-checks.md +54 -0
- package/templates/skills/debrief-classic/phases/inventory.md +40 -0
- package/templates/skills/debrief-classic/phases/loose-ends.md +52 -0
- package/templates/skills/debrief-classic/phases/methodology-capture.md +223 -0
- package/templates/skills/debrief-classic/phases/qa-handoff-sweep.md +78 -0
- package/templates/skills/debrief-classic/phases/record-lessons.md +177 -0
- package/templates/skills/debrief-classic/phases/report.md +59 -0
- package/templates/skills/debrief-classic/phases/update-state.md +48 -0
- package/templates/skills/debrief-classic/phases/upstream-feedback.md +185 -0
- package/templates/skills/debrief-classic/phases/verify-coverage.md +101 -0
- package/templates/skills/execute/SKILL.md +77 -6
- package/templates/skills/execute/phases/commit-and-deploy.md +8 -0
- package/templates/skills/execute-group/SKILL.md +23 -26
- package/templates/skills/generate-plan-groups/SKILL.md +20 -0
- package/templates/skills/inbox/SKILL.md +124 -7
- package/templates/skills/memory/SKILL.md +22 -6
- package/templates/skills/orient/SKILL.md +140 -52
- package/templates/skills/orient-classic/SKILL.md +770 -0
- package/templates/skills/orient-classic/phases/auto-maintenance.md +52 -0
- package/templates/skills/orient-classic/phases/briefing.md +53 -0
- package/templates/skills/orient-classic/phases/cabinet.md +46 -0
- package/templates/skills/orient-classic/phases/checklist-status.md +54 -0
- package/templates/skills/orient-classic/phases/context.md +88 -0
- package/templates/skills/orient-classic/phases/data-sync.md +35 -0
- package/templates/skills/orient-classic/phases/deferred-check.md +55 -0
- package/templates/skills/orient-classic/phases/dx-captures.md +53 -0
- package/templates/skills/orient-classic/phases/health-checks.md +50 -0
- package/templates/skills/orient-classic/phases/verify-backfill.md +109 -0
- package/templates/skills/orient-classic/phases/work-scan.md +69 -0
- package/templates/skills/plan/SKILL.md +8 -0
- package/templates/skills/qa-drain/SKILL.md +157 -0
- package/templates/skills/qa-handoff/SKILL.md +287 -46
- package/templates/skills/session-handoff/SKILL.md +175 -6
- package/templates/skills/setup-accounts/SKILL.md +38 -16
- package/templates/skills/threads/SKILL.md +4 -0
- package/templates/skills/triage-audit/SKILL.md +6 -0
- package/templates/skills/validate/phases/validators.md +41 -0
- package/templates/skills/watchtower/SKILL.md +199 -2
- package/templates/verify-runtime/CONVENTIONS.md +9 -0
- package/templates/verify-runtime/README.md +37 -0
- package/templates/watchtower/config.json.template +10 -2
- package/templates/watchtower/queue/items/item.json.schema +1 -1
- 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.
|
|
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
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
415
|
-
|
|
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
|