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
|
@@ -80,7 +80,23 @@ the section, Ring 1 writes its own ephemeral fallback ("Active: …" /
|
|
|
80
80
|
last-commit line) and rebuilds it freely; once the marker is present,
|
|
81
81
|
Ring 1 must carry the existing section forward verbatim. Without this,
|
|
82
82
|
Ring 1's full-file rebuild deterministically clobbers Ring 3's summary
|
|
83
|
-
within one cron tick (~5 minutes).
|
|
83
|
+
within one cron tick (~5 minutes). Rebuild writes must go through
|
|
84
|
+
`writeProjectStatePreservingRing3()` in `watchtower-lib.mjs` (re-read
|
|
85
|
+
verify with retry), never a raw read→merge→write — the raw form has a
|
|
86
|
+
read-then-write race that drops a Last Session Ring 3 authors mid-merge.
|
|
87
|
+
|
|
88
|
+
The inline `## Last Session` block holds the COMPLETE session bullet set
|
|
89
|
+
— byte-identical to the bullets in the per-session record at
|
|
90
|
+
`state/projects/<slug>/sessions/<date>-<session-id>.md`. It is NOT a
|
|
91
|
+
pointer, a digest, or a truncated subset. Both bodies derive from the
|
|
92
|
+
ONE model bullet string via `buildLastSessionBlock()`, and Ring 3 writes
|
|
93
|
+
the inline block UNCONDITIONALLY — creating `<slug>.md` if it does not
|
|
94
|
+
yet exist — so the inline section is never silently skipped (the prior
|
|
95
|
+
`existsSync` gate dropped it on fresh projects, leaving readers to render
|
|
96
|
+
Ring 1's truncated one-line fallback while the per-session file held the
|
|
97
|
+
full set; act:ac119994). `upsertLastSessionSection()` performs the
|
|
98
|
+
in-place splice/append with the same line-anchored header search
|
|
99
|
+
`preserveRing3LastSession()` reads with — one splice convention, not two.
|
|
84
100
|
|
|
85
101
|
### Thread File Durability (disk wins over model)
|
|
86
102
|
|
|
@@ -111,6 +127,182 @@ standard files:
|
|
|
111
127
|
`/inbox` reads these when `enrichment_status` is `"complete"`.
|
|
112
128
|
Missing files degrade gracefully (null in the read result).
|
|
113
129
|
|
|
130
|
+
## Routine Dispatch (declared interactive routines)
|
|
131
|
+
|
|
132
|
+
Projects declare interactive customs as **routines** in `config.json`,
|
|
133
|
+
under their project entry:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
"projects": {
|
|
137
|
+
"flow": {
|
|
138
|
+
"path": "/Users/x/flow",
|
|
139
|
+
"routines": [
|
|
140
|
+
{
|
|
141
|
+
"name": "morning-briefing",
|
|
142
|
+
"description": "Walk the operator through the day's landscape",
|
|
143
|
+
"trigger": { "type": "time-of-day", "at": "08:30" },
|
|
144
|
+
"script": ".claude/routines/morning-briefing.md",
|
|
145
|
+
"urgency": "normal",
|
|
146
|
+
"stale_after_hours": 24
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- `name` — kebab-case slug, unique within the project.
|
|
154
|
+
- `trigger.type` — `time-of-day` (`at: "HH:MM"`, local, fires once per
|
|
155
|
+
day), `interval` (`minutes`, >= 5), `path-nonempty` (`path` relative
|
|
156
|
+
to the project root or absolute; `cooldown_minutes` default 60), or
|
|
157
|
+
`session-close` (fires when Ring 3 closes a session for the project;
|
|
158
|
+
worktree sessions resolve to the main project; `cooldown_minutes`
|
|
159
|
+
default 0).
|
|
160
|
+
- `script` — project-authored phase file (the established CC phase
|
|
161
|
+
convention) holding the routine's conversation script. The receiving
|
|
162
|
+
session reads it and runs it; watchtower never parses it.
|
|
163
|
+
- `stale_after_hours` (default 24) — how long a pending dispatch stays
|
|
164
|
+
blocking before a fresh firing supersedes it.
|
|
165
|
+
|
|
166
|
+
Ownership: `watchtower-routines.mjs` owns trigger evaluation, firing
|
|
167
|
+
state (`state/routine-state.json`), and dispatch. Ring 1 ticks the
|
|
168
|
+
mechanical triggers; Ring 3 raises session-close events. Both are
|
|
169
|
+
feature-flagged by `defaults.routine_dispatch`. A firing files an inbox
|
|
170
|
+
item (category `routine` — the durable record) and pushes a descriptor
|
|
171
|
+
through `mux qa dispatch`, the SAME hardened desk-dispatch path as
|
|
172
|
+
qa-handoffs (act:796fe6dc) — never a parallel one. Terminal exits on
|
|
173
|
+
any dispatched-category item (qa-handoff, routine) clear the matching
|
|
174
|
+
mux descriptor; routine items resolve normally (no QA gate, normal
|
|
175
|
+
expiry — expiry also clears the descriptor). A pending routine item
|
|
176
|
+
blocks refiring of the same routine until resolved or stale.
|
|
177
|
+
|
|
178
|
+
## Session Advisor Pass (re-homed standing advisors)
|
|
179
|
+
|
|
180
|
+
The standing session advisors (historian, system-advocate,
|
|
181
|
+
user-advocate, anthropic-insider) hold two session-boundary seats, both
|
|
182
|
+
index-driven via `.claude/skills/_index.json` — the roster is whoever
|
|
183
|
+
declares the mandate, never a hardcoded list (act:aded4fc9):
|
|
184
|
+
|
|
185
|
+
- **Close side (automatic):** Ring 3's Phase 2m runs every member whose
|
|
186
|
+
`standingMandate` includes `session-close` against the compressed
|
|
187
|
+
session transcript, scoped by `directives.session-close`. Findings
|
|
188
|
+
file as `advisor-finding` inbox items (max 2 per member per session,
|
|
189
|
+
deduped against pending + resolved/dismissed corpora). Feature-flagged
|
|
190
|
+
by `defaults.session_advisors`. Cost: reuses Ring 3's pinned-sonnet
|
|
191
|
+
run — no Claude Code spawn.
|
|
192
|
+
- **Start side (pull, intent-aware):** `/briefing` full mode spawns
|
|
193
|
+
every member whose `standingMandate` includes `briefing`, scoped by
|
|
194
|
+
`directives.briefing`, with the briefing's gathered portfolio context.
|
|
195
|
+
Observations route through the curator's Step 3b selection and Step 4
|
|
196
|
+
close — never a permanent section (anti-accretion invariant).
|
|
197
|
+
- **Consultation side (skill-level):** the /orient and /debrief
|
|
198
|
+
cabinet-consultations phases persist their members' ACTIONABLE
|
|
199
|
+
findings through this same channel before the briefing/report
|
|
200
|
+
renders (act:84daa7bc) — `advisor-finding` items with
|
|
201
|
+
`filed_by: "manual"` and `evidence.directive_key: "orient"` or
|
|
202
|
+
`"debrief"`, capped at 2 per member per session like Phase 2m.
|
|
203
|
+
Same dedup-vs-pending discipline (an existing pending item is
|
|
204
|
+
referenced, not re-filed), same /briefing re-surfacing — never a
|
|
205
|
+
parallel path. Projects without watchtower fall back to a pib-db
|
|
206
|
+
action; with neither available, the finding is marked UNTRACKED in
|
|
207
|
+
the rendered output so the non-persistence is loud.
|
|
208
|
+
|
|
209
|
+
`advisor-finding` is ungated and undispatched: normal expiry applies
|
|
210
|
+
(findings decay naturally), and `/briefing` weighs fresh ones near the
|
|
211
|
+
top of the owning project's chunk — the re-surfacing the close pass
|
|
212
|
+
promises. A `session-close`/`briefing` mandate without the matching
|
|
213
|
+
directive key is a data error: consumers skip the member and log it.
|
|
214
|
+
|
|
215
|
+
## Session-Close Extraction Lenses — Phase 2n/2o/2p (act:4ff2cfb3)
|
|
216
|
+
|
|
217
|
+
Ring 3's session close runs three extraction lenses beyond the knowledge
|
|
218
|
+
pass (2d) and advisor pass (2m), each modeled on `advisorPass` with an
|
|
219
|
+
injectable `callFn` (testable; never folded into the hardened Phase 2d —
|
|
220
|
+
see `lesson_ring3_lens_injectable_phase_pattern`):
|
|
221
|
+
|
|
222
|
+
- **Phase 2n — Raised-but-unhandled.** Loose ends the session RAISED
|
|
223
|
+
(promises, side-issues, open questions) but neither completed nor
|
|
224
|
+
filed → an inbox item under the `raised-unhandled` category. Capped at
|
|
225
|
+
`RAISED_UNHANDLED_MAX = 5` per session.
|
|
226
|
+
- **Phase 2o — Skill-candidate.** A manual procedure repeated by hand
|
|
227
|
+
that a skill could absorb → a `skill-candidate` inbox item ("make a
|
|
228
|
+
skill?"). Capped at `SKILL_CANDIDATE_MAX = 3` per session.
|
|
229
|
+
- **Phase 2p — Checklist-catch.** A surfaced change-impact check that
|
|
230
|
+
caught a real bug → recorded to `checklist-stats.json` (the
|
|
231
|
+
catch-recording side; consumed by `/debrief`'s checklist-feedback and
|
|
232
|
+
the audit `checklist-pruning` phase). Not an inbox item — a stats write.
|
|
233
|
+
|
|
234
|
+
The two new inbox categories (`raised-unhandled`, `skill-candidate`)
|
|
235
|
+
follow the standard item schema and the same dedup-vs-corpora discipline
|
|
236
|
+
as the other Ring 3 extractions.
|
|
237
|
+
|
|
238
|
+
## Suppression Ledger (Ring 3 dedup observability)
|
|
239
|
+
|
|
240
|
+
The recall-fix program (audit act:7d6a4b3b) makes Ring 3's
|
|
241
|
+
knowledge-capture dedup **auditable**. Every time `isDuplicate`
|
|
242
|
+
suppresses an extraction, the suppression site appends one structured
|
|
243
|
+
record to a shared sidecar so over-suppression is visible instead of
|
|
244
|
+
vanishing into a write-only cron.
|
|
245
|
+
|
|
246
|
+
**File:** `state/suppression-ledger.jsonl` (under `WATCHTOWER_DIR`).
|
|
247
|
+
Append-only JSON-lines. NOT shipped as a template — generated at
|
|
248
|
+
runtime, like `checklist-stats.json` and `.memory-pressure.jsonl`.
|
|
249
|
+
|
|
250
|
+
**Producer:** `recordSuppression(record, opts)` in `watchtower-lib.mjs`
|
|
251
|
+
(shared home; reachable by every ring). Called from every Ring 3
|
|
252
|
+
suppression site — Phase 2d (extraction), Phase 2m (advisor close
|
|
253
|
+
pass), Phase 2n (raised-unhandled lens), Phase 2o (skill-candidate
|
|
254
|
+
lens). The human `log()` line stays beside it; the ledger is the
|
|
255
|
+
machine-readable twin, never a parse of the prose.
|
|
256
|
+
|
|
257
|
+
**Consumer:** the Ring 2 slow over-suppression canary (M5) — the ONLY
|
|
258
|
+
reader. It windows records by `ts`, computes a per-project
|
|
259
|
+
suppression-rate (suppressed / (suppressed + filed)) against a captured
|
|
260
|
+
post-M1a baseline, samples actual suppressed titles + what they matched
|
|
261
|
+
for human eyeball, tracks net-durably-saved (filed × approval-rate), and
|
|
262
|
+
writes `state/recall-canary.json`. The canary also prunes the ledger to
|
|
263
|
+
a retention window on each run (growth is bounded at canary cadence, not
|
|
264
|
+
on the suppression hot path). It is over-suppression-only and states so
|
|
265
|
+
in its own `note` field. Guards: `total===0 ⇒ no alert`; a minimum
|
|
266
|
+
denominator before a rate can fire; baseline captured on first run (else
|
|
267
|
+
it would alert on its own improvement).
|
|
268
|
+
|
|
269
|
+
**The surfacing path** (no write-only telemetry): the canary writes
|
|
270
|
+
`state/recall-canary.json` (per-project `{suppressed, filed, rate,
|
|
271
|
+
baseline, by_corpus, approval_rate, net_durably_saved, sample, alert}`);
|
|
272
|
+
**Ring 1** reads it and renders an alerting project's entry into that
|
|
273
|
+
project's **Standing Issues** (Ring 1 owns the per-project state file);
|
|
274
|
+
**/briefing**'s State-file-flags reader surfaces it. Three named hops,
|
|
275
|
+
each owning its tier. Flag: `defaults.recall_canary` (opt-out).
|
|
276
|
+
|
|
277
|
+
**Record shape (the contract):**
|
|
278
|
+
|
|
279
|
+
```json
|
|
280
|
+
{
|
|
281
|
+
"ts": "ISO-8601 timestamp of the suppression",
|
|
282
|
+
"project": "resolved project name (or null)",
|
|
283
|
+
"corpus": "memory | thread-cursor | pending | resolved | dismissed",
|
|
284
|
+
"suppressed_title": "the extraction title that was killed",
|
|
285
|
+
"matched_against": "the corpus entry it collided with",
|
|
286
|
+
"session_id": "the closing session's id (or null)"
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Invariants:**
|
|
291
|
+
|
|
292
|
+
- **Fail-open.** `recordSuppression` never throws. A failed append logs
|
|
293
|
+
one line and continues — a dropped ledger record must NEVER block an
|
|
294
|
+
extraction from filing. The ledger is observability, not the decision.
|
|
295
|
+
- **Additive.** New fields ride alongside; readers default missing
|
|
296
|
+
fields. The `corpus` enum grows when a new dedup corpus lands (M1a
|
|
297
|
+
added `thread-cursor` as a first-class corpus split off from
|
|
298
|
+
`memory`).
|
|
299
|
+
- **Baseline before behavior.** The ledger lands BEFORE any matcher
|
|
300
|
+
change so it records the OLD (pre-M1a) suppression behavior as the
|
|
301
|
+
before-baseline the canary compares against.
|
|
302
|
+
- **Over-suppression only.** The ledger sees what dedup KILLED, not what
|
|
303
|
+
extraction never proposed (M2 under-extraction). The canary states
|
|
304
|
+
this blindness in its own output.
|
|
305
|
+
|
|
114
306
|
## Deferred Schemas
|
|
115
307
|
|
|
116
308
|
The following are specified in project notes but NOT formalized until
|
|
@@ -195,3 +387,313 @@ They're complementary, not redundant.
|
|
|
195
387
|
staleness detection (stale work, memory hygiene). Where's the line
|
|
196
388
|
between R2's "is this work item stale?" and R4's "is this document
|
|
197
389
|
still true?" Is R4 an extension of R2 slow, or genuinely distinct?
|
|
390
|
+
|
|
391
|
+
## Ring 4 — Truth Reconciliation (built, stage 1)
|
|
392
|
+
|
|
393
|
+
**Status: BUILT (act:36dae795). Supersedes the design-phase section
|
|
394
|
+
above** — the open design questions there are now resolved here.
|
|
395
|
+
Implemented in `templates/scripts/watchtower-ring4.mjs` (the ring) +
|
|
396
|
+
`templates/scripts/watchtower-ring4-runner.sh` (PID-locked runner,
|
|
397
|
+
modeled on the Ring 2 runner). Hermetic suite:
|
|
398
|
+
`templates/scripts/__tests__/ring4-reconcile.test.mjs`. Cron/launchd
|
|
399
|
+
registration is a `/watchtower install` follow-up, not part of this
|
|
400
|
+
build.
|
|
401
|
+
|
|
402
|
+
### What it does
|
|
403
|
+
|
|
404
|
+
Periodically compares documentary CLAIMS against codebase REALITY and
|
|
405
|
+
files drift as `doc-drift` inbox items. Reconciliation is COMPARISON,
|
|
406
|
+
NOT generation — Ring 4 never rewrites a document; it flags drift with
|
|
407
|
+
enough context to fix, and the operator triages it. Stage 1 is PURELY
|
|
408
|
+
MECHANICAL (no Claude API call), which keeps the cron cheap and the
|
|
409
|
+
ring hermetically testable.
|
|
410
|
+
|
|
411
|
+
It owns two ledger duties at orient/debrief retirement
|
|
412
|
+
(`orient-debrief-coverage-ledger.md` §C/§D): documentation staleness
|
|
413
|
+
(CLAUDE.md key-file claims, briefing files, system-status) and the
|
|
414
|
+
dropped embedded-pulse description-accuracy spot checks (counts). The
|
|
415
|
+
standalone `/pulse` stays invocable on demand.
|
|
416
|
+
|
|
417
|
+
### Resolved design questions
|
|
418
|
+
|
|
419
|
+
- **Scope per run → rotation, not full sweep.** Each run reconciles at
|
|
420
|
+
most `RING4_PROJECT_CAP` (3) projects, least-recently-reconciled
|
|
421
|
+
first, BUT a project with commits since its last reconcile jumps the
|
|
422
|
+
queue. This is the nervous-system principle in mechanical form: R4
|
|
423
|
+
consumes R1's signal (git recency) to prioritize, rather than
|
|
424
|
+
blind-sweeping the portfolio. State in `state/ring4-reconcile.json`
|
|
425
|
+
(`last_run`, `projects_checked`).
|
|
426
|
+
- **Drift threshold → mechanical existence/counts, high precision.**
|
|
427
|
+
Stage 1 flags only (a) a backtick-fenced repo-relative path that is
|
|
428
|
+
missing on disk, gated so the path's TOP-LEVEL directory still
|
|
429
|
+
exists (kills illustrative/foreign-path noise — `webapp/...` in a
|
|
430
|
+
CC memory file is skipped because `webapp/` isn't a repo dir) and
|
|
431
|
+
not gitignored (a gitignored absent path is a generated/local
|
|
432
|
+
artifact — its absence is expected, not drift); and (b) a count
|
|
433
|
+
claim that disagrees with a mechanical file count, but ONLY for
|
|
434
|
+
config-declared count rules (zero-noise default). A "slightly
|
|
435
|
+
outdated line count" is deliberately NOT flagged.
|
|
436
|
+
- **Trigger vs schedule → weekly cadence + git-recency priority.** A
|
|
437
|
+
weekly gate (`RECONCILE_INTERVAL_DAYS`, mirrors Ring 2's roster
|
|
438
|
+
review) plus git-recency-driven project order, rather than coupling
|
|
439
|
+
to R1/R2 internal trigger files.
|
|
440
|
+
- **Relationship to Ring 2 → genuinely distinct ring.** R2 slow asks
|
|
441
|
+
"is this WORK ITEM stale?" (pib-db state); R4 asks "is this DOCUMENT
|
|
442
|
+
still true?" (doc claims vs codebase). Distinct cadence (weekly vs
|
|
443
|
+
30-min), distinct runner/cron, distinct produced category.
|
|
444
|
+
- **Cross-project → per-project only, stage 1.** A CC template
|
|
445
|
+
claiming something about consumer behavior is a documented future
|
|
446
|
+
extension, not built.
|
|
447
|
+
|
|
448
|
+
### The document set (current-reality docs only)
|
|
449
|
+
|
|
450
|
+
`collectDocuments()` scans docs consumed AS TRUTH, capped at
|
|
451
|
+
`RING4_DOC_CAP` (40): root `CLAUDE.md`, `system-status.md` (root and
|
|
452
|
+
`.claude/`), `.claude/cabinet/_briefing*.md`, and the project's
|
|
453
|
+
built-in memory entries (`MEMORY.md` + curated `*.md`). It
|
|
454
|
+
deliberately EXCLUDES `.claude/plans/` and `.claude/methodology/`:
|
|
455
|
+
those are HISTORICAL records — a plan for retired work SHOULD
|
|
456
|
+
reference now-deleted files, so path-existence over them is mostly
|
|
457
|
+
noise (the CC dogfood produced ~90 false claims from historical plans;
|
|
458
|
+
filtering to current-reality docs + gitignore-awareness cut it to 2
|
|
459
|
+
real findings). Whether a plan is actually DONE — plan-to-reality
|
|
460
|
+
drift — is a distinct SEMANTIC check, deferred.
|
|
461
|
+
|
|
462
|
+
### Memory is DRIFT-SUPERSEDE only
|
|
463
|
+
|
|
464
|
+
The memory pass is the missing-path reconciler applied to memory files
|
|
465
|
+
("is what we wrote still true?" — entries referencing renamed/removed
|
|
466
|
+
files). Paths in a memory file resolve against the PROJECT root, not
|
|
467
|
+
the memory dir. Memory HYGIENE (budget / decay / consolidate) stays
|
|
468
|
+
Ring 2 slow's `runMemoryHygiene` / `surfacePersistentViolations` —
|
|
469
|
+
Ring 4 does NOT fork a second memory-health producer (act:36dae795
|
|
470
|
+
2026-06-16 scope note; design in `cross-store-memory-redesign.md`).
|
|
471
|
+
|
|
472
|
+
### Inbox contract
|
|
473
|
+
|
|
474
|
+
Findings file `doc-drift` items (`filed_by: 'ring4'`, urgency `low`,
|
|
475
|
+
three triage options: fix-doc / fix-code / dismiss). Dedup is ONE
|
|
476
|
+
pending item per `evidence.drift_key` per project — `path:<doc>` for
|
|
477
|
+
missing-path, `count:<doc>:<label>` for count-mismatch (mirrors Ring
|
|
478
|
+
2's `roster_kind` convention: a still-open drift item is a reference,
|
|
479
|
+
not a duplicate). At most `RING4_ITEM_CAP` (8) items filed per run.
|
|
480
|
+
`doc-drift` is NOT a dispatched or gated category — it is a plain
|
|
481
|
+
inbox item the operator triages. The weekly cadence + per-doc dedup
|
|
482
|
+
keep re-nagging bounded.
|
|
483
|
+
|
|
484
|
+
### Config
|
|
485
|
+
|
|
486
|
+
Feature flag `defaults.truth_reconciliation` (default true) gates the
|
|
487
|
+
whole ring; `ring_frequencies.ring4` (604800 = weekly) carries the
|
|
488
|
+
intended cron interval. Count rules are per-project and opt-in:
|
|
489
|
+
`projects[<name>].reconcile.count_rules = [{ label, doc, pattern,
|
|
490
|
+
glob }]` where `pattern` is a regex source with one capture group
|
|
491
|
+
around the number and `glob` uses the small dependency-free grammar in
|
|
492
|
+
`countGlob` (`dir/*`, `dir/prefix-*/`, `dir/*.ext`, `dir/**/*.ext`).
|
|
493
|
+
Default empty → the count pass no-ops (no false drift).
|
|
494
|
+
|
|
495
|
+
### Deferred (future stages)
|
|
496
|
+
|
|
497
|
+
Semantic comparison (does this prose paragraph still describe the
|
|
498
|
+
code?), plan-to-reality / plan-status reconciliation, dependency-
|
|
499
|
+
version drift beyond the count-rule mechanism, gitignored-directory
|
|
500
|
+
patterns that don't match a non-existent path, and cross-project
|
|
501
|
+
template-vs-consumer reconciliation.
|
|
502
|
+
|
|
503
|
+
## Consumer Hook Contract (Plan 9)
|
|
504
|
+
|
|
505
|
+
The formalized extension model for **non-interactive** consumer customs at
|
|
506
|
+
the ring lifecycle seams (act:4a6e907c). Supersedes the deferred-schema
|
|
507
|
+
placeholder `hooks/<ring>-<phase>.d/` above — the real location is
|
|
508
|
+
per-project, not a global runtime dir. Interactive customs are owned by
|
|
509
|
+
routine dispatch (act:c2a55c08, "Routine Dispatch" above), never by hooks.
|
|
510
|
+
|
|
511
|
+
### Seams and invocation
|
|
512
|
+
|
|
513
|
+
The rings already invoke consumer hooks by running each command string in
|
|
514
|
+
`config.hooks[<seam>]` with the ring's state JSON on stdin. Three seams:
|
|
515
|
+
|
|
516
|
+
| Seam | Ring / scope | Outer timeout |
|
|
517
|
+
|------|--------------|---------------|
|
|
518
|
+
| `ring1-post-collect` | Ring 1, per project, post state-collect | 30s |
|
|
519
|
+
| `ring2-slow-post` | Ring 2 slow tier, portfolio-wide | 60s |
|
|
520
|
+
| `ring3-close-post` | Ring 3, per session close | 120s |
|
|
521
|
+
|
|
522
|
+
`watchtower-hook-runner.mjs --seam <seam>` is registered ONCE per seam (via
|
|
523
|
+
`--register`, called by `/watchtower install`). The rings invoke it like any
|
|
524
|
+
hook command — the rings are NOT modified. The runner then resolves the
|
|
525
|
+
target project(s) and runs that project's hook scripts.
|
|
526
|
+
|
|
527
|
+
### Registration (drop-a-file)
|
|
528
|
+
|
|
529
|
+
A consumer registers a hook per-project by placing an executable script in
|
|
530
|
+
`<project-root>/.claude/watchtower/hooks/<seam>/`. Presence is registration —
|
|
531
|
+
no config editing. Discovery is name-sorted (numeric-prefix convention:
|
|
532
|
+
`10-foo.sh` before `20-bar.sh`). A file is runnable if it has the executable
|
|
533
|
+
bit (run directly, shebang decides the interpreter) or a known extension
|
|
534
|
+
(`.sh`/`.bash` → bash, `.mjs`/`.js`/`.cjs` → node, `.py` → python3); other
|
|
535
|
+
files (`.json`, `.md`, …) are ignored. Files prefixed `_` or `.` are skipped
|
|
536
|
+
(disable convention).
|
|
537
|
+
|
|
538
|
+
### Project resolution
|
|
539
|
+
|
|
540
|
+
- Single-project seams (`ring1-post-collect`, `ring3-close-post`): the project
|
|
541
|
+
is taken from the ring's stdin state (`.path`/`.name`, or `.project_path`/
|
|
542
|
+
`.project`).
|
|
543
|
+
- Portfolio seam (`ring2-slow-post`): the slow state carries no project, so the
|
|
544
|
+
runner enumerates `config.projects` and runs each project's seam dir.
|
|
545
|
+
|
|
546
|
+
### Hook I/O contract
|
|
547
|
+
|
|
548
|
+
A hook script receives:
|
|
549
|
+
- **stdin:** the ring's state JSON, verbatim (see the seam table for shape).
|
|
550
|
+
- **cwd:** the project root.
|
|
551
|
+
- **env:** `WATCHTOWER_SEAM`, `WATCHTOWER_PROJECT`, `WATCHTOWER_PROJECT_PATH`,
|
|
552
|
+
`WATCHTOWER_SESSION_ID` (ring3), `WATCHTOWER_HOOK_DIR`.
|
|
553
|
+
|
|
554
|
+
A hook script returns:
|
|
555
|
+
- **exit 0** = success; non-zero = failure (isolated, recorded, never fatal).
|
|
556
|
+
- **stdout:** optional JSON. `{"additional_checks":[…]}` is the Ring 1
|
|
557
|
+
passthrough — the runner surfaces those at the envelope top level so Ring
|
|
558
|
+
1's existing `parsed.additional_checks` consumption works. Other JSON is
|
|
559
|
+
captured as structured `output`; non-JSON is captured verbatim (truncated).
|
|
560
|
+
- **stderr:** captured for diagnostics; non-fatal.
|
|
561
|
+
|
|
562
|
+
### Isolation and budget
|
|
563
|
+
|
|
564
|
+
The runner is bulletproof: any single hook's failure (non-zero exit, timeout,
|
|
565
|
+
spawn error) becomes a typed status (`failed` / `timeout`) and never aborts
|
|
566
|
+
the loop, and the runner always prints a valid envelope and exits 0 — one bad
|
|
567
|
+
consumer hook can never break a ring tick. Because the runner is a single
|
|
568
|
+
command sharing the ring's one timeout envelope, it runs hooks sequentially
|
|
569
|
+
against a deadline (the seam timeout minus headroom) and marks any hook it
|
|
570
|
+
can't reach in time `skipped` (reason `budget-exhausted`) — no silent
|
|
571
|
+
truncation. `WATCHTOWER_HOOK_BUDGET_MS` overrides the per-seam budget.
|
|
572
|
+
|
|
573
|
+
### Output envelope
|
|
574
|
+
|
|
575
|
+
```json
|
|
576
|
+
{ "schema_version": 1, "seam": "<seam>",
|
|
577
|
+
"targets": [{ "name": "...", "path": "..." }],
|
|
578
|
+
"hooks": [{ "hook": "<path>", "project": "...", "status": "success|failed|timeout|skipped", "output"?: ..., "exit"?: N, "stderr"?: "...", "reason"?: "budget-exhausted" }],
|
|
579
|
+
"additional_checks"?: [ ... ] }
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Migration shim
|
|
583
|
+
|
|
584
|
+
`watchtower-phase-shim.mjs --phase <path> [--cwd <dir>] [--seam <seam>]` is the
|
|
585
|
+
adapter for an EXISTING repo script (a former orient/debrief phase) that
|
|
586
|
+
expects the project root as cwd rather than reading stdin. Registered as a one
|
|
587
|
+
-line `config.hooks[<seam>]` entry, it runs the target with the same cwd/env/
|
|
588
|
+
stdin normalization and passes JSON stdout through transparently (preserving
|
|
589
|
+
`additional_checks`), propagating the target's exit code. New hooks prefer the
|
|
590
|
+
drop-in directory; the shim is for scripts that already exist.
|
|
591
|
+
|
|
592
|
+
## Decision Lineage (read-only projection — Plan 12, act:a2efc0ce)
|
|
593
|
+
|
|
594
|
+
The `/briefing lineage` drill-down answers "what past decisions still
|
|
595
|
+
constrain THIS project, and what's reversible?" It is a **projection** over
|
|
596
|
+
existing stores, never a new store, and it **writes nothing** — no state file,
|
|
597
|
+
no inbox item, no memory write. This is the structural invariant: the engine
|
|
598
|
+
holds no persisted copy, so it cannot drift from its sources.
|
|
599
|
+
|
|
600
|
+
`watchtower-narrative-corpus.mjs` is the read-only substrate helper. It covers
|
|
601
|
+
the two watchtower-NATIVE stores so it needs no auto-memory-dir resolver:
|
|
602
|
+
|
|
603
|
+
- `assembleNarrativeCorpus({cwd})` — current project ONLY (resolved
|
|
604
|
+
worktree-aware via `resolveProjectIdentity`; no free `project` arg, so the
|
|
605
|
+
five-namespace keying footgun can't arise). Returns `inbox_decisions`
|
|
606
|
+
(knowledge-extraction items; `resolution_type: 'captured-to-memory'`
|
|
607
|
+
duplicates dropped so a decision that became a memory file isn't
|
|
608
|
+
double-counted; `superseded` status KEPT — it is the supersession view's
|
|
609
|
+
own subject), `thread_cursors` (full `cursor_history` timelines),
|
|
610
|
+
`per_store_counts`, `skipped_unresolved` (phantom-key items repo-wide that
|
|
611
|
+
the resolver couldn't attribute — surfaced, never silently dropped),
|
|
612
|
+
`time_span`. CLI: `--corpus --project-path <cwd>`.
|
|
613
|
+
- `parseDecisionEdges(markdown)` (pure) + `readMemoryDecisions(dir)` — the
|
|
614
|
+
memory/methodology/pib stores are read by the `/briefing` CONSUMER (which
|
|
615
|
+
resolves their paths), but the EDGE RULE lives here, tested: a
|
|
616
|
+
`**Supersedes:**` block is a RECORDED directional edge; a `[[wikilink]]` is
|
|
617
|
+
an undirected see-also and is **never** rendered as a supersession. CLI:
|
|
618
|
+
`--memory <consumer-resolved-dir>`.
|
|
619
|
+
|
|
620
|
+
Provenance is load-bearing because a narrative is a trust surface: the render
|
|
621
|
+
labels *recorded* edges (parsed `Supersedes:` / pib `absorbs act:`) distinctly
|
|
622
|
+
from *inferred* ones (model-read from prose), and never presents an inferred
|
|
623
|
+
link as recorded history. Deliberately a SIBLING of `watchtower-snapshot.mjs`
|
|
624
|
+
(same reader family, same hermetic-test shape), touching zero ring code. Tests:
|
|
625
|
+
`__tests__/narrative-corpus.test.mjs`. Staged: the machine-readable
|
|
626
|
+
supersede-edge discipline (making edges consistent + live) is deferred and
|
|
627
|
+
MERGES INTO the cross-store `supersede` verb
|
|
628
|
+
(`.claude/plans/cross-store-memory-redesign.md`, Ring 2-slow), not a fork.
|
|
629
|
+
|
|
630
|
+
## Cross-Ring Reader (read-only projection — Stage 1, act:f5814d6c)
|
|
631
|
+
|
|
632
|
+
`watchtower-cross-ring-reader.mjs` is the synthesis SUBSTRATE for `/briefing`:
|
|
633
|
+
one call assembles, per configured project, what every ring produced —
|
|
634
|
+
Ring 1's parsed state-file sections + git-attention facts, Ring 3's recent
|
|
635
|
+
session summaries + thread cursor timelines, and the inbox's pending counts +
|
|
636
|
+
resolution history (`resolution_mix`: what the operator's dispositions
|
|
637
|
+
actually engaged vs discarded). The script GATHERS; the consumer SYNTHESIZES —
|
|
638
|
+
no convergence scoring or cross-store "insight" is computed here (a
|
|
639
|
+
mechanical projection on a trust surface must not fabricate synthesis). Like
|
|
640
|
+
its siblings it is a **projection** over existing stores, never a new store,
|
|
641
|
+
and it **writes nothing** — no write path exists in the module; the hermetic
|
|
642
|
+
test asserts the filesystem is untouched.
|
|
643
|
+
|
|
644
|
+
**Per-store join keys** (name = `config.projects` key, slug = `slugify(name)`;
|
|
645
|
+
the reader derives both exactly as `resolveProjectIdentity` does, parity-tested):
|
|
646
|
+
|
|
647
|
+
| Store | Located by | Key |
|
|
648
|
+
|-------|-----------|-----|
|
|
649
|
+
| Ring 1 state file | `state/projects/<slug>.md` | slug |
|
|
650
|
+
| Ring 3 sessions | `state/projects/<slug>/sessions/*.md` | slug |
|
|
651
|
+
| Threads | `sessions[].project` via `threadMatchesProject` | slug |
|
|
652
|
+
| git-attention facts | `fact.project` | name |
|
|
653
|
+
| Inbox items | `item.project` via `listItems` | name |
|
|
654
|
+
|
|
655
|
+
**Ring 2 has no dedicated block by design.** Its durable outputs already
|
|
656
|
+
arrive through two covered channels — pattern-promotion items ride the inbox,
|
|
657
|
+
and the recall canary is projected into Ring 1's Standing Issues — so a
|
|
658
|
+
dedicated block would double-report inside one output. Enrichment BODIES stay
|
|
659
|
+
behind the Enrichment Directory contract (`/inbox` reads them per-item).
|
|
660
|
+
|
|
661
|
+
**Honesty fields** (the complement of the config-driven walk — nothing falls
|
|
662
|
+
silently between views): `unattributed_items` (partitioned on config-key
|
|
663
|
+
MEMBERSHIP, not the `project_unresolved` flag), `orphan_threads`,
|
|
664
|
+
`orphaned_state_files`, `duplicate_path_warning` (realpath-compared),
|
|
665
|
+
per-view `path_exists` + `attributed_but_flagged`, and windowing counts
|
|
666
|
+
(`sessions_total` / `sessions_in_window` / `sessions_included`). Sessions
|
|
667
|
+
order by each file's content `Date:` line (same-day filenames sort randomly
|
|
668
|
+
by uuid). `superseded`/`expired` items never stamp `resolved_at`, so they are
|
|
669
|
+
reported as filed-date-window counts, never blended into the disposition-time
|
|
670
|
+
`resolution_mix`; untyped `resolution_type` lands in `untyped_or_other`,
|
|
671
|
+
counted, never dropped.
|
|
672
|
+
|
|
673
|
+
**Output-size caps, always with totals** (CP3: an uncapped live portfolio
|
|
674
|
+
measured 1.37 MB — no consumer channel carries that): thread histories keep
|
|
675
|
+
the newest `THREAD_HISTORY_CAP` (3) snapshots with per-thread `history_total`
|
|
676
|
+
(the full evolution is lineage mode's job — `projectCursorTimeline` itself is
|
|
677
|
+
uncapped); the resolution-event list keeps the newest `RESOLUTION_EVENTS_CAP`
|
|
678
|
+
(20) with `resolution_events_total` while `resolution_mix` counts ALL events.
|
|
679
|
+
The consumer sees "3 of 85", never a silently complete-looking 3. The
|
|
680
|
+
briefing consumes by writing `--portfolio` to a temp file and Reading slices —
|
|
681
|
+
never by echoing JSON through a truncating shell channel. `--project` accepts
|
|
682
|
+
the config-key name or a UNIQUE slug match (the /briefing scope filter is
|
|
683
|
+
slug-shaped).
|
|
684
|
+
|
|
685
|
+
**Consumer:** `/briefing` Step 1d — the reader is the REPLACEMENT gather path
|
|
686
|
+
for the old per-store state/thread reads (with a raw-read fallback for
|
|
687
|
+
consumers that haven't reinstalled), and its output feeds Step 3a chunk
|
|
688
|
+
synthesis and Step 4 cross-cutting synthesis. Pending counts are a narration
|
|
689
|
+
snapshot; the briefing's live `listPending()` load remains the disposition
|
|
690
|
+
source of truth. The 4-field cursor projection (`projectCursorTimeline`) has
|
|
691
|
+
its ONE definition here; `watchtower-narrative-corpus.mjs` imports it
|
|
692
|
+
(watchtower-lib is ring-loaded and soak-frozen, so the shared helper lives in
|
|
693
|
+
the non-ring reader). CLI: `--portfolio | --project <name> | --project-path
|
|
694
|
+
<cwd>`, `--since <ISO|Ndays>` (default 14d, UTC). Tests:
|
|
695
|
+
`__tests__/cross-ring-reader.test.mjs`.
|
|
696
|
+
|
|
697
|
+
**Staged:** precompute-and-cache (a Ring 2 slow-tier phase persisting the
|
|
698
|
+
view) is Stage 2, deferred until the recall-canary soak lifts; consolidating
|
|
699
|
+
the cursor projection into watchtower-lib waits for the same gate.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Worktree-Invocation Contract
|
|
2
|
+
|
|
3
|
+
The shared, universal preamble for any skill that may run from a
|
|
4
|
+
mux/linked git worktree. `/execute` and `/execute-group` both **read
|
|
5
|
+
this file and follow it** at their Step 0 rather than each spelling the
|
|
6
|
+
mechanism — so the detect/sync/artifact rules change in one place.
|
|
7
|
+
|
|
8
|
+
Running a skill from a linked worktree is the **normal case**, not an
|
|
9
|
+
error — never refuse. Establish these three facts first; everything
|
|
10
|
+
caller-specific (how a particular skill merges, whether it spawns
|
|
11
|
+
worktree agents) lives in the caller's own Step 0, AFTER it reads this.
|
|
12
|
+
|
|
13
|
+
## 1. Detect where you are
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
COMMON="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"
|
|
17
|
+
TOP="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
18
|
+
# In a linked worktree the common .git lives under the MAIN checkout, so
|
|
19
|
+
# dirname(COMMON) != TOP. On the main checkout they are equal.
|
|
20
|
+
if [ -n "$COMMON" ] && [ "$(dirname "$COMMON")" != "$TOP" ]; then
|
|
21
|
+
: # linked worktree — sync + artifact rules below apply
|
|
22
|
+
fi
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`--path-format=absolute` is load-bearing: from the main checkout
|
|
26
|
+
`--git-common-dir` is a bare relative `.git`, and a string compare /
|
|
27
|
+
`dirname` on the relative form misclassifies. Force absolute on the
|
|
28
|
+
read. (Same trap the pib-db path resolver and the completion-gate hook
|
|
29
|
+
both handle.)
|
|
30
|
+
|
|
31
|
+
## 2. Sync with main before anything else (worktree only)
|
|
32
|
+
|
|
33
|
+
A worktree starts behind — plan text, code, and the staleness/CP guards
|
|
34
|
+
all read state that may have moved on main since the worktree was cut.
|
|
35
|
+
If the branch is behind main:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git merge main --no-edit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**On conflict, HALT the whole skill — do not auto-resolve and do not
|
|
42
|
+
proceed to implement against a half-synced tree.** This is an
|
|
43
|
+
interactive skill; resolution is the operator's call. Leave the worktree
|
|
44
|
+
exactly as the conflicted merge left it (conflict markers, an in-progress
|
|
45
|
+
merge) and surface, in plain English:
|
|
46
|
+
|
|
47
|
+
> Sync with main hit a conflict in `<files>`. The worktree is mid-merge.
|
|
48
|
+
> Resolve the conflicts and `git commit` the merge — **or** `git merge
|
|
49
|
+
> --abort` to back out — then re-run the skill. I stopped before
|
|
50
|
+
> implementing so nothing lands on a half-synced tree.
|
|
51
|
+
|
|
52
|
+
## 3. Completion artifacts land in the MAIN checkout
|
|
53
|
+
|
|
54
|
+
A worktree's `.claude/verification/` is gitignored and disposable —
|
|
55
|
+
anything written there is lost on worktree cleanup (the lost-breadcrumb
|
|
56
|
+
failure: `lesson_workflow_report_worktree_cwd`). Resolve the MAIN
|
|
57
|
+
checkout once and write every verification artifact under it:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
MAIN="$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")"
|
|
61
|
+
# breadcrumbs, reports → "$MAIN/.claude/verification/<fid>.json" (never cwd-relative)
|
|
62
|
+
mkdir -p "$MAIN/.claude/verification"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
On the main checkout `$MAIN` resolves to the cwd, so the same line is
|
|
66
|
+
correct in both places — no on-main special case. The completion-gate
|
|
67
|
+
hook already READS breadcrumbs from `$MAIN/.claude/verification/`; this
|
|
68
|
+
makes the WRITE side agree.
|
|
69
|
+
|
|
70
|
+
**Sibling copies of this one git-truth — keep them in step:** the
|
|
71
|
+
`MAIN_RESOLVE` string constant in
|
|
72
|
+
`templates/workflows/execute-group-implement.js`, and the shell spelling
|
|
73
|
+
in `templates/hooks/action-completion-gate.sh` (a hook can't read this
|
|
74
|
+
doc). Three copies, one resolution; change one, change all three.
|
|
75
|
+
|
|
76
|
+
## What this contract does NOT cover
|
|
77
|
+
|
|
78
|
+
Caller-specific orchestration stays in the caller's own Step 0, never
|
|
79
|
+
here:
|
|
80
|
+
|
|
81
|
+
- **`/execute-group`** keeps inline: that its `isolation: worktree`
|
|
82
|
+
agents branch from and merge into MAIN regardless of the invoking cwd,
|
|
83
|
+
and its inline-build escape hatch. These describe the workflow's merge
|
|
84
|
+
model and would contradict `/execute`'s no-merge model — they are not
|
|
85
|
+
universal.
|
|
86
|
+
- **`/execute`** keeps inline: that it does NOT merge (the worktree→main
|
|
87
|
+
bridge is `/qa-handoff`), and how its close-out files the handoff.
|
|
@@ -117,6 +117,17 @@ timelog. The running total appears only in a principal's update — never
|
|
|
117
117
|
in a delegate's. Billing can be scoped by period: the full engagement
|
|
118
118
|
total, the current month, or only hours since the last sync.
|
|
119
119
|
|
|
120
|
+
**Invoicing.** `/collab-consultant invoice [YYYY-MM]` renders a clean
|
|
121
|
+
invoice document from a month's tracked hours (defaults to the last full
|
|
122
|
+
calendar month; `--itemized` for one line per timelog row instead of a
|
|
123
|
+
single consolidated line). The document is built for upload to the
|
|
124
|
+
operator's bank (Relay), whose "Create Invoice → Autofill from document"
|
|
125
|
+
extracts the customer, line items, and total — so the document carries
|
|
126
|
+
no invoice number and no From/bank block (Relay supplies those). It's
|
|
127
|
+
written to `invoices/<YYYY-MM>.html` with a best-effort PDF conversion.
|
|
128
|
+
Relay defaults the due date to today, so the skill reminds you to set the
|
|
129
|
+
Net-30 due date by hand after autofill.
|
|
130
|
+
|
|
120
131
|
### Credential handoff
|
|
121
132
|
|
|
122
133
|
When a client needs to provide a secret (API key, token, password):
|
|
@@ -142,6 +153,7 @@ When a client needs to provide a secret (API key, token, password):
|
|
|
142
153
|
| `/engagement-message` | Consultant | Send a free-text note to a recipient |
|
|
143
154
|
| `/engagement-sync` | Consultant | Push updates to recipients (with preview + confirm) + pull feedback |
|
|
144
155
|
| `/engagement-status` | Consultant | Dashboard: previews, feedback inbox, billing, tag warnings |
|
|
156
|
+
| `/collab-consultant invoice` | Consultant | Render a month's invoice document for upload to Relay's autofill |
|
|
145
157
|
| `/engagement` | Client | Review the latest update and respond |
|
|
146
158
|
| `/engagement-progress` | Client | Quick glance — what's new, counts, read-only |
|
|
147
159
|
|