create-claude-cabinet 0.50.0 → 0.52.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 +35 -28
- package/lib/CLAUDE.md +30 -1
- package/lib/cli.js +26 -1
- package/lib/engagement-server-setup.js +1 -21
- package/lib/installer-gate.js +56 -0
- package/lib/modules.js +20 -4
- package/lib/mux-setup.js +1 -22
- package/lib/output-register.js +629 -0
- package/lib/settings-merge.js +74 -52
- package/lib/watchtower-setup.js +1 -21
- package/package.json +1 -1
- package/templates/CLAUDE.md +134 -12
- package/templates/EXTENSIONS.md +7 -0
- package/templates/README.md +3 -3
- package/templates/briefing/_briefing-template.md +1 -1
- package/templates/briefing/_briefing-work-tracking-template.md +3 -3
- package/templates/cabinet/_cabinet-member-template.md +10 -7
- package/templates/cabinet/memory-lifecycle-contract.md +16 -0
- package/templates/cabinet/pib-db-access.md +1 -1
- package/templates/cabinet/pib-db-triggers.md +20 -16
- package/templates/cabinet/watchtower-contracts.md +119 -0
- package/templates/engagement/pib-db-patches/pib-db-mcp-server.mjs +2 -2
- package/templates/hooks/register-reinforce.sh +125 -0
- package/templates/memory/patterns/_pattern-template.md +1 -1
- package/templates/output-styles/cabinet-register.md +114 -0
- package/templates/rules/enforcement-pipeline.md +2 -2
- package/templates/rules/memory-capture.md +2 -2
- package/templates/scripts/__tests__/absorption-no-prune.test.mjs +89 -0
- package/templates/scripts/__tests__/assessment-recalibration.test.mjs +0 -2
- package/templates/scripts/__tests__/batch-disposition.test.mjs +7 -9
- package/templates/scripts/__tests__/draft-surfacing.test.mjs +46 -162
- package/templates/scripts/__tests__/extraction-classification.test.mjs +219 -0
- package/templates/scripts/__tests__/fixtures/read-pass-2026-07-14.json +325 -0
- package/templates/scripts/__tests__/fold-similarity.test.mjs +290 -0
- package/templates/scripts/__tests__/hold-verb.test.mjs +149 -0
- package/templates/scripts/__tests__/reach1-inbox-relations.test.mjs +274 -0
- package/templates/scripts/__tests__/reach2-significance.test.mjs +300 -0
- package/templates/scripts/__tests__/read-pass-fixture.test.mjs +71 -0
- package/templates/scripts/__tests__/ring3-outage-backfill.test.mjs +156 -0
- package/templates/scripts/memory-consolidation-scan.mjs +149 -0
- package/templates/scripts/pib-db-mcp-server.mjs +2 -2
- package/templates/scripts/watchtower-build-context.mjs +33 -1
- package/templates/scripts/watchtower-lib.mjs +167 -1
- package/templates/scripts/watchtower-queue.mjs +192 -53
- package/templates/scripts/watchtower-ring1.mjs +120 -2
- package/templates/scripts/watchtower-ring2.mjs +49 -112
- package/templates/scripts/watchtower-ring3-close.mjs +728 -38
- package/templates/skills/audit/SKILL.md +5 -5
- package/templates/skills/cabinet-system-advocate/SKILL.md +2 -2
- package/templates/skills/cabinet-workflow-cop/SKILL.md +5 -3
- package/templates/skills/cc-feedback/SKILL.md +4 -4
- package/templates/skills/cc-publish/SKILL.md +20 -0
- package/templates/skills/cc-remember/SKILL.md +4 -4
- package/templates/skills/close/SKILL.md +63 -9
- package/templates/skills/debrief/SKILL.md +14 -8
- package/templates/skills/debrief/phases/qa-handoff-sweep.md +12 -9
- package/templates/skills/dx-feedback/SKILL.md +1 -1
- package/templates/skills/inbox/SKILL.md +85 -16
- package/templates/skills/memory/SKILL.md +1 -1
- package/templates/skills/onboard/SKILL.md +42 -33
- package/templates/skills/onboard/phases/detect-state.md +3 -2
- package/templates/skills/onboard/phases/generate-briefing.md +3 -2
- package/templates/skills/onboard/phases/generate-session-loop.md +70 -37
- package/templates/skills/onboard/phases/interview.md +17 -12
- package/templates/skills/onboard/phases/modularity-menu.md +33 -19
- package/templates/skills/onboard/phases/options.md +3 -1
- package/templates/skills/onboard/phases/post-onboard-audit.md +17 -11
- package/templates/skills/onboard/phases/summary.md +15 -12
- package/templates/skills/onboard/phases/work-tracking.md +14 -8
- package/templates/skills/orient/SKILL.md +7 -5
- package/templates/skills/pulse/SKILL.md +23 -18
- package/templates/skills/seed/phases/build-member.md +5 -3
- package/templates/skills/session-handoff/SKILL.md +72 -45
- package/templates/skills/spring-clean/SKILL.md +15 -13
- package/templates/skills/triage-audit/SKILL.md +3 -3
- package/templates/skills/validate/phases/validators.md +67 -0
package/README.md
CHANGED
|
@@ -18,8 +18,9 @@ and weigh in when their expertise matters:
|
|
|
18
18
|
- **Briefings** — project context members read before weighing in
|
|
19
19
|
- **Committees** — members grouped by concern so you convene the right
|
|
20
20
|
experts for the right question
|
|
21
|
-
- **The session loop** —
|
|
22
|
-
|
|
21
|
+
- **The session loop** — watchtower keeps your project's state current in
|
|
22
|
+
the background and hands it to you when a session opens; `/close` ends a
|
|
23
|
+
session (legacy: `/orient` + `/debrief` by hand)
|
|
23
24
|
|
|
24
25
|
## Install
|
|
25
26
|
|
|
@@ -56,25 +57,28 @@ it's done, open Claude Code and run `/onboard`.
|
|
|
56
57
|
|
|
57
58
|
## What You Get
|
|
58
59
|
|
|
59
|
-
### The Session Loop (
|
|
60
|
+
### The Session Loop (installed by default)
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
Continuity between sessions, so Claude never starts blind. By default this
|
|
63
|
+
is **watchtower** — a background system that keeps your project's state
|
|
64
|
+
current and hands it to Claude when a session opens.
|
|
63
65
|
|
|
64
|
-
-
|
|
65
|
-
state
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
- **`/
|
|
69
|
-
|
|
70
|
-
next time. Without debrief, the next orient starts with stale
|
|
71
|
-
information. The loop is what gives Claude memory across sessions.
|
|
66
|
+
- **Sessions start informed** — watchtower's SessionStart hook injects the
|
|
67
|
+
current state (what you were working on, what's due, what broke). No
|
|
68
|
+
start-of-session command to run.
|
|
69
|
+
- **`/briefing`** — review what needs attention across the project.
|
|
70
|
+
- **`/close`** (or **`/session-handoff`**) — end a session; the background
|
|
71
|
+
rings handle inventory, lesson capture, and routing after you walk away.
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
Watchtower is installed by default; its background rings start when you run
|
|
74
|
+
**`/watchtower install`**. Until then it ships the skills and a dormant
|
|
75
|
+
SessionStart hook — nothing runs in the background and no API cost is
|
|
76
|
+
incurred.
|
|
77
|
+
|
|
78
|
+
**Prefer to run it by hand?** Opt out of watchtower and use the legacy
|
|
79
|
+
loop instead — **`/orient`** at the start (reads state and briefs you) and
|
|
80
|
+
**`/debrief`** at the end (marks work done, records lessons). Both still
|
|
81
|
+
ship, but watchtower replaces them.
|
|
78
82
|
|
|
79
83
|
### The Cabinet (included in lean)
|
|
80
84
|
|
|
@@ -129,7 +133,7 @@ you already use GitHub Issues, Linear, or something else.
|
|
|
129
133
|
Claude Code has built-in file memory, but no guardrails around it.
|
|
130
134
|
The memory module adds structure:
|
|
131
135
|
|
|
132
|
-
- **`/cc-remember`** — write a new memory with automatic indexing. Every memory gets its own file and is made reachable from `MEMORY.md` — by a direct entry or a region pointer — so
|
|
136
|
+
- **`/cc-remember`** — write a new memory with automatic indexing. Every memory gets its own file and is made reachable from `MEMORY.md` — by a direct entry or a region pointer — so the session loop can surface it next session.
|
|
133
137
|
- **`/memory`** — browse and search what Claude remembers.
|
|
134
138
|
- **Validation** — `validate-memory.mjs` checks that the index stays within Claude Code's session-start budget and that every memory file is reachable from `MEMORY.md`. A PostToolUse hook flags unreachable writes in real time.
|
|
135
139
|
|
|
@@ -181,14 +185,17 @@ e2e harness.
|
|
|
181
185
|
|
|
182
186
|
The rhythm of each session:
|
|
183
187
|
|
|
184
|
-
1. **Start a session** → `/
|
|
188
|
+
1. **Start a session** → watchtower briefs you automatically; `/briefing`
|
|
189
|
+
for the full picture (legacy loop: `/orient`)
|
|
185
190
|
2. **Do your work** → talk to Claude, use `/plan` for anything non-trivial
|
|
186
191
|
3. **Build it** → `/execute` to implement approved plans with cabinet oversight
|
|
187
192
|
4. **Check quality** → `/audit` occasionally for a full cabinet review
|
|
188
|
-
5. **Close the session** → `/
|
|
193
|
+
5. **Close the session** → `/close` (the rings capture the rest after you
|
|
194
|
+
walk away; legacy loop: `/debrief`)
|
|
189
195
|
|
|
190
|
-
|
|
191
|
-
needed. The more you use, the more the cabinet
|
|
196
|
+
Step 5 is the minimum — start is automatic under watchtower. Everything in
|
|
197
|
+
between is yours to use as needed. The more you use, the more the cabinet
|
|
198
|
+
learns about your project.
|
|
192
199
|
|
|
193
200
|
## How It Works
|
|
194
201
|
|
|
@@ -243,7 +250,7 @@ npx create-claude-cabinet --frontier-model claude-fable-5 # Designate your fron
|
|
|
243
250
|
|
|
244
251
|
### Frontier-model watchdog
|
|
245
252
|
|
|
246
|
-
`--frontier-model <model>` records, once, which model your heavy thinking is supposed to run on. The designation is per-operator (stored in `~/.claude/cc-registry.json` under `frontierModel`, not per-project), and the installer prints the effective value on every run. From then on,
|
|
253
|
+
`--frontier-model <model>` records, once, which model your heavy thinking is supposed to run on. The designation is per-operator (stored in `~/.claude/cc-registry.json` under `frontierModel`, not per-project), and the installer prints the effective value on every run. From then on, the SessionStart hook (on watchtower installs, the default) — and `/orient` on the legacy loop — compares the session's actual model against it and leads the briefing with a loud warning on mismatch. The key can be an exact model ID (`claude-fable-5`, exact match required) or a family alias (`fable`, matches any model ID containing it). This is **visibility only**: nothing is pinned, blocked, or rerouted — it just makes "you're accidentally on the wrong model" impossible to miss. A stale key after a model-family transition nags loudly by design; update it with the same flag.
|
|
247
254
|
|
|
248
255
|
## What Gets Installed
|
|
249
256
|
|
|
@@ -252,7 +259,7 @@ source code.
|
|
|
252
259
|
|
|
253
260
|
```
|
|
254
261
|
.claude/
|
|
255
|
-
├── skills/ #
|
|
262
|
+
├── skills/ # watchtower, plan, execute, audit, orient, debrief, etc.
|
|
256
263
|
│ └── cabinet-*/ # 34 cabinet member definitions
|
|
257
264
|
├── cabinet/ # committees, lifecycle, composition patterns
|
|
258
265
|
│ # (incl. pib-db-access.md, pib-db-triggers.md)
|
|
@@ -294,10 +301,10 @@ with Claude everywhere.
|
|
|
294
301
|
- **Your identity** (`~/.claude/CLAUDE.md`) — set up once, carries to
|
|
295
302
|
every project. Claude always knows who you are and what you do.
|
|
296
303
|
- **Project registry** (`~/.claude/cc-registry.json`) — tracks all
|
|
297
|
-
your projects. `/onboard` asks how they relate;
|
|
304
|
+
your projects. `/onboard` asks how they relate; the session loop flags
|
|
298
305
|
when work in one might affect another.
|
|
299
|
-
- **
|
|
300
|
-
or your project evolves,
|
|
306
|
+
- **Profile maintenance** — if you mention something new about yourself
|
|
307
|
+
or your project evolves, session close proposes updating your profile
|
|
301
308
|
and registry so the next session starts current.
|
|
302
309
|
|
|
303
310
|
Install in each project folder. They're independent but aware of each
|
package/lib/CLAUDE.md
CHANGED
|
@@ -53,7 +53,15 @@ No build step; CommonJS.
|
|
|
53
53
|
`patterns-scope-check.mjs`).
|
|
54
54
|
- `lib/installer-gate.js` — shared version/hash gating for the setup
|
|
55
55
|
engines (Wave 1, act:a6377c82). Exports `sha256`, `compareVersions`,
|
|
56
|
-
`hashSourceDir`, `tarballIsStale
|
|
56
|
+
`hashSourceDir`, `tarballIsStale`, and — since act:9a0af01a —
|
|
57
|
+
`readGlobalManifest`/`writeGlobalManifest` (+ `ccHome`/
|
|
58
|
+
`globalManifestPath`), hoisted here from mux-setup, watchtower-setup, and
|
|
59
|
+
engagement-server-setup, which each carried a BYTE-IDENTICAL copy of the
|
|
60
|
+
pair: the same duplication condition that created this module in the first
|
|
61
|
+
place. All four user-level engines (the fourth being output-register) now
|
|
62
|
+
delegate; the shape-drift guard (a non-object top level or non-object
|
|
63
|
+
`files` reads as empty) lives in the one copy, since every caller indexes
|
|
64
|
+
`manifest.files[dest]` unconditionally. Before it, `sha256()` was
|
|
57
65
|
copy-pasted three times and `compareVersions()` twice, and the
|
|
58
66
|
equal-version "don't hard-skip, fall through to a per-file hash-compare"
|
|
59
67
|
rule was re-implemented per installer. Per-file-manifest installers
|
|
@@ -195,6 +203,27 @@ No build step; CommonJS.
|
|
|
195
203
|
follows mux-setup.js pattern with global manifest SHA256 tracking,
|
|
196
204
|
incl. the equal-version hash-compare fall-through of act:df1a1dc3 —
|
|
197
205
|
equal versions copy only changed files, `'unchanged'` when none do).
|
|
206
|
+
- `lib/output-register.js` — Cabinet Register output-style installer
|
|
207
|
+
(act:9a0af01a). Seeds `templates/output-styles/*.md` (GLOBBED, never
|
|
208
|
+
hand-enumerated — the orphan test only scans `templates/skills/`) into the
|
|
209
|
+
user-level `~/.claude/output-styles/`, then seeds `outputStyle` into the
|
|
210
|
+
PROJECT's `.claude/settings.json`. Unlike the other four engines this one
|
|
211
|
+
is SEED-ONLY: `isProjectOwnedSeed()` semantics (never overwrite — the
|
|
212
|
+
operator hand-tunes their register) PLUS loud validation of the INSTALLED
|
|
213
|
+
copy, because neither sibling engine's ownership model is right here (mux
|
|
214
|
+
never re-examines a hand edit → the footgun persists; watchtower reverts
|
|
215
|
+
hand edits every reinstall). Its load-bearing export is `versionGate()`:
|
|
216
|
+
`keep-coding-instructions` only exists as of Claude Code **v2.0.37**, and
|
|
217
|
+
below that the field is silently ignored, its `false` default applies, and
|
|
218
|
+
CC's software-engineering instructions are STRIPPED with no error — a
|
|
219
|
+
hazard NO file-content test can see, so the guard shells `claude --version`
|
|
220
|
+
(refuse below the floor; fail OPEN when absent/unparseable, since absence is
|
|
221
|
+
not an old version). `resolveRegisterLevel()` is a three-valued
|
|
222
|
+
`{level, ours, source}` ENUMERATOR (blind to force-for-plugin / CLI /
|
|
223
|
+
managed layers, and says so); `REGISTER_STYLE_NAME` must equal the style's
|
|
224
|
+
frontmatter `name` — CC keys custom styles by that field verbatim. Full
|
|
225
|
+
rationale in `templates/CLAUDE.md` § output-register; 40 tests in
|
|
226
|
+
`test/output-register/`.
|
|
198
227
|
- `lib/mux-setup.js` — mux runtime installer (copies bin/mux, config
|
|
199
228
|
scripts, and popup to user-level paths; creates data dirs;
|
|
200
229
|
`setupDarwinIntegration()` appends source-file to `~/.tmux.conf`,
|
package/lib/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
const { copyTemplates, recordSkip, isProjectOwnedSeed, classifyFileOwnership } = require('./copy');
|
|
7
|
-
const { mergeSettings, healUserSettings, mergeWatchtowerHooks, mergeMuxHooks, mergeBashCompressHooks, mergeCcDevHooks, mergeSkillOverrides } = require('./settings-merge');
|
|
7
|
+
const { mergeSettings, healUserSettings, mergeWatchtowerHooks, mergeMuxHooks, mergeBashCompressHooks, mergeRegisterHooks, mergeCcDevHooks, mergeSkillOverrides } = require('./settings-merge');
|
|
8
8
|
const { create: createMetadata, read: readMetadata, readFrozenModules, setFrozenModules, readMcpOptOut } = require('./metadata');
|
|
9
9
|
const { setupDb, shouldSkipInit, readBundledSchemaVersion } = require('./db-setup');
|
|
10
10
|
const { setupVerifyRuntime } = require('./verify-setup');
|
|
@@ -13,6 +13,7 @@ const { setupEngagement } = require('./engagement-setup');
|
|
|
13
13
|
const { setupMux } = require('./mux-setup');
|
|
14
14
|
const { setupEngagementServer } = require('./engagement-server-setup');
|
|
15
15
|
const { refreshWatchtowerRuntime } = require('./watchtower-setup');
|
|
16
|
+
const { setupOutputRegister, renderRegisterReport } = require('./output-register');
|
|
16
17
|
const { reset } = require('./reset');
|
|
17
18
|
|
|
18
19
|
const VERSION = require('../package.json').version;
|
|
@@ -635,6 +636,7 @@ function parseArgs(argv) {
|
|
|
635
636
|
else if (arg === '--migrate-memory') flags.migrateMemory = true;
|
|
636
637
|
else if (arg === '--unmigrate-memory') flags.unmigrateMemory = true;
|
|
637
638
|
else if (arg === '--list-frozen') flags.listFrozen = true;
|
|
639
|
+
else if (arg === '--register') flags.register = true;
|
|
638
640
|
else if (arg === '--modules' && i + 1 < args.length) {
|
|
639
641
|
flags.modules = args[++i].split(',').map(s => s.trim()).filter(Boolean);
|
|
640
642
|
}
|
|
@@ -692,6 +694,11 @@ function printHelp() {
|
|
|
692
694
|
--unfreeze [keys] Clear the freeze for the given module(s), or all if no
|
|
693
695
|
keys. Re-run the installer to refresh the unfrozen module(s).
|
|
694
696
|
--list-frozen Show which modules are frozen (+ their closure) and exit.
|
|
697
|
+
--register Show where the Cabinet Register output style stands: every
|
|
698
|
+
settings file that names an outputStyle and what each says.
|
|
699
|
+
Enumerates only — it cannot see plugin force-for-plugin, CLI
|
|
700
|
+
--settings, or managed policy, and says so. Use /status and
|
|
701
|
+
/context for ground truth. Exits without installing.
|
|
695
702
|
--reset Remove Claude Cabinet files (uses manifest for safety)
|
|
696
703
|
--force With --reset: remove even customized files;
|
|
697
704
|
with --migrate-memory: override already-migrated guard
|
|
@@ -843,6 +850,18 @@ async function run() {
|
|
|
843
850
|
}
|
|
844
851
|
}
|
|
845
852
|
|
|
853
|
+
// Standalone register enumeration (act:9a0af01a). Prints every settings file
|
|
854
|
+
// that mentions outputStyle and what each says, then names the layers it
|
|
855
|
+
// could NOT consult and points at /status + /context for ground truth. An
|
|
856
|
+
// ENUMERATOR, never a verdict — see renderRegisterReport.
|
|
857
|
+
if (flags.register) {
|
|
858
|
+
const projectDir = path.resolve(flags.targetDir);
|
|
859
|
+
console.log('');
|
|
860
|
+
for (const line of renderRegisterReport({ projectDir })) console.log(line);
|
|
861
|
+
console.log('');
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
846
865
|
if (flags.migrateMemory) {
|
|
847
866
|
const { migrateMemoryCmd } = require('./migrate-memory-cmd');
|
|
848
867
|
const projectDir = path.resolve(flags.targetDir);
|
|
@@ -1422,6 +1441,11 @@ async function run() {
|
|
|
1422
1441
|
console.log(' ⚙️ Registered bash-output compression PostToolUse hook');
|
|
1423
1442
|
}
|
|
1424
1443
|
|
|
1444
|
+
if (selectedModules.includes('output-register')) {
|
|
1445
|
+
mergeRegisterHooks(settingsPath);
|
|
1446
|
+
console.log(' ⚙️ Registered Cabinet Register reinforcement UserPromptSubmit hook');
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1425
1449
|
// Repo-local maintainer hooks — no-op unless this IS the CC source repo
|
|
1426
1450
|
// (dogfood). Registers the node --check PostToolUse gate durably so a
|
|
1427
1451
|
// reinstall can't silently regenerate it away (architecture-0004).
|
|
@@ -1537,6 +1561,7 @@ async function run() {
|
|
|
1537
1561
|
'engagement-setup': setupEngagement,
|
|
1538
1562
|
'mux-setup': setupMux,
|
|
1539
1563
|
'engagement-server-setup': setupEngagementServer,
|
|
1564
|
+
'output-register-setup': setupOutputRegister,
|
|
1540
1565
|
};
|
|
1541
1566
|
for (const moduleKey of selectedModules) {
|
|
1542
1567
|
const mod = MODULES[moduleKey];
|
|
@@ -20,10 +20,9 @@
|
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const path = require('path');
|
|
22
22
|
const os = require('os');
|
|
23
|
-
const { sha256, compareVersions } = require('./installer-gate');
|
|
23
|
+
const { sha256, compareVersions, readGlobalManifest, writeGlobalManifest } = require('./installer-gate');
|
|
24
24
|
|
|
25
25
|
const CC_HOME = path.join(os.homedir(), '.claude-cabinet');
|
|
26
|
-
const GLOBAL_MANIFEST_PATH = path.join(CC_HOME, 'global-manifest.json');
|
|
27
26
|
const INSTALL_DIR = path.join(CC_HOME, 'engagement-server');
|
|
28
27
|
const VERSION_FILE = path.join(INSTALL_DIR, '.cc-version');
|
|
29
28
|
const TEMPLATE_DIR = path.resolve(__dirname, '..', 'templates', 'engagement-server');
|
|
@@ -42,25 +41,6 @@ const DATA_DIRS = [
|
|
|
42
41
|
path.join(INSTALL_DIR, 'migrations'),
|
|
43
42
|
];
|
|
44
43
|
|
|
45
|
-
function readGlobalManifest() {
|
|
46
|
-
if (!fs.existsSync(GLOBAL_MANIFEST_PATH)) return { files: {} };
|
|
47
|
-
try {
|
|
48
|
-
const m = JSON.parse(fs.readFileSync(GLOBAL_MANIFEST_PATH, 'utf8'));
|
|
49
|
-
// Guard shape drift: manifest.files is indexed unconditionally below.
|
|
50
|
-
if (typeof m !== 'object' || m === null || Array.isArray(m)) return { files: {} };
|
|
51
|
-
if (typeof m.files !== 'object' || m.files === null || Array.isArray(m.files)) m.files = {};
|
|
52
|
-
return m;
|
|
53
|
-
} catch {
|
|
54
|
-
return { files: {} };
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function writeGlobalManifest(manifest) {
|
|
59
|
-
fs.mkdirSync(path.dirname(GLOBAL_MANIFEST_PATH), { recursive: true });
|
|
60
|
-
const tmp = GLOBAL_MANIFEST_PATH + '.tmp';
|
|
61
|
-
fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2));
|
|
62
|
-
fs.renameSync(tmp, GLOBAL_MANIFEST_PATH);
|
|
63
|
-
}
|
|
64
44
|
|
|
65
45
|
function readInstalledVersion() {
|
|
66
46
|
if (!fs.existsSync(VERSION_FILE)) return null;
|
package/lib/installer-gate.js
CHANGED
|
@@ -26,8 +26,26 @@
|
|
|
26
26
|
|
|
27
27
|
const crypto = require('crypto');
|
|
28
28
|
const fs = require('fs');
|
|
29
|
+
const os = require('os');
|
|
29
30
|
const path = require('path');
|
|
30
31
|
|
|
32
|
+
// The user-level install root and the SHA256 manifest every user-level engine
|
|
33
|
+
// records its managed files in. Resolved per-call (not frozen at module load)
|
|
34
|
+
// because the tests point HOME at a sandbox in a child process.
|
|
35
|
+
//
|
|
36
|
+
// `home` is injectable for the engines that take an in-process `home` option
|
|
37
|
+
// instead of a child-process $HOME (output-register does). Without it, such an
|
|
38
|
+
// engine threads its sandbox home to its file destinations but still resolves
|
|
39
|
+
// the manifest against the REAL os.homedir() — a half-injection that writes
|
|
40
|
+
// dead temp paths into the operator's live manifest on every test run
|
|
41
|
+
// (act:9a0af01a; caught by the QA drain, 8 stale entries deep).
|
|
42
|
+
function ccHome(home = os.homedir()) {
|
|
43
|
+
return path.join(home, '.claude-cabinet');
|
|
44
|
+
}
|
|
45
|
+
function globalManifestPath(home = os.homedir()) {
|
|
46
|
+
return path.join(ccHome(home), 'global-manifest.json');
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
/** SHA-256 hex digest of a string or Buffer. */
|
|
32
50
|
function sha256(content) {
|
|
33
51
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
@@ -124,6 +142,40 @@ function tarballIsStale({ tarballPath, installDir, runtimeSourceDir, minSize = 1
|
|
|
124
142
|
return { stale: false, sourceHash, reason: 'unchanged' };
|
|
125
143
|
}
|
|
126
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Read the user-level global manifest (`~/.claude-cabinet/global-manifest.json`).
|
|
147
|
+
*
|
|
148
|
+
* Hoisted here from the three user-level engines (mux-setup, watchtower-setup,
|
|
149
|
+
* engagement-server-setup), which each carried a byte-identical copy of this
|
|
150
|
+
* function and its writer — the exact duplication condition that created this
|
|
151
|
+
* module ("one concept, one place"). A fourth copy was about to land with
|
|
152
|
+
* output-register; instead all four delegate.
|
|
153
|
+
*
|
|
154
|
+
* Shape-drift defensive: a non-object top level, or a non-object `files`, reads
|
|
155
|
+
* as an empty manifest, because every caller indexes `manifest.files[dest]`
|
|
156
|
+
* unconditionally. Never throws — an unreadable/corrupt manifest degrades to
|
|
157
|
+
* "nothing recorded", which makes callers re-copy rather than silently skip.
|
|
158
|
+
*/
|
|
159
|
+
function readGlobalManifest(manifestPath = globalManifestPath()) {
|
|
160
|
+
if (!fs.existsSync(manifestPath)) return { files: {} };
|
|
161
|
+
try {
|
|
162
|
+
const m = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
163
|
+
if (typeof m !== 'object' || m === null || Array.isArray(m)) return { files: {} };
|
|
164
|
+
if (typeof m.files !== 'object' || m.files === null || Array.isArray(m.files)) m.files = {};
|
|
165
|
+
return m;
|
|
166
|
+
} catch {
|
|
167
|
+
return { files: {} };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Persist the global manifest atomically (tmp + rename). */
|
|
172
|
+
function writeGlobalManifest(manifest, manifestPath = globalManifestPath()) {
|
|
173
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
174
|
+
const tmp = manifestPath + '.tmp';
|
|
175
|
+
fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2));
|
|
176
|
+
fs.renameSync(tmp, manifestPath);
|
|
177
|
+
}
|
|
178
|
+
|
|
127
179
|
module.exports = {
|
|
128
180
|
sha256,
|
|
129
181
|
compareVersions,
|
|
@@ -131,5 +183,9 @@ module.exports = {
|
|
|
131
183
|
readSourceHash,
|
|
132
184
|
writeSourceHash,
|
|
133
185
|
tarballIsStale,
|
|
186
|
+
readGlobalManifest,
|
|
187
|
+
writeGlobalManifest,
|
|
188
|
+
ccHome,
|
|
189
|
+
globalManifestPath,
|
|
134
190
|
SOURCE_HASH_FILE,
|
|
135
191
|
};
|
package/lib/modules.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
// unchanged.
|
|
16
16
|
const MODULES = {
|
|
17
17
|
'session-loop': {
|
|
18
|
-
name: 'Session Loop (
|
|
19
|
-
description: 'The
|
|
18
|
+
name: 'Session Loop (shared verbs + legacy orient/debrief)',
|
|
19
|
+
description: 'The mandatory session verbs (/menu, /session-handoff) plus the legacy orient/debrief skeletons and their -classic reversibility copies. Watchtower (installed by default) is the live session loop; orient/debrief are the opt-out fallback.',
|
|
20
20
|
mandatory: true,
|
|
21
21
|
templates: [
|
|
22
22
|
'skills/orient',
|
|
@@ -209,9 +209,9 @@ const MODULES = {
|
|
|
209
209
|
},
|
|
210
210
|
watchtower: {
|
|
211
211
|
name: 'Watchtower (continuous background state)',
|
|
212
|
-
description: '
|
|
212
|
+
description: 'The default session loop. Continuous background processing replaces the legacy orient/debrief skeletons: four rings (mechanical cron, Claude intelligence, session-aware, truth reconciliation), ambient state injection via SessionStart hook, and an inbox for extracted knowledge and signals. Sessions start informed with minimal context cost. Installing the module ships the skills + dormant SessionStart hook; the background rings start only when you run /watchtower install. Opt out to fall back to the legacy orient/debrief loop.',
|
|
213
213
|
mandatory: false,
|
|
214
|
-
default:
|
|
214
|
+
default: true,
|
|
215
215
|
lean: false,
|
|
216
216
|
templates: [
|
|
217
217
|
'scripts/watchtower-validate.mjs',
|
|
@@ -251,6 +251,7 @@ const MODULES = {
|
|
|
251
251
|
'scripts/watchtower-ring4.mjs',
|
|
252
252
|
'scripts/watchtower-ring4-runner.sh',
|
|
253
253
|
'scripts/watchtower-status.sh',
|
|
254
|
+
'scripts/memory-consolidation-scan.mjs',
|
|
254
255
|
'skills/briefing',
|
|
255
256
|
'skills/threads',
|
|
256
257
|
'skills/catch-up',
|
|
@@ -278,6 +279,21 @@ const MODULES = {
|
|
|
278
279
|
postInstall: 'engagement-server-setup',
|
|
279
280
|
templates: [],
|
|
280
281
|
},
|
|
282
|
+
'output-register': {
|
|
283
|
+
name: 'Cabinet Register (plain-English output style)',
|
|
284
|
+
description: 'Makes plain-English, decision-level communication the DEFAULT rather than guidance that decays. Ships a Claude Code output style (the contract lands in the system prompt, re-sent every request) to ~/.claude/output-styles/, plus a one-line-per-turn UserPromptSubmit reminder — custom styles get no per-turn reminder from the platform, only built-ins do. Code, paths, errors, and every filed artifact stay technical. On by default; the style is seeded, never clobbered, so a project that already sets an output style is left alone. Requires Claude Code v2.0.37+ (below that the engine refuses, loudly, and installs nothing).',
|
|
285
|
+
mandatory: false,
|
|
286
|
+
default: true,
|
|
287
|
+
lean: false,
|
|
288
|
+
requires: ['hooks'],
|
|
289
|
+
postInstall: 'output-register-setup',
|
|
290
|
+
// Project-level payload only: the per-turn hook. The STYLE itself is
|
|
291
|
+
// user-level (~/.claude/output-styles/ is where Claude Code reads custom
|
|
292
|
+
// styles from) and is seeded by the postInstall engine, which is why this
|
|
293
|
+
// array doesn't list it. Same shape as engagement-server (templates: [] +
|
|
294
|
+
// postInstall + user-level payload) — an existing, green precedent.
|
|
295
|
+
templates: ['hooks/register-reinforce.sh'],
|
|
296
|
+
},
|
|
281
297
|
'bash-compress': {
|
|
282
298
|
name: 'Bash Output Compression Hook',
|
|
283
299
|
description: 'PostToolUse hook that compresses noisy Bash stdout (git status walls, npm/yarn install output, find/ls dumps) to reclaim context in long sessions. Off by default. stderr and error/warning lines pass through verbatim; every rewrite carries a visible [compressed] marker; fail-open on any error. Requires the hooks module (it wires into .claude/settings.json).',
|
package/lib/mux-setup.js
CHANGED
|
@@ -19,10 +19,8 @@
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const os = require('os');
|
|
22
|
-
const { sha256, compareVersions } = require('./installer-gate');
|
|
22
|
+
const { sha256, compareVersions, readGlobalManifest, writeGlobalManifest } = require('./installer-gate');
|
|
23
23
|
|
|
24
|
-
const CC_HOME = path.join(os.homedir(), '.claude-cabinet');
|
|
25
|
-
const GLOBAL_MANIFEST_PATH = path.join(CC_HOME, 'global-manifest.json');
|
|
26
24
|
const MUX_VERSION_FILE = path.join(os.homedir(), '.config', 'mux', '.cc-version');
|
|
27
25
|
const TEMPLATE_DIR = path.resolve(__dirname, '..', 'templates', 'mux');
|
|
28
26
|
|
|
@@ -65,25 +63,6 @@ const DATA_DIRS = [
|
|
|
65
63
|
path.join(os.homedir(), '.local', 'share', 'mux', 'qa-handoff'),
|
|
66
64
|
];
|
|
67
65
|
|
|
68
|
-
function readGlobalManifest() {
|
|
69
|
-
if (!fs.existsSync(GLOBAL_MANIFEST_PATH)) return { files: {} };
|
|
70
|
-
try {
|
|
71
|
-
const m = JSON.parse(fs.readFileSync(GLOBAL_MANIFEST_PATH, 'utf8'));
|
|
72
|
-
// Guard shape drift: manifest.files is indexed unconditionally below.
|
|
73
|
-
if (typeof m !== 'object' || m === null || Array.isArray(m)) return { files: {} };
|
|
74
|
-
if (typeof m.files !== 'object' || m.files === null || Array.isArray(m.files)) m.files = {};
|
|
75
|
-
return m;
|
|
76
|
-
} catch {
|
|
77
|
-
return { files: {} };
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function writeGlobalManifest(manifest) {
|
|
82
|
-
fs.mkdirSync(path.dirname(GLOBAL_MANIFEST_PATH), { recursive: true });
|
|
83
|
-
const tmp = GLOBAL_MANIFEST_PATH + '.tmp';
|
|
84
|
-
fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2));
|
|
85
|
-
fs.renameSync(tmp, GLOBAL_MANIFEST_PATH);
|
|
86
|
-
}
|
|
87
66
|
|
|
88
67
|
function readInstalledVersion() {
|
|
89
68
|
if (!fs.existsSync(MUX_VERSION_FILE)) return null;
|