create-claude-cabinet 0.47.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 +1 -0
- package/lib/CLAUDE.md +218 -0
- package/lib/cli.js +102 -344
- package/lib/copy.js +75 -1
- package/lib/engagement-server-setup.js +1 -17
- package/lib/engagement-setup.js +1 -1
- package/lib/installer-gate.js +135 -0
- 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 +148 -9
- package/lib/site-audit-setup.js +23 -7
- package/lib/verify-setup.js +20 -9
- package/lib/watchtower-setup.js +1 -5
- package/package.json +1 -1
- package/templates/CLAUDE.md +959 -0
- package/templates/briefing/_briefing-template.md +6 -11
- package/templates/cabinet/eval-protocol.md +27 -10
- package/templates/cabinet/watchtower-contracts.md +39 -0
- package/templates/cabinet/worktree-invocation-contract.md +24 -0
- package/templates/engagement/__tests__/checklist-visibility.test.mjs +76 -0
- package/templates/engagement/engagement-checklist.mjs +26 -2
- package/templates/engagement/engagement-schema.md +8 -6
- package/templates/engagement/pib-db-patches/pib-db-lib.mjs +49 -9
- package/templates/engagement/pib-db-patches/pib-db-schema.sql +1 -1
- package/templates/engagement/pib-db-patches/pib-db.mjs +4 -1
- 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 +10 -0
- package/templates/hooks/action-quality-gate.sh +10 -0
- package/templates/hooks/skill-telemetry.sh +10 -5
- package/templates/hooks/skill-tool-telemetry.sh +11 -5
- package/templates/mcp/pib-db.json +1 -1
- package/templates/rules/enforcement-pipeline.md +0 -12
- package/templates/scripts/__tests__/api-usage-idle-gate.test.mjs +81 -0
- package/templates/scripts/__tests__/bsql-loader-verify.test.mjs +47 -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__/ring1-pib-error-surfacing.test.mjs +69 -0
- package/templates/scripts/__tests__/ring1-stale-open-counts.test.mjs +143 -0
- package/templates/scripts/__tests__/ring3-thread-capture.test.mjs +213 -0
- package/templates/scripts/__tests__/watchtower-status-ring4.test.mjs +89 -0
- package/templates/scripts/audit-coherence-check.mjs +123 -0
- package/templates/scripts/audit-synth.mjs +117 -0
- 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 +2 -1
- package/templates/scripts/pib-db.mjs +2 -1
- package/templates/scripts/review-server.mjs +7 -12
- package/templates/scripts/triage-server.mjs +7 -8
- package/templates/scripts/watchtower-cross-ring-reader.mjs +20 -6
- package/templates/scripts/watchtower-inbox-assessment.mjs +706 -0
- package/templates/scripts/watchtower-lib.mjs +282 -4
- package/templates/scripts/watchtower-ring1.mjs +78 -38
- package/templates/scripts/watchtower-ring2.mjs +38 -12
- package/templates/scripts/watchtower-ring3-close.mjs +160 -21
- package/templates/scripts/watchtower-status.sh +17 -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 +8 -0
- package/templates/skills/cabinet-accessibility/SKILL.md +1 -1
- package/templates/skills/cc-publish/SKILL.md +41 -7
- package/templates/skills/debrief/phases/audit-pattern-capture.md +13 -0
- package/templates/skills/inbox/SKILL.md +95 -5
- package/templates/skills/orient/SKILL.md +4 -0
- package/templates/skills/pulse/SKILL.md +10 -8
- package/templates/skills/spring-clean/SKILL.md +1 -1
- package/templates/skills/spring-clean/phases/execute-decisions.md +1 -1
- package/templates/skills/validate/phases/validators.md +86 -0
- package/templates/skills/watchtower/SKILL.md +18 -26
- package/templates/workflows/deliberative-audit.js +258 -87
- package/templates/workflows/execute-group-complete.js +16 -2
- package/templates/workflows/execute-group-implement.js +16 -2
package/lib/settings-merge.js
CHANGED
|
@@ -31,7 +31,12 @@ const DEFAULT_HOOKS = {
|
|
|
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',
|
|
@@ -40,7 +45,7 @@ const DEFAULT_HOOKS = {
|
|
|
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',
|
|
@@ -140,6 +145,28 @@ const BASH_COMPRESS_HOOKS = {
|
|
|
140
145
|
],
|
|
141
146
|
};
|
|
142
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',
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
|
|
143
170
|
// Legacy hook script names that should be stripped on any merge.
|
|
144
171
|
// Centralizes cleanup so a user who skips --migrate-memory but runs
|
|
145
172
|
// any other CC operation still gets omega-era hooks pruned.
|
|
@@ -231,12 +258,20 @@ function mergeSettings(projectDir, { includeDb = true } = {}) {
|
|
|
231
258
|
if (!settings.hooks[event]) continue;
|
|
232
259
|
}
|
|
233
260
|
|
|
234
|
-
// Retire superseded
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
|
|
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)]);
|
|
240
275
|
|
|
241
276
|
// Merge each hook event type from DEFAULT_HOOKS
|
|
242
277
|
for (const [event, newHooks] of Object.entries(DEFAULT_HOOKS)) {
|
|
@@ -400,4 +435,108 @@ function mergeBashCompressHooks(settingsPath) {
|
|
|
400
435
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
401
436
|
}
|
|
402
437
|
|
|
403
|
-
|
|
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');
|
|
@@ -111,10 +111,6 @@ function buildManagedFiles() {
|
|
|
111
111
|
|
|
112
112
|
const MANAGED_FILES = buildManagedFiles();
|
|
113
113
|
|
|
114
|
-
function sha256(content) {
|
|
115
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
114
|
function readGlobalManifest() {
|
|
119
115
|
if (!fs.existsSync(GLOBAL_MANIFEST_PATH)) return { files: {} };
|
|
120
116
|
try {
|
package/package.json
CHANGED