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,31 +1,83 @@
1
1
  ---
2
2
  name: briefing
3
3
  description: |
4
- CEO-style briefing. Your cabinet walks you through the landscape
5
- what watchtower did, what changed across the portfolio, what deserves
6
- attention. Progressive disclosure: top-level summaries, drill into
7
- anything. Also handles /recap (factual-only mode).
4
+ The one daily surface. A portfolio-wide curator: walks the landscape
5
+ project by project plain-English state, what deserves attention,
6
+ and the project's pending inbox items batched into proposed
7
+ dispositions you approve in-conversation. Decisions are chunked by
8
+ project, never interleaved. Also handles /recap (factual-only mode)
9
+ and a per-project decision-lineage drill-down ("why is this project
10
+ the way it is — what still constrains it").
8
11
  Use when: "briefing", "brief me", "/briefing", "what's the landscape",
9
- "recap", "/recap", "what happened", "what did I do yesterday".
10
- argument-hint: "scope e.g., 'today', 'this week', 'maginnis only', 'recap'"
12
+ "recap", "/recap", "what happened", "what did I do yesterday",
13
+ "decision lineage", "why is this the way it is".
14
+ argument-hint: "scope — e.g., 'today', 'this week', 'maginnis only', 'recap', 'lineage'"
11
15
  ---
12
16
 
13
- # /briefing — Portfolio Briefing
17
+ # /briefing — The Daily Surface
18
+
19
+ One run, from any desk, covers the whole portfolio. The briefing is a
20
+ **curator**, not a dashboard: it reads everything watchtower knows,
21
+ selects what actually matters, narrates it in plain English, and lets
22
+ the operator disposition pending inbox items in bulk without leaving
23
+ the conversation. The success test: the operator runs this daily
24
+ because it is pleasant, and the inbox drains through batch decisions
25
+ here instead of item-by-item `/inbox` walks.
26
+
27
+ Tone contract for every section below — **non-negotiable, every run**:
28
+ plain English, conversational, concept-first. No insider jargon, no
29
+ fid/jargon walls. Say "the background system files one note when the
30
+ same mistake keeps happening" — not "Ring 2 emitPatternPromotion
31
+ dedups by failure class." Name the thing in words the operator would
32
+ use, then add the id only as a trailing reference. Item ids and `act:`
33
+ fids appear on request or in disposition confirmations — never as the
34
+ narrative spine. Lead with what matters; counts and mechanics trail.
35
+ This recurred live (2026-06-14): a briefing slipped into "Ring 2
36
+ meta-critiques / generator leak" and the operator could not act on it
37
+ until it was re-said plainly. If you catch yourself writing a term a
38
+ non-author wouldn't recognize, rewrite the sentence. See also the
39
+ decision/context structure rule under Step 3c.
40
+
41
+ ## The Anti-Accretion Invariant (structural rule)
42
+
43
+ Every "the briefing should show X" duty — present and future — routes
44
+ through the curator's top-N selection inside the owning project's
45
+ chunk. **No duty may append its own always-rendered section.** This is
46
+ how the briefing stays a pleasure for years: new signal classes (QA
47
+ filed gaps, advisor findings, hygiene nudges, DX captures, verify
48
+ warnings, whatever ships next) become candidates the curator weighs,
49
+ not permanent real estate. A duty that earns attention today may
50
+ earn one line tomorrow and silence next week. When editing this skill,
51
+ reject any change that adds an unconditional section — give the new
52
+ signal a candidate class in Step 3b instead.
53
+
54
+ Two narrow floors temper curation — both are "never silently dropped"
55
+ rules, not sections:
56
+
57
+ 1. **Gated items are never curated out.** A pending item in a gated
58
+ category (`GATED_CATEGORIES` in `scripts/watchtower-queue.mjs`;
59
+ today: `qa-handoff`) always appears in its project's chunk. The
60
+ queue lib already guarantees these cannot leave the queue silently;
61
+ the briefing must not hide what the gate keeps alive.
62
+ 2. **Open QA filed-gaps get at least a one-line roll-up** in the
63
+ cross-portfolio close (Step 4) while any remain open. "Filed" must
64
+ not become "forgotten" — the qa-handoff contract promises this
65
+ re-surfacing.
14
66
 
15
67
  ## Mode Detection
16
68
 
17
- Determine the briefing mode from how the skill was invoked:
18
-
19
69
  - If `$ARGUMENTS` contains the word "recap", or the skill was invoked
20
- as `/recap`: **Factual mode.** No drill-down, no cabinet members.
21
- Deliver the summary and stop.
22
- - Otherwise: **Full mode.** Summary followed by progressive disclosure
23
- and optional cabinet lens.
70
+ as `/recap`: **Factual mode.** Deliver Step 2's summary, write the
71
+ timestamp (Step 5), and stop. No walk, no dispositions, no cabinet.
72
+ - If `$ARGUMENTS` contains "lineage", "why is", or "decision history":
73
+ **Lineage mode.** Render THIS desk's project decision lineage (see
74
+ "Lineage Mode" below), then stop. No walk, no dispositions, no cabinet.
75
+ - Otherwise: **Full mode.** Summary, then the project walk with batch
76
+ dispositions, then the cross-portfolio close.
24
77
 
25
78
  ## Scope Detection
26
79
 
27
- Parse the arguments (bound in Mode Detection above) to determine the
28
- time window and project filter:
80
+ Parse the arguments to determine the time window and project filter:
29
81
 
30
82
  | Input | Scope |
31
83
  |-------|-------|
@@ -39,214 +91,498 @@ time window and project filter:
39
91
 
40
92
  When a project name is provided, match against project directory names
41
93
  in `state/projects/`. Use fuzzy prefix matching — "maginnis" matches
42
- "maginnis-howard". If multiple projects match, list them and ask the
43
- user to clarify.
94
+ "maginnis-howard". If multiple projects match, list them and ask.
95
+ The time window scopes the NARRATIVE (sessions, threads, what
96
+ happened); pending inbox items are always loaded in full — old pending
97
+ items are exactly what batch dispositions exist to drain.
98
+
99
+ ## Lineage Mode: Decision-Lineage Drill-Down (act:a2efc0ce)
100
+
101
+ On-demand, read-only. Answers "why is THIS desk's project the way it is —
102
+ what past decisions still constrain it, and what's safely reversible?"
103
+ This is a **projection** over existing stores (the inbox, threads, memory
104
+ `decision_*.md`, `.claude/methodology/`, pib-db), never a new store, and
105
+ it **writes nothing**. It is current-project-scoped in Stage 1: it reads
106
+ the project the current desk's cwd belongs to (worktree-aware). If the
107
+ operator named a *different* project, say plainly that lineage is
108
+ per-desk for now — run it from that project's desk — then either run it
109
+ for the current project or stop. (Cross-project lineage is deferred.)
110
+
111
+ Distinct from `/threads arc <slug>`, which tells **one work thread's**
112
+ evolution. Lineage tells the **whole project's decision causality**.
113
+
114
+ **L1 — corpus (inbox + threads).** Run the read-only helper (it lives
115
+ beside the queue lib, in the project-root `scripts/` dir):
116
+ ```bash
117
+ node scripts/watchtower-narrative-corpus.mjs --corpus --project-path "$(pwd)"
118
+ ```
119
+ It returns `project_identity`, `inbox_decisions` (knowledge-extraction
120
+ items, captured-to-memory duplicates already dropped, `superseded`
121
+ included), `thread_cursors` (full `cursor_history` timelines),
122
+ `per_store_counts`, and `skipped_unresolved`. If `reason` is
123
+ `project-unresolved`, say the desk isn't a watchtower project and stop.
124
+
125
+ **L2 — memory decisions + recorded edges.** Resolve the project's
126
+ built-in memory dir the way orient does (prefer
127
+ `node -e "console.log(require('./scripts/project-context.cjs').resolveMemoryDir())"`
128
+ when that script exists; else `~/.claude/projects/<dashified-cwd>/memory`),
129
+ then have the SAME helper parse the decision files (the consumer resolves
130
+ the path; the helper owns the edge rule):
131
+ ```bash
132
+ node scripts/watchtower-narrative-corpus.mjs --memory "<memory-dir>"
133
+ ```
134
+ Each entry carries `supersedes` (RECORDED, directional edges) and
135
+ `see_also` (undirected wikilinks — **never** render a see-also as a
136
+ supersession).
137
+
138
+ **L3 — methodology "why" (richest substrate).** If `.claude/methodology/`
139
+ exists in the cwd, read its `README.md` index and the `-internal.md`
140
+ bodies relevant to the load-bearing decisions — these carry the
141
+ Claim→Evidence→Skeptic→Counter reasoning the other stores summarize away.
142
+
143
+ **L4 — pib-db design rationale.** `pib_list_actions` for the current
144
+ project (resolve the `prj:` fid via `pib_list_projects`; if the name is
145
+ ambiguous, ask — never guess). Action notes carry `absorbs act:…` /
146
+ `Supersedes …` prose — these are **inferred** edges (prose, not a
147
+ structural field), mark them so.
148
+
149
+ **L5 — synthesize, ANSWER-FIRST** (plain English, concept-first; the tone
150
+ contract above applies in full):
151
+
152
+ 1. **What still constrains this project right now** — lead with this. The
153
+ decisions/constraints from earlier that later work still depends on;
154
+ for the ones the operator might want to revisit, say whether anything
155
+ still hangs on them (reversible vs load-bearing). This is the answer
156
+ the drill-down exists to give.
157
+ 2. **Supersession chains** — what replaced what, with **provenance**:
158
+ *recorded* (a parsed `**Supersedes:**` block or a pib `absorbs act:`
159
+ marker — citable) vs *inferred* (read from prose) — labeled, never
160
+ conflated. A narrative is a trust surface: do not present an inferred
161
+ link as recorded history, and never render a `[[wikilink]]` as a
162
+ supersede.
163
+ 3. **The causal timeline** — OPTIONAL deep-read, placed **below** the
164
+ answer. Chapter by causal **pivot / phase transition** (the moments
165
+ direction changed — a constraint discovered, an approach superseded),
166
+ not by raw time gaps. Skip this entirely when the corpus is sparse.
167
+
168
+ **Honesty footer.** Report `per_store_counts` and `skipped_unresolved` in
169
+ plain words when either is notable: "history may be incomplete — N inbox
170
+ items couldn't be attributed to a project and may belong here." Never
171
+ present a silently-truncated history as complete.
172
+
173
+ **Sparse / empty.** If the combined decision count (inbox_kept + memory
174
+ decisions) is under ~3, say "not enough decision history to narrate yet
175
+ (N found)" and stop — a story needs enough points to have an arc.
176
+
177
+ Lineage mode renders inline and stops. It writes nothing — no state file,
178
+ no inbox item, no memory write.
44
179
 
45
180
  ## Step 1: Gather Data
46
181
 
47
- No Claude API calls in this step — pure filesystem reads. All paths
48
- are under `~/.claude-cabinet/watchtower/`.
49
-
50
- ### 1a. Ring Health
51
-
52
- Read health files for each ring:
53
-
54
- - `state/ring1-health.json` — Ring 1 (mechanical cron): last run time,
55
- success/fail, projects scanned
56
- - `state/ring2-fast-health.json` Ring 2 fast tier: last run time,
57
- success/fail, items enriched
58
- - `state/ring2-slow-health.json` Ring 2 slow tier: last run time,
59
- success/fail, synthesis results
60
- - `state/ring3-health.json`Ring 3 (session-aware): last processed
61
- session, backlog count
62
-
63
- If a health file is missing, note the ring as "not yet active" rather
64
- than erroring. Watchtower rings are deployed incrementally — a missing
65
- file means the ring hasn't run, not that something is broken.
66
-
67
- ### 1b. Sessions Processed
68
-
69
- Scan `ring3/processed/` for session files (`.json`) within the scope
70
- window. For each, extract: project name, session date, duration,
71
- summary snippet. Count total sessions processed.
72
-
73
- If the directory is empty or missing, report "No sessions processed
74
- yet" Ring 3 may not have run.
75
-
76
- ### 1c. Queue Activity
77
-
78
- List `queue/items/*.json`. For each item, read `status`, `category`,
79
- `urgency`, `filed_at`, and `title`. Compute:
80
-
81
- - Total items by status (pending, resolved, expired, superseded, dismissed)
82
- - Pending items by urgency (urgent, normal, low)
83
- - Oldest pending item (age in hours/days)
84
- - Category distribution of pending items
85
-
86
- If no items exist, report "Queue empty."
87
-
88
- **QA debt (filed gaps):** while scanning, also collect RESOLVED
89
- `qa-handoff` items whose `qa_verdict.filed_gaps` is non-empty (the
90
- `verified · N gaps filed` verdicts). For each, check whether the filed
91
- `act:` fids are still open in pib-db (`pib_get_action`, or
92
- `node scripts/pib-db.mjs get <fid>`); drop entries whose fids are all
93
- closed. Surviving gaps are unretired QA debt "filed" must not mean
94
- "forgotten."
95
-
96
- ### 1d. Portfolio State
97
-
98
- Read project state files from `state/projects/*.md`. For each project
99
- within scope, extract the key sections: Active Plans, Last Session,
100
- Standing Issues. If a project filter was specified, only read that
101
- project's file.
102
-
103
- ### 1e. Active Threads
104
-
105
- Read `state/threads/*.json`. For each thread file, parse the JSON and
106
- collect threads with `status: "active"`. The **current cursor** is the
107
- last entry of the `cursor_history` array
108
- (`cursor_history[length-1].cursor`); older thread files may instead carry
109
- a single `cursor` object — treat that lone object as the current cursor.
110
- For each active thread, extract from the current cursor:
111
- - `thread` (slug name)
112
- - `cursor.what` (one-line description)
113
- - `cursor.where_left_off` (current state)
114
- - `last_updated` (timestamp)
115
- - `sessions` array length (how many sessions have touched it)
116
-
117
- If a project filter is active, filter to threads whose `sessions` array
118
- contains at least one entry with a matching `project` slug. If no thread
119
- files exist, skip silently — threads accumulate over time.
120
-
121
- Sort by `last_updated` descending (most recent first).
122
-
123
- ### 1f. Last Briefing Timestamp
124
-
125
- Read `state/last-briefing.json` if it exists. Use this to determine
126
- what's "new since last briefing" for the default scope. If the file
127
- doesn't exist, fall back to "last 24 hours."
128
-
129
- ## Step 2: Top-Level Brief
130
-
131
- Present a scannable summary in **10 lines or fewer.** This is the
132
- executive view — the user should know the state of everything in one
133
- glance.
134
-
135
- Format:
136
-
182
+ No Claude API calls — pure filesystem reads plus the queue lib. State
183
+ paths are under `~/.claude-cabinet/watchtower/`; the queue lib is the
184
+ project-root `scripts/watchtower-queue.mjs` (any watchtower-installed
185
+ project's copy operates on the shared global queue — this is what
186
+ makes "run from any desk" work).
187
+
188
+ ### 1a. Ring health
189
+
190
+ Read `state/ring1-health.json`, `state/ring2-fast-health.json`,
191
+ `state/ring2-slow-health.json`, `state/ring3-health.json`. A missing
192
+ file means "not yet active", not an error. Thresholds: **ok** — last
193
+ success within expected cadence (Ring 1: 5 min, Ring 2 fast: 15 min,
194
+ Ring 2 slow: 6 hours, Ring 3: since last session end); **stale** —
195
+ exceeded 2x cadence; **down** missing after previously running, or
196
+ last run failed.
197
+
198
+ ### 1b. Sessions processed
199
+
200
+ Scan `ring3/processed/` for session files within the scope window:
201
+ project, date, duration, summary snippet.
202
+
203
+ ### 1c. The inbox, in full
204
+
205
+ Run `runExpiry()` first (warn 14d, expire 30d the mechanical decay
206
+ floor; gated categories never expire, they warn). Then load ALL
207
+ pending items via `listPending()` and group by `item.project`. Items
208
+ carrying `project_unresolved: true` form their own group (see Step 3).
209
+ Collect per project: counts by category and urgency, age distribution,
210
+ and titles. Also collect resolved `qa-handoff` items whose
211
+ `qa_verdict.filed_gaps` is non-empty; check each filed `act:` fid in
212
+ pib-db (`pib_get_action` or `node scripts/pib-db.mjs get <fid>`) and
213
+ keep only still-open ones these are the open QA debts for Step 4.
214
+
215
+ ### 1d. Cross-ring portfolio view (state, threads, git attention, resolution history)
216
+
217
+ Run the cross-ring reader ONE gather call that replaces the old
218
+ per-store reads (state files + threads) and adds two signals nothing
219
+ here consulted before: the inbox **resolution history** (with
220
+ `resolution_mix` ratios — what the operator's past decisions actually
221
+ engaged vs discarded, per project) and the structured **git-attention
222
+ facts**:
223
+
224
+ ```bash
225
+ XRING="$(mktemp)"
226
+ node scripts/watchtower-cross-ring-reader.mjs --portfolio > "$XRING"
227
+ # with a project filter active: --project "<name or slug>" instead
228
+ # (config-key name preferred; a unique slug match also resolves)
137
229
  ```
138
- ## Briefing — [scope description]
139
230
 
140
- **Sessions:** N processed since [time]. Last: [project] ([when]).
141
- **Queue:** N pending (M urgent). Oldest: [age]. [N resolved since last briefing.]
142
- **Health:** Ring 1 [ok/stale/down]. Ring 2 fast [ok/stale/down]. Ring 2 slow [ok/stale/down]. Ring 3 [ok/stale/down].
143
- **Threads:** N active. Most recent: [thread-slug] [cursor.what].
144
- **Portfolio:** N projects tracked. [1-2 line highlight of what's active or needs attention.]
231
+ **Consume by Reading the file, never by echoing it through Bash** — a
232
+ live portfolio view runs to a few hundred KB and Bash tool output
233
+ truncates long before that (a truncated JSON parse is the failure this
234
+ instruction exists to prevent). Read the envelope's top (counts +
235
+ honesty fields) for Step 2, then Read each project's view slice as you
236
+ walk that chunk in Step 3; the JSON is pretty-printed, so each
237
+ project's view is one contiguous block.
238
+
239
+ Per project it returns the parsed Ring 1 state sections (Active Plans,
240
+ Last Session, Standing Issues, Tech Stack), git-attention facts, recent
241
+ session summaries, thread cursor timelines (capped to the newest 3
242
+ snapshots with `history_total` — the full evolution is lineage mode's
243
+ job; a legacy lone-`cursor` file arrives as a single-entry history;
244
+ current cursor = the LAST history entry), pending counts by
245
+ category/urgency, and the resolution history (`resolution_mix` carries
246
+ full counts; the event list is capped with `resolution_events_total`).
247
+ Every cap reports its total — "3 of 85" is signal, not truncation. The
248
+ envelope's honesty fields (`unattributed_items`, `orphan_threads`,
249
+ `orphaned_state_files`) are real signal — surface them in plain words
250
+ in Step 4 when notable. Pending COUNTS here are a narration snapshot;
251
+ 1c's live `listPending()` load stays the disposition source of truth.
252
+
253
+ **Fallback (script absent — a consumer that hasn't reinstalled since
254
+ the reader shipped):** read the raw stores directly as 1e describes,
255
+ and proceed without the resolution-history/git-attention signals.
256
+
257
+ ### 1e. Active threads (fallback path only)
258
+
259
+ Normally covered by the 1d view. When the 1d fallback is in effect,
260
+ read `state/threads/*.json`; collect `status: "active"` threads. The
261
+ current cursor is `cursor_history[last].cursor` (older files may carry
262
+ a lone `cursor` object — treat it as current). Extract `what`,
263
+ `where_left_off`, `next_steps`, `last_updated`, session count. Sort by
264
+ `last_updated` descending. (Also read `state/projects/*.md` — Active
265
+ Plans, Last Session, Standing Issues per project, scope-filtered if a
266
+ project filter is active.)
267
+
268
+ ### 1f. Last briefing timestamp
269
+
270
+ Read `state/last-briefing.json` if present — it anchors "new since
271
+ last briefing" for the default scope.
272
+
273
+ ### 1g. Spawn the live advisor panel (full mode only)
274
+
275
+ The standing session advisors hold their start-side seat here — this is
276
+ where intent exists, the thing background runs are blind to. Discover
277
+ the panel from `.claude/skills/_index.json`: entries whose
278
+ `standingMandate` includes `"briefing"`, each with a
279
+ `directives.briefing` scoped task (a mandate without a directive is a
280
+ data error — skip that member, say so). Spawn all members in parallel
281
+ NOW, right after the Step 1 reads, so they work while you compose Step
282
+ 2. Each agent gets: its SKILL.md (from the index `path`), the
283
+ portfolio data gathered above (state summaries, active threads, pending
284
+ counts), and its `directives.briefing` as the task — constrained to a
285
+ **one-to-few-line observation**. The constraint is load-bearing.
286
+
287
+ Collect results before Step 3. Route them through the curation seam —
288
+ never as their own section: an observation about a specific project
289
+ becomes a Step 3b candidate in that project's chunk; a portfolio-level
290
+ observation is Step 4 close material. Include only members who said
291
+ something; silence is fine and is the common case. In recap mode, skip
292
+ the panel entirely — it spawns only on full-mode invocation.
293
+
294
+ ### 1h. Unprocessed DX captures (mux's global capture list)
295
+
296
+ Read mux's global DX-capture store — `~/.local/share/mux/dx.json` — the
297
+ single cross-project list where `mux dx "..."` and `/dx-feedback` drop
298
+ developer-experience ideas and friction. A pure filesystem read with no
299
+ ordering dependency on the panel spawn. If the file is absent or
300
+ unparseable (mux not installed, or no captures yet), skip silently — it
301
+ is a mux-owned store and its absence is normal, not an error.
302
+
303
+ An item is **active** (unprocessed) only when BOTH `done` is false AND
304
+ `filed` is false. `done` items were cleared; `filed` items already live
305
+ in pib-db (an action or feedback) — resurfacing either is exactly the
306
+ write-only-telemetry bug this duty exists to kill. Collect each active
307
+ item's `text`, `created_at`, `origin`, and its **global active-list
308
+ position** (1-based index among active items — the position
309
+ `manage-dx.py` addresses when draining). Resolve each item's `origin` to
310
+ a walk project where you can: labels like `"claude-cabinet"` or `"flow"`
311
+ map to a project chunk; labels like `"session-observation"` or
312
+ `"unknown"` do not — those are portfolio-level (Step 4). If there are
313
+ zero active items, skip silently. (This was orient's job; post-orient
314
+ nothing reads these unless the briefing does — see
315
+ `lesson_write_only_telemetry_wire_a_reader`.)
316
+
317
+ ## Step 2: Top Brief
318
+
319
+ Ten lines or fewer — the whole portfolio in one glance, curated. Not a
320
+ fixed template: lead with the one or two things that genuinely matter
321
+ today (a gate waiting, an urgent item, a fresh advisor finding from
322
+ last session's close, a thread left mid-flight, a ring down), then the
323
+ compact vitals:
145
324
 
146
- [If any urgent queue items exist, list them here — one line each.]
147
- [If any resolved qa-handoff verdicts carry still-open filed_gaps, add:
148
- **QA debt:** N gaps filed by past QA verdicts still open — act:fid (gap), one line each.]
149
325
  ```
326
+ ## Briefing — [scope, in words]
150
327
 
151
- Health status thresholds:
152
- - **ok** — last successful run within expected cadence (Ring 1: 5 min,
153
- Ring 2 fast: 15 min, Ring 2 slow: 6 hours, Ring 3: since last session end)
154
- - **stale** — last run exceeded 2x expected cadence
155
- - **down** — health file missing or last run was a failure
156
-
157
- ## Step 3: Progressive Disclosure (Full mode only)
158
-
159
- In **factual/recap mode, STOP here.** The summary is the deliverable.
160
-
161
- In **full mode**, offer drill-down using `AskUserQuestion`:
162
-
163
- - `header`: `"Briefing"`
164
- - `question`: `"What would you like to drill into?"`
165
- - `options`:
166
- - Sessions — "Review processed sessions in detail"
167
- - Queue — "Walk through pending inbox items"
168
- - Threads — "Active work threads and their cursors"
169
- - Health — "Ring health details and diagnostics"
170
- - Portfolio — "Per-project deep dive"
171
- - Looks good — "Nothing to drill into, move on"
172
- - `multiSelect: false`
173
-
174
- ### Sessions drill-down
175
-
176
- List each session processed within scope: project, date, duration, and
177
- the Ring 3 summary. Group by project if multiple projects are in scope.
178
-
179
- ### Threads drill-down
180
-
181
- Present all active threads sorted by `last_updated` (most recent first).
182
- For each thread, show:
183
-
184
- - Thread slug and `cursor.what`
185
- - `cursor.where_left_off` (the understanding that survives across sessions)
186
- - `cursor.open_questions` if any
187
- - `cursor.next_steps` if any
188
- - Session count and last updated date
189
-
190
- If a project filter is active, show only threads touching that project,
191
- with the primary thread (most recently touched by a session in that
192
- project) expanded with full cursor detail, and other threads as one-line
193
- summaries.
194
-
195
- If there are dormant threads (status: "dormant"), mention the count but
196
- don't list them unless the user asks.
197
-
198
- ### Queue drill-down
199
-
200
- Present pending queue items sorted by urgency (urgent first), then by
201
- age (oldest first). For each item, show: id, title, category, urgency,
202
- age, summary. If the item has enrichment (`enrichment_status: complete`),
203
- note that enrichment is available.
204
-
205
- After presenting, offer to open the `/inbox` skill if the user
206
- wants to act on queue items.
207
-
208
- ### Health drill-down
209
-
210
- For each ring, show: last run timestamp, success/failure history,
211
- items processed, any error messages from the health file. If a ring
212
- is stale or down, suggest diagnostic steps.
213
-
214
- ### Portfolio drill-down
215
-
216
- Present the full project state file for each project in scope. If the
217
- user specified a project filter, show only that project. Otherwise,
218
- list projects and let the user pick one.
219
-
220
- After any drill-down completes, offer the drill-down menu again (loop)
221
- until the user selects "Looks good."
222
-
223
- ## Step 4: Cabinet Lens
224
-
225
- **Only activates** when the user selects "Portfolio" drill-down with
226
- scope "all" — meaning they want the broadest view.
227
-
228
- Discover cabinet members with `standing-mandate` that includes `orient`
229
- (same discovery mechanism as /orient's cabinet consultation phase: read
230
- `.claude/skills/_index.json`, filter by `standingMandate` containing
231
- `"orient"`).
232
-
233
- Spawn each matching member with a constrained task: **ONE LINE
234
- observation** about the portfolio state gathered in Step 1. No full
235
- audit, no detailed analysis. The constraint is load-bearing — without
236
- it, cabinet members produce pages of analysis that bury the briefing's
237
- signal.
238
-
239
- Collect responses and present them after the portfolio detail:
328
+ [1-3 curated headline lines — what actually deserves attention today.]
240
329
 
330
+ **Since [last briefing/time]:** N sessions across M projects. Last: [project] ([when]).
331
+ **Inbox:** N pending across M projects ([K urgent, G gated]). [Expiry note if items expired.]
332
+ **Health:** one line — "all rings ok" when true; name only the stale/down ones otherwise.
241
333
  ```
242
- ### Cabinet Observations
243
- - **[member-name]:** [one-line observation]
244
- - **[member-name]:** [one-line observation]
334
+
335
+ In **factual/recap mode, stop here** (after Step 5's timestamp). The
336
+ summary is the deliverable.
337
+
338
+ In **full mode**, continue straight into the walk — no menu, no
339
+ ceremony. One transition sentence: name the chunk order and why
340
+ ("Three projects have items waiting; starting with X — it has the
341
+ gate.").
342
+
343
+ ## Step 3: The Project Walk
344
+
345
+ One project at a time, in curated order: lead with the chunks the
346
+ operator can actually act on from this session — the current desk and
347
+ any project with dispositionable (ungated, this-desk) items — then
348
+ projects with pending gated items, then urgent items, then by thread
349
+ recency and pending count; quiet projects last (and a project with
350
+ nothing to say gets one line or silence — absence of news is news the
351
+ operator doesn't need). A project filter scopes the walk to that
352
+ project. **Why actionable-first:** a cross-desk gated item can only be
353
+ "left queued for its own desk" from here — it is read-only from this
354
+ session, so surfacing it (and a wall of another desk's state) before
355
+ the operator's own decisions just front-loads context they must hold.
356
+ Surface it, but later. A gated item is never dropped (floor #1); it is
357
+ only re-ordered.
358
+
359
+ **Decisions are never interleaved.** Each project's chunk completes —
360
+ narrative, attention items, dispositions executed — before the next
361
+ project is named. Cross-project observations wait for Step 4.
362
+
363
+ Items in the `project_unresolved` group form a final chunk of their
364
+ own, presented explicitly as "unresolved project" with each item's
365
+ stored fallback `project` value inline — never blended into real
366
+ project chunks, never silently dropped (fail-loud contract shared
367
+ with `/inbox`).
368
+
369
+ Each chunk has three movements:
370
+
371
+ ### 3a. State, in plain English
372
+
373
+ Two to four sentences synthesized from the project's threads, Last
374
+ Session, and standing issues — all already in hand from the Step 1d
375
+ cross-ring view (no second read; its parsed sections, session bullets,
376
+ and cursor timelines are the input). Concept-first — "the publish
377
+ pipeline is built but has never run against a live tag" — not file
378
+ paths and fids. This
379
+ is the operator-facing register (`cabinet/skill-output-conventions.md`
380
+ §9) as the briefing's whole default; keep it that way. Offer nothing
381
+ here; drill-down happens by the operator asking.
382
+
383
+ ### 3b. Curated attention (top-N selection)
384
+
385
+ Select **at most five** attention items for this project. This selection
386
+ step is where ALL "briefing should show X" duties land — see the
387
+ invariant. The weighable **candidate classes** (source · freshness/decay ·
388
+ floor-or-not) — a new duty adds an entry here, never a section:
389
+
390
+ - **Gated items** (`qa-handoff`) · pending in queue · **floor #1, always
391
+ included** (specific handling below).
392
+ - **Urgent-urgency items** · pending · weigh high.
393
+ - **Moment-is-now items** · a `routine` still within `stale_after_hours`,
394
+ a deferred trigger that fired · weigh high while fresh (handling below).
395
+ - **Open QA filed-gaps** belonging to this project · until the `act:` fid
396
+ closes · floor in Step 4, weighable here.
397
+ - **Advisor signals** · fresh `advisor-finding` items + live panel
398
+ observations · weigh fresh near the top, then decay (sources below).
399
+ - **Backlog-hygiene nudge** · the per-project state file's Standing Issues
400
+ shows ≥3 stale project(s) OR ≥2 completion candidate(s) (Ring 1 renders
401
+ these counts) · one line max, "backlog showing cruft — consider
402
+ /spring-clean" · decays once the counts fall; never its own section.
403
+ - **Environment advisories** · `suggested` entries in
404
+ `.claude/cabinet/advisories-state.json` (the watchtower context builder's
405
+ advisory pass already wrote them) for this chunk's project · one line
406
+ each, weigh low — they self-quiet after two surfacings · no probe here,
407
+ just read the surfacing state.
408
+ - **Verify signals** · shipped-uncovered warnings · weigh as they arrive.
409
+ - **State-file flags** · anything in Standing Issues that needs a human.
410
+ - **Unprocessed DX captures** · active items in mux's global `dx.json`
411
+ (1h) whose `origin` resolves to this chunk's project · weigh fresh,
412
+ then decay like any aged item — one line each, with a process/dismiss
413
+ path so the list actually drains (handling below) · not a floor:
414
+ captures are freely drainable and dismissable. Captures whose origin
415
+ resolves to no walk project go to the Step 4 close — never silently
416
+ dropped.
417
+
418
+ Everything else stays in the batch counts below.
419
+
420
+ Advisor signals come from two sources, both weighed here:
421
+
422
+ - **Fresh close-pass findings** — pending `advisor-finding` items filed
423
+ since the last briefing (Ring 3's session advisor pass, or an
424
+ /orient / /debrief consultation that persisted an actionable
425
+ finding — same channel). These are
426
+ the re-surfacing path the close pass promises: weigh a fresh one near
427
+ the top of this project's selection so it surfaces at the briefing
428
+ after the session that produced it, then let it decay into batch
429
+ fodder like anything else. Stale advisor findings are ordinary
430
+ ungated items — legal batch material in 3c.
431
+ - **Live panel observations** (Step 1g) that named this project.
432
+
433
+ Three categories get specific handling when selected:
434
+
435
+ - **Gated items (`qa-handoff`):** present what merged and what hangs
436
+ on it, then offer exactly two paths — run the recipient gate now in
437
+ this session (the qa-handoff skill's "The recipient gate" section is
438
+ the playbook; the stamped verdict is the only exit), or leave it
439
+ queued for its desk's QA station. Never fold a gated item into any
440
+ batch; never dismiss it to tidy the count (the lib will throw, by
441
+ design).
442
+ - **Routine items:** a declared routine fired and wasn't picked up at
443
+ its desk. If its moment is still live (within `stale_after_hours`),
444
+ offer to run its `evidence.script` conversation here and resolve
445
+ `acted-on`. If the moment passed (yesterday's morning briefing),
446
+ propose it for the stale batch below — routine items are dispatched
447
+ but not gated, and the engine supersedes stale ones anyway.
448
+ - **DX captures:** write-only until something drains them. Surface each
449
+ active capture as one plain line (the idea · when · where it came
450
+ from) and offer exactly two paths, one decision at a time — **process**
451
+ it (act on it now, or file it to pib-db as an action or feedback, then
452
+ mark it filed) or **dismiss** it as no-longer-relevant. Both paths
453
+ drain the list through mux's own tooling — single source of truth,
454
+ never hand-edit `dx.json`:
455
+
456
+ ```bash
457
+ # dismiss (clear) a capture — n is its position in the active list
458
+ python3 ~/.config/mux/manage-dx.py done <origin> <n>
459
+ # after filing a capture to pib-db, mark it filed (drops it from the panel)
460
+ python3 ~/.config/mux/manage-dx.py mark-filed <origin> <n>
461
+ ```
462
+
463
+ `<n>` is the global active-list position, and positions renumber as
464
+ items leave — when draining several, clear from the **highest position
465
+ down** (or re-read positions between marks) so an earlier removal can't
466
+ shift the index out from under you. `<origin>` is context only; the
467
+ number addresses the global active list regardless of origin.
468
+
469
+ ### 3c. Batch dispositions
470
+
471
+ **First, the sign-off batch — the SAVE direction (M4, act:ffae4eec).**
472
+ Before any decay clustering, partition this project's ungated pending
473
+ items with `partitionForBatchSignoff` (the lib owns the predicate). Its
474
+ `signoff` set is the high-confidence pre-drafted knowledge-extraction
475
+ items (non-empty `draft_artifact`, not urgent, not low-confidence) —
476
+ work the rings already did that needs a nod, not a judgment. Offer the
477
+ whole set as ONE sign-off decision ("the background system drafted these
478
+ N memory entries; approving writes them to memory — sign off on all?").
479
+ On approval, write each `draft_artifact` to memory (`/cc-remember` or a
480
+ direct write), THEN one `applyBatch` resolve with
481
+ `resolution_type: 'captured-to-memory'`. This is the throughput valve:
482
+ M1/M2 raise inflow and success = filed × approval-rate (the M5 canary
483
+ tracks it), so a fast sign-off keeps drafts from ageing out
484
+ unadjudicated. Pulled-out items get per-item `/inbox` treatment after
485
+ the walk; everything else falls to the decay flow below — still one
486
+ decision at a time, the sign-off batch presented first.
487
+
488
+ **Then, decay clustering.** Cluster this project's REMAINING pending
489
+ items into **at most three
490
+ proposed batches** by theme — category × age × thread is the usual
491
+ axis. For each batch, propose a disposition in plain English with the
492
+ reasoning visible. Decay lives here: the curator's default proposal
493
+ for an aged, ungated cluster is dismissal-as-stale ("these 14 are
494
+ knowledge-extraction drafts from May; the threads they fed have moved
495
+ on — archive?"), always scoped to this project, never a global sweep.
496
+ A batch proposal must name: what's in it (count + a few example
497
+ titles), the proposed disposition, and the typed reason that will land
498
+ on every item.
499
+
500
+ Ask for approval **one decision at a time** — never a stack of
501
+ questions under a wall of context, never a same-turn dialog. Two
502
+ failure modes, both observed live: a same-turn dialog eclipses the
503
+ reasoning above it, so the operator approves logic they cannot see
504
+ (recurred 2026-06-13); and a long narrative followed by several
505
+ questions at the end forces the operator to hold every batch's
506
+ rationale in their head at once, which they cannot (recurred
507
+ 2026-06-14). **The decision/context structure rule:** the only thing
508
+ that should require working memory is the single decision in front of
509
+ the operator, with its reason immediately beside it.
510
+
511
+ The shape: present **one batch** — what's in it (count + a few example
512
+ titles), the proposed disposition, the typed reason — then a one-line
513
+ question ("Dismiss these 14 as stale? y / n / show me") and the turn
514
+ STOPS. The operator answers in prose. Apply it, confirm in one line,
515
+ then present the next batch the same way. Pure read-only material —
516
+ portfolio state for desks the operator isn't on, platform notes,
517
+ anything needing no decision — is delivered AFTER all decisions are
518
+ made; it costs nothing to hold, so it never sits above a decision.
519
+ Reserve `AskUserQuestion` only for a self-contained choice whose
520
+ options stand on their own inside the dialog (e.g. a layout pick with
521
+ previews); batch dispositions never qualify.
522
+
523
+ Execute approved batches immediately via the queue lib, then say what
524
+ happened in one line each and move to the next project:
525
+
526
+ ```bash
527
+ node --input-type=module -e "
528
+ import { applyBatch, partitionForBatchSignoff, listPending } from './scripts/watchtower-queue.mjs';
529
+ // Sign-off batch (M4): partitionForBatchSignoff(listPending({project})).signoff
530
+ // → resolve 'captured-to-memory' (write each draft_artifact FIRST). Decay='dismiss'.
531
+ const r = applyBatch([/* ids */], {
532
+ disposition: 'dismiss', // or 'resolve' (the sign-off batch)
533
+ resolution_type: 'stale', // typed reason — required ('captured-to-memory' for sign-off)
534
+ notes: '<the reason shown to the operator, verbatim>',
535
+ });
536
+ console.log(JSON.stringify(r));
537
+ "
245
538
  ```
246
539
 
540
+ `applyBatch` pre-validates the whole batch and throws before writing
541
+ anything if a gated item slipped in — the carve-out is structural, not
542
+ etiquette. Choose `resolution_type` honestly per batch: `stale`
543
+ (overtaken by events), `noise` (shouldn't have been filed),
544
+ `captured-to-memory` / `acted-on` for resolve batches — these feed the
545
+ trust-ladder ratios. Items the operator pulls out of a batch get the
546
+ `/inbox` per-item treatment (offer it after the walk, not mid-chunk).
547
+
548
+ ## Step 4: Cross-Portfolio Close
549
+
550
+ The only non-project section, and it is curated like everything else:
551
+ include a part only when it has signal.
552
+
553
+ - **Cross-cutting synthesis** — patterns spanning projects (the same
554
+ friction filed from two desks, a theme across threads, a decision
555
+ made in one project that answers a question open in another). The
556
+ substrate is the Step 1d cross-ring portfolio view — synthesize from
557
+ its per-project views (git-attention facts across projects,
558
+ `resolution_mix` ratios, thread/session convergence) instead of
559
+ re-deriving from raw stores. Its honesty fields belong here too, in
560
+ plain words when notable: "N inbox items couldn't be attributed to
561
+ any project", "one active thread matches no configured project".
562
+ The synthesis itself stays live — the reader gathers, this session
563
+ thinks.
564
+ - **Standing debts** — floor #2: while any QA filed-gaps remain open,
565
+ one roll-up line each ("X still owes the migration test filed by
566
+ last week's QA — act:fid"). Same treatment for unmerged-worktree
567
+ warnings surfaced by the state files.
568
+ - **Advisor panel, portfolio-level** — the Step 1g observations that
569
+ span projects rather than belonging to one chunk land here (a
570
+ historian precedent that answers an open question, a platform delta
571
+ from the insider, a legibility flag from the user-advocate). One line
572
+ each, only members who said something — never a roll call.
573
+ - **Unprocessed DX captures, portfolio-level** — active `dx.json`
574
+ captures (1h) whose `origin` resolved to no walk project land here,
575
+ one line each, with the same process/dismiss drain path as the
576
+ per-project handling (3b). Only when some exist; never a standing
577
+ section.
578
+
579
+ Close with one sentence: dispositions applied this run (N resolved, M
580
+ dismissed, across K projects), what remains pending, and — only if
581
+ something is genuinely waiting on the operator — the single next thing.
582
+
247
583
  ## Step 5: Record Timestamp
248
584
 
249
- After the briefing completes (regardless of mode), write
585
+ After the briefing completes (any mode), atomically write
250
586
  `~/.claude-cabinet/watchtower/state/last-briefing.json`:
251
587
 
252
588
  ```json
@@ -255,42 +591,54 @@ After the briefing completes (regardless of mode), write
255
591
  "scope": "<the scope that was used>",
256
592
  "mode": "full" | "recap",
257
593
  "sessions_covered": <count>,
258
- "queue_pending_at_time": <count>
594
+ "queue_pending_at_time": <count>,
595
+ "dispositions": { "resolved": <n>, "dismissed": <n> }
259
596
  }
260
597
  ```
261
598
 
262
- Use atomic write (temp + rename) per watchtower contracts.
263
-
264
- ---
599
+ ## Edge Cases
600
+
601
+ - No watchtower install (`~/.claude-cabinet/watchtower/` missing):
602
+ say so and point at `/watchtower install`. Don't improvise state.
603
+ - Queue lib missing at `scripts/watchtower-queue.mjs` (project without
604
+ the watchtower module): deliver the read-only briefing from global
605
+ state and say dispositions need a watchtower-installed project.
606
+ - Corrupt/unparseable item files: skip with a one-line warning count.
607
+ - No mux DX store (`~/.local/share/mux/dx.json` missing, or mux not
608
+ installed): skip the DX-capture candidate silently — a mux-owned
609
+ store's absence is normal, not an error.
610
+ - An `applyBatch` throw is a stop-and-show, not a retry loop: name the
611
+ item that tripped the gate, pull it into per-item handling, re-offer
612
+ the rest as a new batch.
265
613
 
266
614
  ## Calibration
267
615
 
268
- **Core failure this targets:** The user returns to their portfolio and
269
- has no idea what happened while they were away. Watchtower processed
270
- sessions, enriched queue items, detected issues but none of that
271
- reaches the user unless they know where to look and which files to read.
616
+ **Core failure this targets:** attention flow-control. The background
617
+ rings file faster than item-by-item triage drains, so the inbox grows
618
+ until the operator stops lookingand the things that DO need eyes
619
+ (a QA gate, a fired routine, a dying ring) drown in batchable noise.
272
620
 
273
621
  ### Without Skill (Bad)
274
622
 
275
- The user starts a session after a day away. They know watchtower is
276
- running but don't know what it found. They manually check
277
- `~/.claude-cabinet/watchtower/state/summary.md` it's a template with
278
- placeholders, or it was last updated hours ago. They `ls` the queue
279
- directory and count files. They open each project state file. They piece
280
- together a picture from raw artifacts across five directories.
281
-
282
- Twenty minutes of manual archaeology to answer "what happened while I
283
- was gone?" — and they still miss the urgent queue item filed by Ring 2
284
- because it was in a subdirectory they didn't check.
623
+ The operator returns after a day. The inbox shows 174 pending. They
624
+ open `/inbox`, review four items one at a time, lose patience, and
625
+ close it. The qa-handoff filed yesterday sits unworked behind 50
626
+ knowledge-extraction drafts. Next week the inbox shows 230 and the
627
+ operator has stopped running anything at all the surfaces produced
628
+ signal but no decision throughput.
285
629
 
286
630
  ### With Skill (Good)
287
631
 
288
- The user says "/briefing" or "what's the landscape." Ten lines appear:
289
- 3 sessions processed, 2 queue items pending (1 urgent), all rings
290
- healthy, the maginnis project has a stale branch. The urgent queue item
291
- is right there in the summary. The user drills into "Queue," reads the
292
- enrichment, opens `/inbox` to resolve it. Five minutes, full picture,
293
- nothing missed.
294
-
295
- Or the user just wants the facts "/recap" delivers the summary and
296
- stops. No questions, no drill-down, no cabinet. Just the numbers.
632
+ The operator says "/briefing". Ten lines: one gate waiting on
633
+ claude-cabinet, two rings ok one stale, 174 pending of which ~150 are
634
+ three stale batches. The walk starts with claude-cabinet: state in two
635
+ sentences, the gate presented with its two paths, then "these 40
636
+ knowledge drafts predate the threads they fed archive?" Three
637
+ plain-prose approvals later, 120 items are dispositioned with typed
638
+ reasons, the gate is queued deliberately for the QA station, and the
639
+ close notes one cross-project pattern worth a future action. Eight
640
+ minutes, decisions made where the operator already was and nothing
641
+ gated or filed-as-debt was waved away in the bulk.
642
+
643
+ Or the operator just wants the facts — "/recap" delivers the summary
644
+ and stops.