create-claude-cabinet 0.46.0 → 0.48.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 +3 -6
- package/lib/CLAUDE.md +218 -0
- package/lib/cli.js +397 -382
- package/lib/copy.js +182 -6
- package/lib/db-setup.js +122 -17
- package/lib/engagement-server-setup.js +1 -17
- package/lib/engagement-setup.js +1 -1
- package/lib/installer-gate.js +135 -0
- package/lib/metadata.js +51 -2
- package/lib/modules.js +292 -0
- package/lib/mux-setup.js +1 -17
- package/lib/project-context.js +16 -96
- package/lib/settings-merge.js +194 -13
- package/lib/site-audit-setup.js +23 -7
- package/lib/verify-setup.js +20 -9
- package/lib/watchtower-setup.js +23 -7
- package/package.json +1 -1
- package/templates/CLAUDE.md +959 -0
- package/templates/briefing/_briefing-template.md +6 -11
- package/templates/cabinet/advisories-state-schema.md +1 -1
- package/templates/cabinet/checklist-stats-schema.md +15 -1
- package/templates/cabinet/eval-protocol.md +27 -10
- package/templates/cabinet/memory-lifecycle-contract.md +135 -0
- package/templates/cabinet/pib-db-access.md +13 -0
- package/templates/cabinet/watchtower-contracts.md +453 -0
- package/templates/cabinet/worktree-invocation-contract.md +111 -0
- package/templates/engagement/OVERVIEW.md +12 -0
- package/templates/engagement/__tests__/checklist-visibility.test.mjs +76 -0
- package/templates/engagement/__tests__/engagement.test.mjs +177 -2
- package/templates/engagement/__tests__/invoice-doc.test.mjs +195 -0
- package/templates/engagement/engagement-checklist.mjs +26 -2
- package/templates/engagement/engagement-preview.mjs +100 -0
- package/templates/engagement/engagement-schema.md +248 -6
- package/templates/engagement/engagement.mjs +391 -11
- package/templates/engagement/pib-db-patches/pib-db-lib.mjs +49 -9
- package/templates/engagement/pib-db-patches/pib-db-mcp-server.mjs +5 -44
- package/templates/engagement/pib-db-patches/pib-db-schema.sql +1 -1
- package/templates/engagement/pib-db-patches/pib-db.mjs +9 -3
- package/templates/engagement/sql-constants.mjs +9 -1
- package/templates/engagement-server/__tests__/cross-tenant-auth.test.mjs +87 -0
- package/templates/engagement-server/server.mjs +65 -40
- package/templates/hooks/action-completion-gate.sh +19 -3
- package/templates/hooks/action-quality-gate.sh +10 -0
- package/templates/hooks/memory-index-guard.sh +17 -11
- package/templates/hooks/skill-telemetry.sh +10 -5
- package/templates/hooks/skill-tool-telemetry.sh +11 -5
- package/templates/hooks/watchtower-session-start.sh +24 -2
- package/templates/mcp/pib-db.json +1 -4
- package/templates/mux/bin/mux +8 -1
- package/templates/mux/config/help.txt +1 -0
- package/templates/rules/acknowledge-when-corrected.md +33 -0
- package/templates/rules/enforcement-pipeline.md +0 -12
- 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__/ahead-check-origin.test.mjs +355 -0
- package/templates/scripts/__tests__/api-usage-idle-gate.test.mjs +81 -0
- package/templates/scripts/__tests__/batch-disposition.test.mjs +98 -0
- package/templates/scripts/__tests__/bsql-loader-verify.test.mjs +47 -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__/hook-runner.test.mjs +247 -0
- package/templates/scripts/__tests__/inbox-assessment.test.mjs +341 -0
- package/templates/scripts/__tests__/memory-reachability.test.mjs +150 -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-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 +120 -0
- 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-pib-error-surfacing.test.mjs +69 -0
- package/templates/scripts/__tests__/ring1-script-drift.test.mjs +126 -0
- package/templates/scripts/__tests__/ring1-stale-open-counts.test.mjs +143 -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__/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 +92 -12
- 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__/ring3-thread-capture.test.mjs +213 -0
- package/templates/scripts/__tests__/ring4-reconcile.test.mjs +393 -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-status-ring4.test.mjs +89 -0
- package/templates/scripts/__tests__/watchtower-sync.test.mjs +311 -0
- package/templates/scripts/audit-coherence-check.mjs +123 -0
- package/templates/scripts/audit-synth.mjs +117 -0
- package/templates/scripts/load-triage-history.js +5 -2
- package/templates/scripts/merge-findings.js +4 -1
- package/templates/scripts/patterns-scope-check.mjs +122 -0
- package/templates/scripts/pib-db-lib.mjs +61 -11
- package/templates/scripts/pib-db-mcp-server.mjs +7 -45
- package/templates/scripts/pib-db-path.mjs +61 -0
- package/templates/scripts/pib-db.mjs +7 -3
- package/templates/scripts/review-server.mjs +7 -12
- package/templates/scripts/triage-server.mjs +7 -8
- package/templates/scripts/validate-memory.mjs +214 -16
- package/templates/scripts/watchtower-advisories.mjs +7 -3
- package/templates/scripts/watchtower-build-context.mjs +192 -8
- package/templates/scripts/watchtower-cross-ring-reader.mjs +714 -0
- package/templates/scripts/watchtower-hook-runner.mjs +422 -0
- package/templates/scripts/watchtower-inbox-assessment.mjs +706 -0
- package/templates/scripts/watchtower-lib.mjs +555 -8
- package/templates/scripts/watchtower-narrative-corpus.mjs +385 -0
- package/templates/scripts/watchtower-phase-shim.mjs +171 -0
- package/templates/scripts/watchtower-queue.mjs +341 -3
- package/templates/scripts/watchtower-ring1.mjs +481 -81
- package/templates/scripts/watchtower-ring2.mjs +748 -80
- package/templates/scripts/watchtower-ring3-close.mjs +1147 -167
- package/templates/scripts/watchtower-ring4-runner.sh +85 -0
- package/templates/scripts/watchtower-ring4.mjs +753 -0
- package/templates/scripts/watchtower-routines.mjs +1 -1
- package/templates/scripts/watchtower-snapshot.mjs +452 -0
- package/templates/scripts/watchtower-status.sh +17 -0
- package/templates/scripts/watchtower-sync.mjs +393 -0
- package/templates/scripts/work-tracker-server.mjs +26 -22
- package/templates/skills/audit/SKILL.md +18 -5
- package/templates/skills/audit/phases/structural-checks.md +22 -0
- package/templates/skills/briefing/SKILL.md +289 -41
- package/templates/skills/cabinet-accessibility/SKILL.md +1 -1
- package/templates/skills/cabinet-process-therapist/SKILL.md +28 -0
- package/templates/skills/cabinet-security/SKILL.md +11 -0
- package/templates/skills/catch-up/SKILL.md +113 -0
- package/templates/skills/cc-publish/SKILL.md +89 -18
- 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 +21 -4
- package/templates/skills/debrief/phases/audit-pattern-capture.md +13 -0
- 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 +71 -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 +177 -31
- package/templates/skills/memory/SKILL.md +22 -6
- package/templates/skills/orient/SKILL.md +75 -5
- 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/pulse/SKILL.md +10 -8
- package/templates/skills/qa-drain/SKILL.md +38 -0
- package/templates/skills/qa-handoff/SKILL.md +287 -46
- package/templates/skills/setup-accounts/SKILL.md +38 -16
- package/templates/skills/spring-clean/SKILL.md +1 -1
- package/templates/skills/spring-clean/phases/execute-decisions.md +1 -1
- package/templates/skills/threads/SKILL.md +4 -0
- package/templates/skills/validate/phases/validators.md +127 -0
- package/templates/skills/watchtower/SKILL.md +174 -30
- package/templates/verify-runtime/CONVENTIONS.md +9 -0
- package/templates/verify-runtime/README.md +37 -0
- package/templates/watchtower/config.json.template +8 -2
- package/templates/workflows/deliberative-audit.js +258 -87
- package/templates/workflows/execute-group-complete.js +26 -3
- package/templates/workflows/execute-group-implement.js +16 -2
package/lib/settings-merge.js
CHANGED
|
@@ -8,7 +8,7 @@ const DEFAULT_HOOKS = {
|
|
|
8
8
|
hooks: [
|
|
9
9
|
{
|
|
10
10
|
type: 'command',
|
|
11
|
-
command: '
|
|
11
|
+
command: '$CLAUDE_PROJECT_DIR/.claude/hooks/git-guardrails.sh',
|
|
12
12
|
},
|
|
13
13
|
],
|
|
14
14
|
},
|
|
@@ -17,7 +17,7 @@ const DEFAULT_HOOKS = {
|
|
|
17
17
|
hooks: [
|
|
18
18
|
{
|
|
19
19
|
type: 'command',
|
|
20
|
-
command: '
|
|
20
|
+
command: '$CLAUDE_PROJECT_DIR/.claude/hooks/work-tracker-guard.sh',
|
|
21
21
|
},
|
|
22
22
|
],
|
|
23
23
|
},
|
|
@@ -26,25 +26,30 @@ const DEFAULT_HOOKS = {
|
|
|
26
26
|
hooks: [
|
|
27
27
|
{
|
|
28
28
|
type: 'command',
|
|
29
|
-
command: '
|
|
29
|
+
command: '$CLAUDE_PROJECT_DIR/.claude/hooks/cc-upstream-guard.sh',
|
|
30
30
|
},
|
|
31
31
|
],
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
|
|
34
|
+
// pib tools arrive as `mcp__<server>__<tool>`, so the bare
|
|
35
|
+
// `pib_create_action` spelling never matched and this gate was silently
|
|
36
|
+
// dead since it shipped (audit cabinet-anthropic-insider-0001, run
|
|
37
|
+
// 09-20-25 — proven live: all 14 audit actions were created with no AC
|
|
38
|
+
// check). Match the real MCP tool name.
|
|
39
|
+
matcher: 'mcp__pib-db__pib_create_action',
|
|
35
40
|
hooks: [
|
|
36
41
|
{
|
|
37
42
|
type: 'command',
|
|
38
|
-
command: '
|
|
43
|
+
command: '$CLAUDE_PROJECT_DIR/.claude/hooks/action-quality-gate.sh',
|
|
39
44
|
},
|
|
40
45
|
],
|
|
41
46
|
},
|
|
42
47
|
{
|
|
43
|
-
matcher: '
|
|
48
|
+
matcher: 'mcp__pib-db__pib_complete_action',
|
|
44
49
|
hooks: [
|
|
45
50
|
{
|
|
46
51
|
type: 'command',
|
|
47
|
-
command: '
|
|
52
|
+
command: '$CLAUDE_PROJECT_DIR/.claude/hooks/action-completion-gate.sh',
|
|
48
53
|
},
|
|
49
54
|
],
|
|
50
55
|
},
|
|
@@ -55,7 +60,7 @@ const DEFAULT_HOOKS = {
|
|
|
55
60
|
hooks: [
|
|
56
61
|
{
|
|
57
62
|
type: 'command',
|
|
58
|
-
command: '
|
|
63
|
+
command: '$CLAUDE_PROJECT_DIR/.claude/hooks/skill-telemetry.sh',
|
|
59
64
|
},
|
|
60
65
|
],
|
|
61
66
|
},
|
|
@@ -66,7 +71,7 @@ const DEFAULT_HOOKS = {
|
|
|
66
71
|
hooks: [
|
|
67
72
|
{
|
|
68
73
|
type: 'command',
|
|
69
|
-
command: '
|
|
74
|
+
command: '$CLAUDE_PROJECT_DIR/.claude/hooks/skill-tool-telemetry.sh',
|
|
70
75
|
},
|
|
71
76
|
],
|
|
72
77
|
},
|
|
@@ -75,7 +80,7 @@ const DEFAULT_HOOKS = {
|
|
|
75
80
|
hooks: [
|
|
76
81
|
{
|
|
77
82
|
type: 'command',
|
|
78
|
-
command: '
|
|
83
|
+
command: '$CLAUDE_PROJECT_DIR/.claude/hooks/memory-index-guard.sh',
|
|
79
84
|
},
|
|
80
85
|
],
|
|
81
86
|
},
|
|
@@ -133,7 +138,29 @@ const BASH_COMPRESS_HOOKS = {
|
|
|
133
138
|
hooks: [
|
|
134
139
|
{
|
|
135
140
|
type: 'command',
|
|
136
|
-
command: '
|
|
141
|
+
command: '$CLAUDE_PROJECT_DIR/.claude/hooks/bash-output-compress.sh',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Maintainer-only ("repo-local") dev hooks — registered by mergeCcDevHooks()
|
|
149
|
+
// ONLY when installing into the Claude Cabinet SOURCE repo (package.json
|
|
150
|
+
// name === 'create-claude-cabinet'), never in a consumer install. node-check
|
|
151
|
+
// mechanizes the CLAUDE.md "node -c after CLI edits" rule (architecture-0004).
|
|
152
|
+
// The hook script is a tracked maintainer tool at scripts/node-check-hook.sh —
|
|
153
|
+
// NOT a MODULES template, so it never ships to consumers. Registered durably
|
|
154
|
+
// (re-added on every dogfood reinstall) rather than hand-added to the gitignored
|
|
155
|
+
// settings.json, which the installer would silently regenerate away.
|
|
156
|
+
const CC_DEV_HOOKS = {
|
|
157
|
+
PostToolUse: [
|
|
158
|
+
{
|
|
159
|
+
matcher: 'Edit|Write',
|
|
160
|
+
hooks: [
|
|
161
|
+
{
|
|
162
|
+
type: 'command',
|
|
163
|
+
command: '$CLAUDE_PROJECT_DIR/scripts/node-check-hook.sh',
|
|
137
164
|
},
|
|
138
165
|
],
|
|
139
166
|
},
|
|
@@ -156,6 +183,38 @@ const LEGACY_HOOK_COMMANDS = [
|
|
|
156
183
|
'domain-memories.sh',
|
|
157
184
|
];
|
|
158
185
|
|
|
186
|
+
// CC-owned hook commands ship with a `$CLAUDE_PROJECT_DIR/` prefix so they
|
|
187
|
+
// resolve regardless of the hook's working directory. Older installs wrote them
|
|
188
|
+
// bare-relative (`.claude/hooks/X.sh`), which break ("No such file or
|
|
189
|
+
// directory") whenever a hook fires from a non-root cwd. bareForm() derives that
|
|
190
|
+
// legacy spelling so a reinstall can REPLACE it (drop old, add prefixed) rather
|
|
191
|
+
// than leave a broken duplicate.
|
|
192
|
+
function bareForm(command) {
|
|
193
|
+
return (command || '').replace(/^\$CLAUDE_PROJECT_DIR\//, '');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Every CC-owned hook command in a hook set, in its current spelling.
|
|
197
|
+
function ccHookCommands(hookSet) {
|
|
198
|
+
return Object.values(hookSet)
|
|
199
|
+
.flatMap(entries => entries.flatMap(e => (e.hooks || []).map(h => h.command)));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Remove every sub-hook whose command exactly matches one of `commands`, and
|
|
203
|
+
// prune entries left empty. Exact-match on CC-owned command strings only — a
|
|
204
|
+
// consumer's own hooks (different paths) are never touched.
|
|
205
|
+
function dropHookCommands(settings, commands) {
|
|
206
|
+
const drop = new Set(commands.filter(Boolean));
|
|
207
|
+
for (const [event, entries] of Object.entries(settings.hooks || {})) {
|
|
208
|
+
if (!Array.isArray(entries)) continue;
|
|
209
|
+
settings.hooks[event] = entries
|
|
210
|
+
.map(entry => (entry.hooks && Array.isArray(entry.hooks))
|
|
211
|
+
? { ...entry, hooks: entry.hooks.filter(h => !drop.has(h.command)) }
|
|
212
|
+
: entry)
|
|
213
|
+
.filter(entry => !entry.hooks || !Array.isArray(entry.hooks) || entry.hooks.length > 0);
|
|
214
|
+
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
159
218
|
/**
|
|
160
219
|
* Merge PIB hooks into the project's .claude/settings.json.
|
|
161
220
|
* Creates the file if it doesn't exist. Preserves existing hooks.
|
|
@@ -199,6 +258,21 @@ function mergeSettings(projectDir, { includeDb = true } = {}) {
|
|
|
199
258
|
if (!settings.hooks[event]) continue;
|
|
200
259
|
}
|
|
201
260
|
|
|
261
|
+
// Retire superseded CC hook entries so the DEFAULT_HOOKS forms below REPLACE
|
|
262
|
+
// them rather than duplicate — covering TWO kinds of drift:
|
|
263
|
+
// (a) a changed command spelling (bare-relative `.claude/hooks/X.sh` →
|
|
264
|
+
// `$CLAUDE_PROJECT_DIR/.claude/hooks/X.sh`, which resolves from any cwd);
|
|
265
|
+
// (b) a changed MATCHER on an unchanged command (the bare `pib_create_action`
|
|
266
|
+
// matcher → `mcp__pib-db__pib_create_action`). The merge loop below
|
|
267
|
+
// dedups by COMMAND alone, so a stale-matcher entry would otherwise
|
|
268
|
+
// survive untouched while the corrected entry was skipped as a dup —
|
|
269
|
+
// exactly how the pib gate hooks stayed dead across reinstalls.
|
|
270
|
+
// Dropping every CURRENT-spelling CC command (and its bare form) forces a
|
|
271
|
+
// clean re-add with the correct current matcher. Exact-match on CC-owned
|
|
272
|
+
// commands only — consumer-authored hooks are left intact.
|
|
273
|
+
const ccCommands = ccHookCommands(DEFAULT_HOOKS);
|
|
274
|
+
dropHookCommands(settings, [...ccCommands, ...ccCommands.map(bareForm)]);
|
|
275
|
+
|
|
202
276
|
// Merge each hook event type from DEFAULT_HOOKS
|
|
203
277
|
for (const [event, newHooks] of Object.entries(DEFAULT_HOOKS)) {
|
|
204
278
|
if (!settings.hooks[event]) {
|
|
@@ -254,7 +328,7 @@ function healUserSettings() {
|
|
|
254
328
|
const filtered = entries.filter(entry => {
|
|
255
329
|
if (!entry.hooks || !Array.isArray(entry.hooks)) return true;
|
|
256
330
|
const hasRelativeCcHook = entry.hooks.some(h =>
|
|
257
|
-
|
|
331
|
+
/^(\$CLAUDE_PROJECT_DIR\/)?\.claude\/hooks\//.test(h.command || '')
|
|
258
332
|
);
|
|
259
333
|
if (hasRelativeCcHook) removed++;
|
|
260
334
|
return !hasRelativeCcHook;
|
|
@@ -338,6 +412,9 @@ function mergeBashCompressHooks(settingsPath) {
|
|
|
338
412
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
339
413
|
if (!settings.hooks) settings.hooks = {};
|
|
340
414
|
|
|
415
|
+
// Replace any superseded bare-relative form with the prefixed one (see mergeSettings).
|
|
416
|
+
dropHookCommands(settings, ccHookCommands(BASH_COMPRESS_HOOKS).map(bareForm));
|
|
417
|
+
|
|
341
418
|
for (const [event, newHooks] of Object.entries(BASH_COMPRESS_HOOKS)) {
|
|
342
419
|
if (!settings.hooks[event]) {
|
|
343
420
|
settings.hooks[event] = newHooks;
|
|
@@ -358,4 +435,108 @@ function mergeBashCompressHooks(settingsPath) {
|
|
|
358
435
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
359
436
|
}
|
|
360
437
|
|
|
361
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Register maintainer-only ("repo-local") dev hooks — but ONLY when installing
|
|
440
|
+
* into the Claude Cabinet SOURCE repo itself (target package.json
|
|
441
|
+
* name === 'create-claude-cabinet'). A silent no-op in every consumer project.
|
|
442
|
+
* Idempotent + reinstall-safe: drops the CC-owned dev commands (current + bare
|
|
443
|
+
* spellings) then re-adds them, so a reinstall REPLACES rather than duplicates.
|
|
444
|
+
* Returns true if it registered, false if it no-op'd (not the CC repo).
|
|
445
|
+
*/
|
|
446
|
+
function mergeCcDevHooks(projectDir) {
|
|
447
|
+
let pkgName = '';
|
|
448
|
+
try {
|
|
449
|
+
pkgName =
|
|
450
|
+
JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), 'utf8'))
|
|
451
|
+
.name || '';
|
|
452
|
+
} catch {
|
|
453
|
+
/* no/invalid package.json → not the CC repo → no-op */
|
|
454
|
+
}
|
|
455
|
+
if (pkgName !== 'create-claude-cabinet') return false;
|
|
456
|
+
|
|
457
|
+
const settingsDir = path.join(projectDir, '.claude');
|
|
458
|
+
const settingsPath = path.join(settingsDir, 'settings.json');
|
|
459
|
+
if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
|
|
460
|
+
|
|
461
|
+
let settings = {};
|
|
462
|
+
if (fs.existsSync(settingsPath)) {
|
|
463
|
+
try {
|
|
464
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
465
|
+
} catch {
|
|
466
|
+
settings = {};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
|
|
470
|
+
settings = {};
|
|
471
|
+
}
|
|
472
|
+
if (!settings.hooks) settings.hooks = {};
|
|
473
|
+
|
|
474
|
+
// Drop current + bare spellings so a reinstall replaces, not duplicates.
|
|
475
|
+
const devCommands = ccHookCommands(CC_DEV_HOOKS);
|
|
476
|
+
dropHookCommands(settings, [...devCommands, ...devCommands.map(bareForm)]);
|
|
477
|
+
|
|
478
|
+
for (const [event, newHooks] of Object.entries(CC_DEV_HOOKS)) {
|
|
479
|
+
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
480
|
+
for (const newHook of newHooks) settings.hooks[event].push(newHook);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Set `skillOverrides: "name-only"` for the given skills in .claude/settings.json
|
|
489
|
+
* (act:bee0fa16, cabinet-anthropic-insider-0002). Claude Code loads every skill's
|
|
490
|
+
* name+description into context so the model can auto-invoke by trigger keyword,
|
|
491
|
+
* under a budget of ~1% of the context window; when it overflows, the
|
|
492
|
+
* least-invoked skills' descriptions are dropped first — silently stripping the
|
|
493
|
+
* keywords that make them auto-invocable. cabinet-* skills are ~half that
|
|
494
|
+
* listing and are spawned by name (agentType), never auto-invoked by
|
|
495
|
+
* description, so "name-only" costs them nothing while freeing budget for the
|
|
496
|
+
* routing skills (orient/debrief/briefing/inbox/…) that DO need full keywords.
|
|
497
|
+
*
|
|
498
|
+
* Only-if-absent: an existing skillOverrides entry (a user who deliberately set
|
|
499
|
+
* a cabinet skill back to "on") is never clobbered. Idempotent — a reinstall
|
|
500
|
+
* re-applies only for skills with no entry yet (e.g. a newly-added cabinet
|
|
501
|
+
* member). Creates .claude/settings.json if absent. Returns the count added.
|
|
502
|
+
*/
|
|
503
|
+
function mergeSkillOverrides(projectDir, skillNames = []) {
|
|
504
|
+
if (!Array.isArray(skillNames) || skillNames.length === 0) return 0;
|
|
505
|
+
const settingsDir = path.join(projectDir, '.claude');
|
|
506
|
+
const settingsPath = path.join(settingsDir, 'settings.json');
|
|
507
|
+
if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
|
|
508
|
+
|
|
509
|
+
let settings = {};
|
|
510
|
+
if (fs.existsSync(settingsPath)) {
|
|
511
|
+
try {
|
|
512
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
513
|
+
} catch {
|
|
514
|
+
settings = {};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
|
|
518
|
+
settings = {};
|
|
519
|
+
}
|
|
520
|
+
if (
|
|
521
|
+
!settings.skillOverrides ||
|
|
522
|
+
typeof settings.skillOverrides !== 'object' ||
|
|
523
|
+
Array.isArray(settings.skillOverrides)
|
|
524
|
+
) {
|
|
525
|
+
settings.skillOverrides = {};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let added = 0;
|
|
529
|
+
for (const name of skillNames) {
|
|
530
|
+
if (!name) continue;
|
|
531
|
+
if (!(name in settings.skillOverrides)) {
|
|
532
|
+
settings.skillOverrides[name] = 'name-only';
|
|
533
|
+
added++;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (added > 0) {
|
|
537
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
538
|
+
}
|
|
539
|
+
return added;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
module.exports = { mergeSettings, healUserSettings, mergeWatchtowerHooks, mergeMuxHooks, mergeBashCompressHooks, mergeCcDevHooks, mergeSkillOverrides, DEFAULT_HOOKS, WATCHTOWER_HOOKS, MUX_HOOKS, BASH_COMPRESS_HOOKS, CC_DEV_HOOKS, LEGACY_HOOK_COMMANDS };
|
package/lib/site-audit-setup.js
CHANGED
|
@@ -10,12 +10,17 @@ const { execSync } = require('child_process');
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const os = require('os');
|
|
13
|
+
const { tarballIsStale, writeSourceHash } = require('./installer-gate');
|
|
13
14
|
|
|
14
15
|
const CC_HOME = path.join(os.homedir(), '.claude-cabinet');
|
|
15
16
|
const SITE_AUDIT_BASE = path.join(CC_HOME, 'site-audit');
|
|
16
17
|
|
|
17
18
|
function setupSiteAuditRuntime(opts = {}) {
|
|
18
19
|
const dryRun = !!opts.dryRun;
|
|
20
|
+
const exec = opts.exec || execSync; // injectable for tests
|
|
21
|
+
// The heavy extract+install step (npm install, puppeteer, chromedriver) is
|
|
22
|
+
// injectable so the pack/skip/re-pack gate can be exercised in tests.
|
|
23
|
+
const installTarball = opts.installTarball || extractAndInstall;
|
|
19
24
|
const runtimeSourceDir =
|
|
20
25
|
opts.runtimeSourceDir || path.resolve(__dirname, '..', 'templates', 'site-audit-runtime');
|
|
21
26
|
|
|
@@ -43,20 +48,28 @@ function setupSiteAuditRuntime(opts = {}) {
|
|
|
43
48
|
return { installPath: installDir, version, status: 'dry-run', results };
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
// Idempotency + equal-version fall-through: reuse the tarball ONLY when the
|
|
52
|
+
// runtime SOURCE content is unchanged (hash sidecar matches). A same-version
|
|
53
|
+
// reinstall carrying a template edit re-packs instead of hard-skipping (the
|
|
54
|
+
// tarball parallel to act:df1a1dc3; technical-debt-0003).
|
|
55
|
+
const staleness = tarballIsStale({ tarballPath, installDir, runtimeSourceDir });
|
|
56
|
+
if (!staleness.stale) {
|
|
57
|
+
// Tarball exists and source is unchanged — ensure it's extracted and
|
|
58
|
+
// installed (may have been packed but not installed on a prior run)
|
|
59
|
+
installTarball(path.join(SITE_AUDIT_BASE, version), tarballPath, results);
|
|
50
60
|
updateCurrentBinSymlink(SITE_AUDIT_BASE, version);
|
|
51
61
|
writeVersionPointer(versionPointer, version);
|
|
52
|
-
results.push(`@claude-cabinet/site-audit@${version} already installed (${tarballPath})`);
|
|
62
|
+
results.push(`@claude-cabinet/site-audit@${version} already installed, source unchanged (${tarballPath})`);
|
|
53
63
|
return { installPath: installDir, version, status: 'skipped', results };
|
|
54
64
|
}
|
|
65
|
+
if (staleness.reason === 'source-changed' || staleness.reason === 'no-source-hash') {
|
|
66
|
+
results.push(`@claude-cabinet/site-audit@${version} runtime source changed — re-packing`);
|
|
67
|
+
}
|
|
55
68
|
|
|
56
69
|
if (fs.existsSync(tarballPath)) fs.unlinkSync(tarballPath);
|
|
57
70
|
|
|
58
71
|
fs.mkdirSync(installDir, { recursive: true });
|
|
59
|
-
const packStdout =
|
|
72
|
+
const packStdout = exec(`npm pack --silent --pack-destination "${installDir}"`, {
|
|
60
73
|
cwd: runtimeSourceDir,
|
|
61
74
|
encoding: 'utf8',
|
|
62
75
|
}).trim();
|
|
@@ -75,8 +88,11 @@ function setupSiteAuditRuntime(opts = {}) {
|
|
|
75
88
|
}
|
|
76
89
|
|
|
77
90
|
// Extract and install so the runtime is runnable (not just a tarball)
|
|
78
|
-
|
|
91
|
+
installTarball(path.join(SITE_AUDIT_BASE, version), tarballPath, results);
|
|
79
92
|
|
|
93
|
+
// Record the runtime-source hash so the next same-version reinstall can
|
|
94
|
+
// skip when unchanged and re-pack when it changed.
|
|
95
|
+
writeSourceHash(installDir, staleness.sourceHash);
|
|
80
96
|
writeVersionPointer(versionPointer, version);
|
|
81
97
|
updateCurrentBinSymlink(SITE_AUDIT_BASE, version);
|
|
82
98
|
results.push(`Installed @claude-cabinet/site-audit@${version}`);
|
package/lib/verify-setup.js
CHANGED
|
@@ -15,6 +15,7 @@ const { execSync } = require('child_process');
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const os = require('os');
|
|
18
|
+
const { tarballIsStale, writeSourceHash } = require('./installer-gate');
|
|
18
19
|
|
|
19
20
|
const CC_HOME = path.join(os.homedir(), '.claude-cabinet');
|
|
20
21
|
const VERIFY_BASE = path.join(CC_HOME, 'verify');
|
|
@@ -40,6 +41,7 @@ const VERIFY_BASE = path.join(CC_HOME, 'verify');
|
|
|
40
41
|
*/
|
|
41
42
|
function setupVerifyRuntime(opts = {}) {
|
|
42
43
|
const dryRun = !!opts.dryRun;
|
|
44
|
+
const exec = opts.exec || execSync; // injectable for tests
|
|
43
45
|
const runtimeSourceDir =
|
|
44
46
|
opts.runtimeSourceDir || path.resolve(__dirname, '..', 'templates', 'verify-runtime');
|
|
45
47
|
|
|
@@ -72,21 +74,27 @@ function setupVerifyRuntime(opts = {}) {
|
|
|
72
74
|
return { installPath: installDir, version, status: 'dry-run', results };
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
// 3. Idempotency
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
// 3. Idempotency + equal-version fall-through. A present, non-truncated
|
|
78
|
+
// tarball is reused ONLY when the runtime SOURCE content is unchanged (its
|
|
79
|
+
// hash sidecar still matches). A same-version reinstall that carries a
|
|
80
|
+
// template edit re-packs instead of hard-skipping — the tarball parallel
|
|
81
|
+
// to the per-file-manifest fix (act:df1a1dc3; technical-debt-0003). A
|
|
82
|
+
// truncated stub (earlier fixture touch → ENODATA/TAR_BAD_ARCHIVE on the
|
|
83
|
+
// consumer) also counts as stale.
|
|
84
|
+
const staleness = tarballIsStale({ tarballPath, installDir, runtimeSourceDir });
|
|
85
|
+
if (!staleness.stale) {
|
|
86
|
+
results.push(`cabinet-verify@${version} already installed, source unchanged (${tarballPath})`);
|
|
82
87
|
// Even on skip, ensure the VERSION pointer is up-to-date so a later
|
|
83
88
|
// version downgrade doesn't leave a stale pointer behind.
|
|
84
89
|
writeVersionPointer(versionPointer, version);
|
|
85
90
|
results.push(`Updated VERSION pointer: ${versionPointer}`);
|
|
86
91
|
return { installPath: installDir, version, status: 'skipped', results };
|
|
87
92
|
}
|
|
93
|
+
if (staleness.reason === 'source-changed' || staleness.reason === 'no-source-hash') {
|
|
94
|
+
results.push(`cabinet-verify@${version} runtime source changed — re-packing`);
|
|
95
|
+
}
|
|
88
96
|
|
|
89
|
-
// Remove a
|
|
97
|
+
// Remove a stale/stub tarball before re-packing so the move below doesn't
|
|
90
98
|
// hit a "file exists" surprise.
|
|
91
99
|
if (fs.existsSync(tarballPath)) {
|
|
92
100
|
fs.unlinkSync(tarballPath);
|
|
@@ -96,7 +104,7 @@ function setupVerifyRuntime(opts = {}) {
|
|
|
96
104
|
fs.mkdirSync(installDir, { recursive: true });
|
|
97
105
|
// Run npm pack in the runtime source dir. --pack-destination writes
|
|
98
106
|
// the tarball directly to installDir without an intermediate cwd move.
|
|
99
|
-
const packStdout =
|
|
107
|
+
const packStdout = exec(`npm pack --silent --pack-destination "${installDir}"`, {
|
|
100
108
|
cwd: runtimeSourceDir,
|
|
101
109
|
encoding: 'utf8',
|
|
102
110
|
}).trim();
|
|
@@ -122,6 +130,9 @@ function setupVerifyRuntime(opts = {}) {
|
|
|
122
130
|
);
|
|
123
131
|
}
|
|
124
132
|
|
|
133
|
+
// Record the runtime-source hash so the next same-version reinstall can
|
|
134
|
+
// skip when the source is unchanged, and re-pack when it changed.
|
|
135
|
+
writeSourceHash(installDir, staleness.sourceHash);
|
|
125
136
|
results.push(`Installed cabinet-verify@${version} to ${tarballPath}`);
|
|
126
137
|
|
|
127
138
|
// 5. Write VERSION pointer
|
package/lib/watchtower-setup.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
const fs = require('fs');
|
|
35
35
|
const path = require('path');
|
|
36
36
|
const os = require('os');
|
|
37
|
-
const
|
|
37
|
+
const { sha256 } = require('./installer-gate');
|
|
38
38
|
|
|
39
39
|
const CC_HOME = path.join(os.homedir(), '.claude-cabinet');
|
|
40
40
|
const GLOBAL_MANIFEST_PATH = path.join(CC_HOME, 'global-manifest.json');
|
|
@@ -76,6 +76,7 @@ function buildManagedFiles() {
|
|
|
76
76
|
const shellRunners = [
|
|
77
77
|
'watchtower-ring1-runner.sh',
|
|
78
78
|
'watchtower-ring2-runner.sh',
|
|
79
|
+
'watchtower-ring4-runner.sh',
|
|
79
80
|
'watchtower-status.sh',
|
|
80
81
|
];
|
|
81
82
|
for (const name of shellRunners) {
|
|
@@ -110,10 +111,6 @@ function buildManagedFiles() {
|
|
|
110
111
|
|
|
111
112
|
const MANAGED_FILES = buildManagedFiles();
|
|
112
113
|
|
|
113
|
-
function sha256(content) {
|
|
114
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
114
|
function readGlobalManifest() {
|
|
118
115
|
if (!fs.existsSync(GLOBAL_MANIFEST_PATH)) return { files: {} };
|
|
119
116
|
try {
|
|
@@ -168,8 +165,27 @@ function refreshWatchtowerRuntime(opts = {}) {
|
|
|
168
165
|
const content = fs.readFileSync(file.src);
|
|
169
166
|
const hash = sha256(content);
|
|
170
167
|
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
// Content-aware skip: compare the template hash to the runtime file's
|
|
169
|
+
// ACTUAL on-disk bytes, NOT the cached manifest. The manifest can lie — a
|
|
170
|
+
// hand-edited or externally-replaced runtime file still carries the hash
|
|
171
|
+
// the manifest recorded at last write, so a manifest-only check would skip
|
|
172
|
+
// it and leave the divergence running. Reading the real file makes the
|
|
173
|
+
// refresh self-healing: any runtime file that differs from the template
|
|
174
|
+
// (or is missing) is rewritten, so a reinstall reliably reconverges the
|
|
175
|
+
// runtime onto the template instead of silently leaving it stale.
|
|
176
|
+
let runtimeHash = null;
|
|
177
|
+
if (fs.existsSync(file.dest)) {
|
|
178
|
+
try {
|
|
179
|
+
runtimeHash = sha256(fs.readFileSync(file.dest));
|
|
180
|
+
} catch {
|
|
181
|
+
runtimeHash = null; // unreadable runtime file → treat as divergent
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (runtimeHash === hash) {
|
|
186
|
+
// Already in sync on disk. Keep the manifest honest as bookkeeping.
|
|
187
|
+
if (!dryRun) manifest.files[file.dest] = hash;
|
|
188
|
+
continue;
|
|
173
189
|
}
|
|
174
190
|
|
|
175
191
|
if (dryRun) {
|
package/package.json
CHANGED