create-claude-cabinet 0.44.0 → 0.46.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 (90) hide show
  1. package/README.md +9 -4
  2. package/lib/cli.js +77 -6
  3. package/lib/copy.js +56 -10
  4. package/lib/engagement-server-setup.js +34 -9
  5. package/lib/migrate-from-omega.js +13 -1
  6. package/lib/mux-setup.js +34 -9
  7. package/lib/watchtower-setup.js +210 -0
  8. package/package.json +5 -1
  9. package/templates/cabinet/_cabinet-member-template.md +8 -3
  10. package/templates/cabinet/advisories-state-schema.md +34 -7
  11. package/templates/cabinet/checklist-stats-schema.md +104 -0
  12. package/templates/cabinet/checkpoint-protocol.md +17 -5
  13. package/templates/cabinet/composition-patterns.md +4 -3
  14. package/templates/cabinet/qa-dimensions-template.yaml +7 -0
  15. package/templates/cabinet/skill-output-conventions.md +35 -1
  16. package/templates/cabinet/watchtower-contracts.md +126 -0
  17. package/templates/engagement/pib-db-patches/pib-db-lib.mjs +14 -2
  18. package/templates/hooks/action-completion-gate.sh +17 -0
  19. package/templates/hooks/watchtower-session-start.sh +80 -5
  20. package/templates/mux/__tests__/claude-carveout.fixture.sh +136 -0
  21. package/templates/mux/__tests__/claude-carveout.test.mjs +38 -0
  22. package/templates/mux/__tests__/mux-fail-loud.fixture.sh +298 -0
  23. package/templates/mux/__tests__/mux-fail-loud.test.mjs +41 -0
  24. package/templates/mux/__tests__/station-liveness.fixture.sh +234 -0
  25. package/templates/mux/__tests__/station-liveness.test.mjs +47 -0
  26. package/templates/mux/__tests__/worktree-dirty-check.fixture.sh +184 -0
  27. package/templates/mux/__tests__/worktree-dirty-check.test.mjs +35 -0
  28. package/templates/mux/bin/mux +485 -107
  29. package/templates/mux/config/worktree-cleanup.sh +55 -9
  30. package/templates/mux/config/worktree-dirty-check.sh +128 -0
  31. package/templates/mux/config/worktree-session-health.sh +62 -35
  32. package/templates/scripts/__tests__/advisor-pass.test.mjs +238 -0
  33. package/templates/scripts/__tests__/advisories.test.mjs +262 -0
  34. package/templates/scripts/__tests__/batch-disposition.test.mjs +137 -0
  35. package/templates/scripts/__tests__/feedback-outbox-flush.test.mjs +232 -0
  36. package/templates/scripts/__tests__/qa-handoff-aging.e2e.test.mjs +108 -0
  37. package/templates/scripts/__tests__/qa-handoff-gate.test.mjs +403 -0
  38. package/templates/scripts/__tests__/resolve-project.test.mjs +144 -0
  39. package/templates/scripts/__tests__/ring-state-ownership.test.mjs +333 -0
  40. package/templates/scripts/__tests__/ring2-thread-context.test.mjs +189 -0
  41. package/templates/scripts/__tests__/ring3-dedup.test.mjs +387 -0
  42. package/templates/scripts/__tests__/routine-dispatch.test.mjs +312 -0
  43. package/templates/scripts/pib-db-lib.mjs +4 -1
  44. package/templates/scripts/pib-db.mjs +4 -1
  45. package/templates/scripts/validate-memory.mjs +6 -2
  46. package/templates/scripts/watchtower-advisories.mjs +305 -0
  47. package/templates/scripts/watchtower-build-context.mjs +122 -19
  48. package/templates/scripts/watchtower-lib.mjs +441 -2
  49. package/templates/scripts/watchtower-migrate-keys.mjs +305 -0
  50. package/templates/scripts/watchtower-queue.mjs +372 -2
  51. package/templates/scripts/watchtower-ring1.mjs +138 -2
  52. package/templates/scripts/watchtower-ring2.mjs +122 -23
  53. package/templates/scripts/watchtower-ring3-close.mjs +558 -137
  54. package/templates/scripts/watchtower-routines.mjs +358 -0
  55. package/templates/scripts/watchtower-status.sh +1 -1
  56. package/templates/skills/audit/SKILL.md +30 -7
  57. package/templates/skills/audit/phases/checklist-pruning.md +108 -0
  58. package/templates/skills/briefing/SKILL.md +342 -223
  59. package/templates/skills/cabinet/SKILL.md +2 -2
  60. package/templates/skills/cabinet-anthropic-insider/SKILL.md +14 -6
  61. package/templates/skills/cabinet-historian/SKILL.md +14 -11
  62. package/templates/skills/cabinet-system-advocate/SKILL.md +22 -21
  63. package/templates/skills/cabinet-user-advocate/SKILL.md +13 -7
  64. package/templates/skills/cc-publish/SKILL.md +105 -19
  65. package/templates/skills/collab-consultant/SKILL.md +1 -1
  66. package/templates/skills/debrief/SKILL.md +160 -15
  67. package/templates/skills/debrief/phases/checklist-feedback.md +10 -3
  68. package/templates/skills/debrief/phases/qa-handoff-sweep.md +78 -0
  69. package/templates/skills/engagement-create/SKILL.md +1 -1
  70. package/templates/skills/engagement-help/SKILL.md +1 -1
  71. package/templates/skills/execute/SKILL.md +7 -1
  72. package/templates/skills/execute/phases/post-impl-checklist.md +18 -0
  73. package/templates/skills/execute-group/SKILL.md +76 -24
  74. package/templates/skills/inbox/SKILL.md +97 -13
  75. package/templates/skills/orient/SKILL.md +168 -52
  76. package/templates/skills/orient/phases/checklist-status.md +12 -0
  77. package/templates/skills/plan/SKILL.md +22 -6
  78. package/templates/skills/qa-drain/SKILL.md +119 -0
  79. package/templates/skills/qa-handoff/SKILL.md +132 -5
  80. package/templates/skills/session-handoff/SKILL.md +334 -0
  81. package/templates/skills/setup-accounts/SKILL.md +1 -1
  82. package/templates/skills/triage-audit/SKILL.md +6 -0
  83. package/templates/skills/unwrap/SKILL.md +1 -1
  84. package/templates/skills/verify/SKILL.md +2 -2
  85. package/templates/skills/watchtower/SKILL.md +64 -1
  86. package/templates/watchtower/config.json.template +3 -1
  87. package/templates/watchtower/queue/items/item.json.schema +10 -1
  88. package/templates/workflows/deliberative-audit.js +3 -0
  89. package/templates/workflows/execute-group-complete.js +93 -16
  90. package/templates/workflows/execute-group-implement.js +164 -19
@@ -0,0 +1,210 @@
1
+ /**
2
+ * watchtower-setup.js — keep the GLOBAL watchtower runtime fresh on reinstall.
3
+ *
4
+ * The watchtower module copies its files into the PROJECT (the normal module
5
+ * copy in lib/cli.js). But the GLOBAL runtime at ~/.claude-cabinet/watchtower/
6
+ * is only ever set up by the manual `/watchtower install` SKILL.md one-time
7
+ * step. So `npx create-claude-cabinet` (reinstall) refreshed the project files
8
+ * but NOT the global runtime scripts/docs/hooks — they went stale, and a
9
+ * brand-new runtime script (e.g. watchtower-advisories.mjs) never appeared
10
+ * until a hand-copy.
11
+ *
12
+ * This installer mirrors lib/mux-setup.js's content-aware refresh: a global
13
+ * SHA256 manifest at ~/.claude-cabinet/global-manifest.json records each
14
+ * dest's last-written hash, and only changed-or-new files are copied. Reusing
15
+ * mux's manifest is safe — entries are keyed by absolute dest path, so the
16
+ * watchtower dests (~/.claude-cabinet/watchtower/...) never collide with mux's
17
+ * (~/.config/mux/..., ~/.local/bin/...).
18
+ *
19
+ * REFRESH-ONLY semantics (critical): if the runtime directory does NOT already
20
+ * exist, this returns immediately with status 'absent' and writes nothing.
21
+ * Fresh runtime setup — launchd plist / cron, config.json, migrate-keys, the
22
+ * coherence assertion — is the `/watchtower install` SKILL.md step's job and is
23
+ * NOT replicated here. This function only keeps the code/docs/hooks of an
24
+ * EXISTING runtime current.
25
+ *
26
+ * The .mjs script set is globbed from templates/scripts/watchtower-*.mjs at
27
+ * load time (single source of truth — a newly shipped runtime script is picked
28
+ * up automatically; that "new file appears" case was the watchtower-advisories
29
+ * failure). The shell runners, session hooks, and cabinet docs are mapped
30
+ * explicitly since they live in different template subtrees and land in
31
+ * different runtime subdirs.
32
+ */
33
+
34
+ const fs = require('fs');
35
+ const path = require('path');
36
+ const os = require('os');
37
+ const crypto = require('crypto');
38
+
39
+ const CC_HOME = path.join(os.homedir(), '.claude-cabinet');
40
+ const GLOBAL_MANIFEST_PATH = path.join(CC_HOME, 'global-manifest.json');
41
+ const RUNTIME_DIR = path.join(CC_HOME, 'watchtower');
42
+ const RUNTIME_SCRIPTS_DIR = path.join(RUNTIME_DIR, 'scripts');
43
+ const RUNTIME_HOOKS_DIR = path.join(RUNTIME_DIR, 'hooks');
44
+ const RUNTIME_CABINET_DIR = path.join(RUNTIME_DIR, 'cabinet');
45
+ const TEMPLATE_DIR = path.resolve(__dirname, '..', 'templates');
46
+
47
+ /**
48
+ * Build the MANAGED_FILES list: { src (absolute), dest (absolute), mode? }.
49
+ *
50
+ * - ALL templates/scripts/watchtower-*.mjs → runtime scripts/
51
+ * - the watchtower shell runners under scripts/ → runtime scripts/ (0o755)
52
+ * - templates/hooks/watchtower-session-*.sh → runtime hooks/ (0o755)
53
+ * - the two cabinet docs → runtime cabinet/
54
+ *
55
+ * Globbing the .mjs set keeps it the single source of truth — no hand-picked
56
+ * subset that can drift as new runtime scripts ship.
57
+ */
58
+ function buildManagedFiles() {
59
+ const files = [];
60
+ const scriptsDir = path.join(TEMPLATE_DIR, 'scripts');
61
+
62
+ // All watchtower runtime .mjs scripts (globbed — complete set, no drift).
63
+ if (fs.existsSync(scriptsDir)) {
64
+ for (const name of fs.readdirSync(scriptsDir)) {
65
+ if (/^watchtower-.*\.mjs$/.test(name)) {
66
+ files.push({
67
+ src: path.join(scriptsDir, name),
68
+ dest: path.join(RUNTIME_SCRIPTS_DIR, name),
69
+ });
70
+ }
71
+ }
72
+ }
73
+
74
+ // Shell runners the runtime executes (cron/launchd target these). They live
75
+ // in templates/scripts/ and land beside the .mjs in the runtime scripts/ dir.
76
+ const shellRunners = [
77
+ 'watchtower-ring1-runner.sh',
78
+ 'watchtower-ring2-runner.sh',
79
+ 'watchtower-status.sh',
80
+ ];
81
+ for (const name of shellRunners) {
82
+ files.push({
83
+ src: path.join(scriptsDir, name),
84
+ dest: path.join(RUNTIME_SCRIPTS_DIR, name),
85
+ mode: 0o755,
86
+ });
87
+ }
88
+
89
+ // Session hooks → runtime hooks/.
90
+ const hooksDir = path.join(TEMPLATE_DIR, 'hooks');
91
+ for (const name of ['watchtower-session-start.sh', 'watchtower-session-end.sh']) {
92
+ files.push({
93
+ src: path.join(hooksDir, name),
94
+ dest: path.join(RUNTIME_HOOKS_DIR, name),
95
+ mode: 0o755,
96
+ });
97
+ }
98
+
99
+ // Cabinet docs the runtime / advisory pass reads → runtime cabinet/.
100
+ const cabinetDir = path.join(TEMPLATE_DIR, 'cabinet');
101
+ for (const name of ['advisories-state-schema.md', 'watchtower-contracts.md']) {
102
+ files.push({
103
+ src: path.join(cabinetDir, name),
104
+ dest: path.join(RUNTIME_CABINET_DIR, name),
105
+ });
106
+ }
107
+
108
+ return files;
109
+ }
110
+
111
+ const MANAGED_FILES = buildManagedFiles();
112
+
113
+ function sha256(content) {
114
+ return crypto.createHash('sha256').update(content).digest('hex');
115
+ }
116
+
117
+ function readGlobalManifest() {
118
+ if (!fs.existsSync(GLOBAL_MANIFEST_PATH)) return { files: {} };
119
+ try {
120
+ const m = JSON.parse(fs.readFileSync(GLOBAL_MANIFEST_PATH, 'utf8'));
121
+ // Guard shape drift: manifest.files is indexed unconditionally below.
122
+ if (typeof m !== 'object' || m === null || Array.isArray(m)) return { files: {} };
123
+ if (typeof m.files !== 'object' || m.files === null || Array.isArray(m.files)) m.files = {};
124
+ return m;
125
+ } catch {
126
+ return { files: {} };
127
+ }
128
+ }
129
+
130
+ function writeGlobalManifest(manifest) {
131
+ fs.mkdirSync(path.dirname(GLOBAL_MANIFEST_PATH), { recursive: true });
132
+ const tmp = GLOBAL_MANIFEST_PATH + '.tmp';
133
+ fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2));
134
+ fs.renameSync(tmp, GLOBAL_MANIFEST_PATH);
135
+ }
136
+
137
+ /**
138
+ * Refresh the code/docs/hooks of an EXISTING watchtower runtime, content-aware.
139
+ *
140
+ * @param {Object} opts
141
+ * @param {boolean} [opts.dryRun]
142
+ * @param {string[]} [opts.results] — human-readable lines are pushed here; a
143
+ * fresh array is created and returned when not supplied.
144
+ * @returns {{ results: string[], status: 'absent'|'unchanged'|'refreshed' }}
145
+ */
146
+ function refreshWatchtowerRuntime(opts = {}) {
147
+ const dryRun = !!opts.dryRun;
148
+ const results = opts.results || [];
149
+
150
+ // Refresh-only: never bootstrap a fresh runtime here. Fresh setup (launchd,
151
+ // cron, config.json, migrate-keys, coherence assertion) is the
152
+ // `/watchtower install` SKILL.md step's job. No runtime dir ⇒ no-op.
153
+ if (!fs.existsSync(RUNTIME_DIR)) {
154
+ return { results, status: 'absent' };
155
+ }
156
+
157
+ results.push('Refreshing watchtower runtime (content-aware)');
158
+
159
+ const manifest = readGlobalManifest();
160
+ let copiedCount = 0;
161
+
162
+ for (const file of MANAGED_FILES) {
163
+ if (!fs.existsSync(file.src)) {
164
+ results.push(` ⚠ Template missing: ${file.src}`);
165
+ continue;
166
+ }
167
+
168
+ const content = fs.readFileSync(file.src);
169
+ const hash = sha256(content);
170
+
171
+ if (manifest.files[file.dest] === hash) {
172
+ continue; // file unchanged
173
+ }
174
+
175
+ if (dryRun) {
176
+ results.push(` [dry-run] ${path.relative(TEMPLATE_DIR, file.src)} → ${file.dest}`);
177
+ } else {
178
+ fs.mkdirSync(path.dirname(file.dest), { recursive: true });
179
+ fs.writeFileSync(file.dest, content);
180
+ if (file.mode) {
181
+ fs.chmodSync(file.dest, file.mode);
182
+ }
183
+ manifest.files[file.dest] = hash;
184
+ }
185
+ copiedCount++;
186
+ }
187
+
188
+ if (!dryRun && copiedCount > 0) {
189
+ manifest.installedAt = new Date().toISOString();
190
+ writeGlobalManifest(manifest);
191
+ }
192
+
193
+ if (copiedCount > 0) {
194
+ results.push(` ${copiedCount} runtime file${copiedCount !== 1 ? 's' : ''} refreshed under ${RUNTIME_DIR}`);
195
+ return { results, status: 'refreshed' };
196
+ }
197
+
198
+ results.push(' watchtower runtime already current');
199
+ return { results, status: 'unchanged' };
200
+ }
201
+
202
+ // MANAGED_FILES, RUNTIME_DIR, and TEMPLATE_DIR are exported for the
203
+ // runtime-refresh test so it can seed a faithful, complete global manifest
204
+ // (single source of truth — the test never re-derives the file list).
205
+ module.exports = {
206
+ refreshWatchtowerRuntime,
207
+ MANAGED_FILES,
208
+ RUNTIME_DIR,
209
+ TEMPLATE_DIR,
210
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-cabinet",
3
- "version": "0.44.0",
3
+ "version": "0.46.0",
4
4
  "description": "Claude Cabinet — opinionated process scaffolding for Claude Code projects",
5
5
  "bin": {
6
6
  "create-claude-cabinet": "bin/create-claude-cabinet.js"
@@ -22,6 +22,10 @@
22
22
  ],
23
23
  "author": "Oren Magid",
24
24
  "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/orenmagid/claude-cabinet.git"
28
+ },
25
29
  "engines": {
26
30
  "node": ">=18"
27
31
  },
@@ -27,7 +27,10 @@ briefing:
27
27
  # Common: .claude/cabinet/_briefing-architecture.md, _briefing-jurisdictions.md, _briefing-api.md
28
28
  standing-mandate: audit
29
29
  # Add plan, execute, orient, debrief if this member should activate
30
- # in those contexts. Most members are audit-only.
30
+ # in those contexts. Most members are audit-only. Two session-boundary
31
+ # contexts exist for standing advisors (watchtower installs):
32
+ # session-close (Ring 3's automatic transcript-fed advisor pass) and
33
+ # briefing (/briefing's live advisor panel).
31
34
  tools:
32
35
  # List every external tool/command this member uses.
33
36
  # Format: "tool-name (scope -- what it does)"
@@ -37,8 +40,10 @@ tools:
37
40
  # - grep patterns (all projects -- dead code detection)
38
41
  # Use "tools: []" for pure-reasoning members with no tool stage.
39
42
  directives:
40
- # Only if standing-mandate includes plan, execute, orient, or debrief.
41
- # Each directive is a one-sentence focused task for that context.
43
+ # Only if standing-mandate includes plan, execute, orient, debrief,
44
+ # session-close, or briefing. Each directive is a one-sentence focused
45
+ # task for that context. A mandate without a matching directive is a
46
+ # data error — consumers skip the member and say so.
42
47
  # plan: >
43
48
  # What to evaluate when reviewing a plan.
44
49
  # execute: >
@@ -1,12 +1,18 @@
1
1
  # Advisory dismissal state — schema and rules
2
2
 
3
- Orient surfaces stack-aware advisories (install the Ruby language server, register the Railway MCP, install `hookify`, …). Without memory, every advisory re-nags every session — the same attention-fatigue pattern the watchtower rings were built to eliminate. This file defines the per-project state that gives advisories a memory, and the exact rules orient follows so an advisory is never *permanently* silenced by accident.
3
+ The watchtower **SessionStart context builder** surfaces stack-aware advisories (install the Ruby language server, register the Railway MCP, install `hookify`, …) via `scripts/watchtower-advisories.mjs` (act:f9ea075d). Without memory, every advisory re-nags every session — the same attention-fatigue pattern the watchtower rings were built to eliminate. This file defines the per-project state that gives advisories a memory, and the exact rules the advisory pass follows so an advisory is never *permanently* silenced by accident.
4
+
5
+ > **Actor.** The owner of these rules is `watchtower-advisories.mjs` (`runAdvisoryPass`), called by the context builder when watchtower is installed. On a project WITHOUT watchtower, orient runs a thin one-shot fallback (it may shell the same module via `node scripts/watchtower-advisories.mjs` when present, else surface basic install hints with no persistent dismissal state). The module is the single implementation of the rules below; do not re-encode them anywhere else.
4
6
 
5
7
  ## Where it lives
6
8
 
7
- `.claude/cabinet/advisories-state.json` — **per project, generated at runtime**, NOT shipped as a template. Orient creates it on first write. It must never be added to a module's template array: a shipped stub would overwrite a project's real dismissal history on reinstall (the `.ccrc.json` clobber class of bug). If the file is absent, every advisory is treated as never-seen.
9
+ `.claude/cabinet/advisories-state.json` — **per project, generated at runtime**, NOT shipped as a template. The advisory pass creates it on first write. It must never be added to a module's template array: a shipped stub would overwrite a project's real dismissal history on reinstall (the `.ccrc.json` clobber class of bug). If the file is absent OR malformed JSON, every advisory is treated as never-seen (the reader degrades to `{}`, never throws).
10
+
11
+ > Worktree note: `.claude/cabinet/` is copied per worktree, so dismissal state can diverge between a worktree and its main checkout. That is acceptable — advisories are advisory — and is the reason this is project-local, not user-global. **Path-consistency rule:** the pass reads AND writes the state at the SAME path (the session's `--project-path` cwd). Never read from one path and write to another, or a decline recorded in one place is invisible from the other and the advisory re-nags forever.
12
+
13
+ ## Reserved `_meta` key
8
14
 
9
- > Worktree note: `.claude/cabinet/` is copied per worktree, so dismissal state can diverge between a worktree and its main checkout. That is acceptable advisories are advisory and is the reason this is project-local, not user-global.
15
+ `_meta` is a **reserved top-level key**, NOT an advisory entry. It holds pass-level bookkeeping — currently `{ "last_probe": "<UTC YYYY-MM-DD>" }`, the throttle stamp for the `claude plugin list` install-probe (run at most once/day/checkout; UTC to match `last_shown` and sqlite `date('now')`). Any consumer iterating advisory entries (e.g. `Object.entries(state)`) MUST skip `_meta`. No advisory id may be named `_meta`.
10
16
 
11
17
  ## Schema
12
18
 
@@ -30,9 +36,26 @@ Orient surfaces stack-aware advisories (install the Ruby language server, regist
30
36
  - **`last_shown`** — ISO date of the most recent surfacing.
31
37
  - **`signal`** — *the key field that makes "resurface if the stack changed" actually work.* A short, deterministic fingerprint of the stack indicators present when the advisory was last shown/declined. For a multi-indicator advisory like Ruby (`Gemfile` OR `*.rb`), the fingerprint records *which* indicators were present (e.g. `gemfile` vs `gemfile+rb`), so a later change is detectable. Without this stored snapshot, orient has only the *current* indicators and no baseline to diff against — which is the gap this schema closes.
32
38
 
33
- ## The rules orient follows
39
+ ## Computing the signal (the indicator fingerprint)
40
+
41
+ A signal is a deterministic fingerprint of the stack indicators present now. Two rules keep it stable:
42
+
43
+ - **Sorted tokens.** A multi-indicator advisory joins its present indicators (e.g. Ruby = `gemfile`, `rb`, or `gemfile+rb`) **after sorting** them. Readdir order is not stable; without the sort, `gemfile+rb` and `rb+gemfile` would alternate and spuriously re-arm a declined advisory every session.
44
+ - **Bounded stack scan.** Source-file indicators (`*.ts`, `*.py`, `*.rb`) are detected by a **depth-limited (≤3) walk** with a denylist (`node_modules`, `dist`, `build`, `vendor`, `coverage`, `target`, `out`, plus all dot-dirs) and **early-exit on first match** (existence only). A recursive walk would be pathological on the session-start critical path; a root-only scan would silently miss the common `src/**/*.ts` layout (a TS project with no root `tsconfig.json`).
45
+
46
+ ## The probe is tri-state (the fourth input state)
34
47
 
35
- Before surfacing any advisory, orient computes the advisory's **current signal** (fingerprint of the indicators present now) and reads the stored entry:
48
+ The install-probe (`claude plugin list`) answers "is this plugin installed?" — but it can fail to answer (claude not on PATH, nonzero exit, timeout). Its result is therefore **`true | false | null`**, never a boolean. `null` ("unknown") is a distinct input state and is handled per advisory **kind**:
49
+
50
+ - **probe-suppressed** (the LSP advisories): the signal is on disk (stack files); the probe only *suppresses* (confirms installed → terminal). `installed===null` → **still surface** from the signal/state rules; freeze only the `installed` transition.
51
+ - **probe-gated** (`plugin:hookify`): the surfacing predicate *is* the probe ("hookify not installed"). `installed===null` → predicate unknowable → **suppress this session, do NOT increment count, do NOT write state** (freeze the entry untouched).
52
+ - **no-probe** (`mcp:railway`, `briefing-file`, `registry-orphan`): pure filesystem/config signal; the probe is irrelevant.
53
+
54
+ In all kinds, `installed===true` flips the entry to terminal `installed`.
55
+
56
+ ## The rules the advisory pass follows
57
+
58
+ Before surfacing any advisory, the pass computes the advisory's **current signal** (fingerprint of the indicators present now) and reads the stored entry:
36
59
 
37
60
  1. **No entry / file absent** → surface it. Write `{status:"suggested", count:1, last_shown:today, signal:current}`.
38
61
  2. **`installed`** → never surface (terminal). (Re-probe may flip a `suggested`/`declined` entry to `installed`; never the reverse automatically.)
@@ -64,5 +87,9 @@ Document any new advisory's signal source here when you add it, and call out exp
64
87
  | `lsp:rust` | `Cargo.toml` | `/plugin install rust-analyzer-lsp` |
65
88
  | `lsp:go` | `go.mod` | `/plugin install gopls-lsp` |
66
89
  | `lsp:ruby` | `Gemfile` or `*.rb` | `/plugin install ruby-lsp@claude-plugins-official` (also needs `gem install ruby-lsp` AND `ENABLE_LSP_TOOL=1`) |
67
- | `mcp:railway` | `railway.toml` and no railway key in `~/.claude.json` | local: `railway setup agent -y` · remote: register `mcp.railway.com` (OAuth) |
68
- | `plugin:hookify` | `.claude/rules/enforcement-pipeline.md` exists and hookify not in `claude plugin list` (signal is **static**) | `/plugin install hookify` |
90
+ | `mcp:railway` | `railway.toml` and no railway key in `~/.claude.json` (no-probe) | local: `railway setup agent -y` · remote: register `mcp.railway.com` (OAuth) |
91
+ | `plugin:hookify` | `.claude/rules/enforcement-pipeline.md` exists and hookify not in `claude plugin list` (signal is **static**; probe-gated) | `/plugin install hookify` |
92
+ | `briefing-file` | `.claude/briefing/_briefing.md` is **absent** (signal `missing`, **static**; no-probe) | run `/onboard` to create one |
93
+ | `registry-orphan` | `~/.claude/cc-registry.json` lists project path(s) that no longer exist (signal = sorted orphan-name set, so it re-arms when the registry changes; no-probe) | remove the dead entr(y/ies) from `~/.claude/cc-registry.json` |
94
+
95
+ > Note: `mcp:railway` keys on `railway.toml`, which Ring 1 also marker-checks for deploy detection — these are independent reads of the same file for different purposes; do not consolidate them (the advisory adds the `~/.claude.json` registration predicate Ring 1 lacks).
@@ -0,0 +1,104 @@
1
+ # Checklist Stats — hit-rate sidecar schema and write protocol
2
+
3
+ `.claude/cabinet/checklist-stats.json` records how the change-impact
4
+ checklist (`qa-dimensions.yaml`) performs over time: which dimensions
5
+ fire, which checks actually catch problems, and what pruning verdicts
6
+ the operator has already given. It is the evidence base for the audit
7
+ skill's `checklist-pruning` phase — without it, the checklist only ever
8
+ grows (debrief's `checklist-feedback` is add-only by design) and decays
9
+ into noise.
10
+
11
+ **This file is RUNTIME STATE, generated on first write — never shipped
12
+ as a template.** Shipping it would clobber accumulated stats on every
13
+ reinstall (same rule as `advisories-state-schema.md`). And it never
14
+ lives inside `qa-dimensions.yaml`: config files do not contain runtime
15
+ state.
16
+
17
+ ## Who writes what
18
+
19
+ | Writer | When | What |
20
+ |--------|------|------|
21
+ | `/execute` `post-impl-checklist` phase | every run past its no-op guard | increments `runs`; per triggered dimension increments `fires`, sets `last_fired` |
22
+ | `/debrief` `checklist-feedback` phase | when a session bug WAS caught via a surfaced check | appends to that dimension's `catches` |
23
+ | `/audit` `checklist-pruning` phase | every pruning verdict (including "keep") | appends to `pruning_reviews` |
24
+
25
+ ## Schema (`schema_version: 1`)
26
+
27
+ ```json
28
+ {
29
+ "schema_version": 1,
30
+ "runs": 14,
31
+ "dimensions": {
32
+ "data-coherence": {
33
+ "fires": 12,
34
+ "last_fired": "2026-06-11",
35
+ "catches": [
36
+ {
37
+ "date": "2026-06-10",
38
+ "check": "[run] Run schema validation if any schema or migration file changed.",
39
+ "note": "caught missing FK backfill before commit"
40
+ }
41
+ ]
42
+ }
43
+ },
44
+ "pruning_reviews": [
45
+ {
46
+ "date": "2026-06-11",
47
+ "target": "test-staleness",
48
+ "verdict": "keep",
49
+ "note": "fires often, zero catches, but cheap insurance at moderate severity"
50
+ }
51
+ ]
52
+ }
53
+ ```
54
+
55
+ Field semantics:
56
+
57
+ - **`runs`** — total executions of the post-impl-checklist phase that
58
+ passed its no-op guard, INCLUDING runs where zero dimensions
59
+ triggered. This is the denominator for "never fired in N runs."
60
+ - **`dimensions.<name>.fires`** — number of runs in which the dimension
61
+ triggered (matched at least one changed path). Dimension-level, not
62
+ check-level: checks have no stable IDs, so firing is counted where it
63
+ happens (path match) and catching is attributed by quoting the check.
64
+ - **`dimensions.<name>.catches`** — append-only evidence that a
65
+ surfaced check caught a real issue. `check` quotes the check text as
66
+ written in the yaml at the time.
67
+ - **`pruning_reviews`** — append-only verdict log. `verdict` is one of
68
+ `removed | trimmed | paths-fixed | severity-changed | keep`. The
69
+ pruning phase skips candidates with any verdict in the last 90 days,
70
+ so a "keep" decision is not re-litigated at every audit.
71
+
72
+ ## Write protocol
73
+
74
+ 1. Read the file. If absent, bootstrap the skeleton
75
+ (`{"schema_version": 1, "runs": 0, "dimensions": {}, "pruning_reviews": []}`).
76
+ If present but unparseable, move it aside to
77
+ `checklist-stats.json.corrupt-<YYYY-MM-DD>` (never delete) and
78
+ bootstrap fresh.
79
+ 2. Modify in memory.
80
+ 3. Write to `checklist-stats.json.tmp`, then rename over the original
81
+ (atomic — safe under concurrent sessions).
82
+
83
+ **Fail-open, always:** a stats read or write failure must never block
84
+ the phase doing the recording. Emit one warning line and continue —
85
+ losing a data point is fine; blocking an execute/debrief/audit run over
86
+ bookkeeping is not.
87
+
88
+ ## Anti-trap rules
89
+
90
+ - **Stats inform; the human decides.** Nothing auto-prunes from this
91
+ data, ever. Low hit-rate is evidence presented at audit, not a
92
+ trigger.
93
+ - **Per-dimension judgment, not universal thresholds.** A high-severity
94
+ security check that fires often and never catches may still be cheap
95
+ insurance; an info-severity check with the same profile is noise.
96
+ The pruning phase presents severity alongside the numbers.
97
+ - **Renames orphan stats.** If a dimension is renamed in
98
+ `qa-dimensions.yaml`, its stats entry goes stale. The pruning phase
99
+ reports entries with no matching dimension as orphans (offer to fold
100
+ or drop them); writers simply start a fresh entry under the new name.
101
+ - **Counts are honest, not precise.** Concurrent sessions can lose an
102
+ increment to a race; the rename-based write keeps the file valid and
103
+ the trend signal is what matters. Do not build exact-count logic on
104
+ top of this file.
@@ -37,7 +37,7 @@ high-stakes reviews is to put judgment in front of the operator.
37
37
  | Mode | Where it runs | What a `stop`/`pause` does | Used by |
38
38
  |------|---------------|----------------------------|---------|
39
39
  | **Interactive CP** | Main session (skill level) | Surfaced to the operator, who decides (proceed / drop / override / abort). Never automatic. | `/execute-group` CP1 |
40
- | **Advisory CP** | Workflow | Recorded in the Completion Report as a concern. Never halts or reverts. The only automatic gate alongside it is `/validate`. | `/execute-group` CP3 |
40
+ | **Advisory CP** | Workflow | Recorded in the Completion Report as a concern. Never halts or reverts. The only automatic gate alongside it is merge-delta `/validate` — new failures vs the group's pre-merge baseline; inherited debt is reported, not gated. | `/execute-group` CP3 |
41
41
  | **Full CP** | Main session or workflow | Halts on `stop`, escalates 3+ `pause` to a halt, requires explicit override. The classic gate. | `/execute` CP1/CP2/CP3 |
42
42
 
43
43
  **Why Interactive and Advisory exist.** `/execute-group` once ran CP1 and CP3
@@ -45,8 +45,20 @@ as autonomous gates inside a single workflow: a cabinet `stop` halted the run
45
45
  or reverted a merge with no human in the loop. False positives there cost real
46
46
  money (a CP1 halted twice consecutively — 1.6M+ tokens — on concerns the plan
47
47
  text already addressed). Moving CP1 to interactive (operator decides) and CP3
48
- to advisory (concerns recorded, `/validate` is the only hard gate) keeps the
49
- review signal while removing the destructive autonomous action.
48
+ to advisory (concerns recorded, merge-delta `/validate` is the only hard
49
+ gate) keeps the review signal while removing the destructive autonomous
50
+ action.
51
+
52
+ **The hard gate is merge-delta, not absolute.** `/execute-group` captures a
53
+ `/validate` baseline on main before the group's first merge. Only failures
54
+ NOT in that baseline (i.e. failures the group itself introduced) gate a merge
55
+ or completion. Failures that pre-date the group are inherited debt: listed
56
+ loudly in the Completion Report's `pre_existing_debt` section, never gated.
57
+ This too is field-driven — two consecutive groups were gated on documented
58
+ pre-existing main debt with zero merge-delta regressions, and the manual
59
+ recovery (judge the delta by hand, close the plans) ran identically both
60
+ times, so the delta judgment was promoted into the gate itself. The gate
61
+ stays hard for new failures: the point is removing ritual, not weakening it.
50
62
 
51
63
  ### Interactive CP adds a required `addressed_by_plan` field
52
64
 
@@ -154,8 +166,8 @@ At **Interactive CP** (`/execute-group` CP1), add the required
154
166
  The escalation below is **Full CP** behavior (used by `/execute`). For
155
167
  **Interactive CP** the verdicts are surfaced to the operator severity-first
156
168
  and the operator decides — no automatic halt. For **Advisory CP** the concerns
157
- are recorded in the Completion Report and nothing halts or reverts; `/validate`
158
- is the only automatic gate. See "Checkpoint modes" above.
169
+ are recorded in the Completion Report and nothing halts or reverts; merge-delta
170
+ `/validate` is the only automatic gate. See "Checkpoint modes" above.
159
171
 
160
172
  Collect every verdict, then:
161
173
 
@@ -107,9 +107,10 @@ to do its own work. One cabinet member consults another mid-evaluation.
107
107
  needs full conversation history) references another cabinet member's known
108
108
  findings — from memory, from audit history, or from prior session output.
109
109
 
110
- **Example:** During debrief, a historian cabinet member is activated to check:
111
- "Has this kind of change been done before? What happened? Are there
112
- lessons from prior sessions relevant to what was just completed?"
110
+ **Example:** At session close (Ring 3's advisor pass), the historian is
111
+ activated to check: "Has this kind of change been done before? What
112
+ happened? Are there lessons from prior sessions relevant to what was
113
+ just completed?"
113
114
 
114
115
  **Example:** During planning, the organized-mind cabinet member might need
115
116
  the historian's input: "Has this kind of information architecture been
@@ -11,6 +11,13 @@
11
11
  # and surfaces the matched dimensions' checks as context for the
12
12
  # pre-commit cabinet sweep (Checkpoint 3). QA is the primary consumer.
13
13
  #
14
+ # The checklist learns in both directions: /debrief's checklist-feedback
15
+ # phase ADDS checks when bugs slip through, and /audit's
16
+ # checklist-pruning phase surfaces low-hit-rate dimensions for
17
+ # human-approved REMOVAL (evidence lives in checklist-stats.json — see
18
+ # cabinet/checklist-stats-schema.md; runtime state never lives in this
19
+ # file).
20
+ #
14
21
  # ── Schema ────────────────────────────────────────────────────────
15
22
  # dimensions: # top-level map; keys are dimension names
16
23
  # <dimension-name>:
@@ -133,7 +133,41 @@ that format here; reference it.
133
133
  user's actual situation. This applies whether the choice is rendered via
134
134
  AskUserQuestion or prose — the primitive changes, the posture doesn't.
135
135
 
136
- ## 9. Calibration Examples
136
+ ## 9. Operator-Facing Register: Plain English by Default
137
+
138
+ When a skill addresses the operator about substantial work — a plan, an
139
+ audit finding, a checkpoint decision, a status report — there are **two
140
+ readers, and they need two registers:**
141
+
142
+ - **The filed artifact** (the plan in pib-db, the finding's record, the
143
+ report on disk) stays **technical and complete** — exact file paths,
144
+ function names, fids, acceptance criteria. A future session executes
145
+ from it cold, so precision is the whole point.
146
+ - **What you SAY to the operator** is **plain English** — concept first,
147
+ the stakes spelled out, any decision framed as options with tradeoffs.
148
+ Keep file paths and symbol names out of the spoken prose; they live in
149
+ the filed artifact, reachable on request or behind a collapsed
150
+ "full detail" pointer.
151
+
152
+ The operator is the director, not the implementer (see the user-role
153
+ brief): they decide *what* and *why* and must be able to weigh in without
154
+ parsing every technical detail. A wall of paths and identifiers is noise
155
+ to that judgment — it buries the one thing they're being asked to decide.
156
+ Lead with what changed and why it matters; keep the file-and-line
157
+ narrative for the record.
158
+
159
+ This is the **default register** for operator-facing skill output. It
160
+ does NOT relax the filed artifact's rigor — a plan still files
161
+ QA-compliant notes, an audit still records exact locations. The split is
162
+ between the *record* (technical) and the *briefing* (plain). When in
163
+ doubt, write the briefing as if explaining to a sharp colleague who
164
+ hasn't seen the code.
165
+
166
+ This is a *register* rule (how to phrase), distinct from §5 (prose vs
167
+ dialog as the *primitive*) and §8 (present-don't-prescribe as the
168
+ *posture*) — all three compose.
169
+
170
+ ## 10. Calibration Examples
137
171
 
138
172
  **Before/after — engagement decision items** (the Tier 1 conversion):
139
173