create-claude-cabinet 0.45.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -10
- package/lib/cli.js +374 -91
- package/lib/copy.js +108 -6
- package/lib/db-setup.js +122 -17
- package/lib/engagement-server-setup.js +34 -9
- package/lib/metadata.js +51 -2
- package/lib/migrate-from-omega.js +13 -1
- package/lib/mux-setup.js +33 -9
- package/lib/settings-merge.js +52 -10
- package/lib/watchtower-setup.js +230 -0
- package/package.json +5 -1
- package/templates/cabinet/_cabinet-member-template.md +8 -3
- package/templates/cabinet/advisories-state-schema.md +34 -7
- package/templates/cabinet/checklist-stats-schema.md +15 -1
- package/templates/cabinet/composition-patterns.md +4 -3
- package/templates/cabinet/memory-lifecycle-contract.md +135 -0
- package/templates/cabinet/pib-db-access.md +13 -0
- package/templates/cabinet/skill-output-conventions.md +35 -1
- package/templates/cabinet/watchtower-contracts.md +503 -1
- package/templates/cabinet/worktree-invocation-contract.md +87 -0
- package/templates/engagement/OVERVIEW.md +12 -0
- package/templates/engagement/__tests__/engagement.test.mjs +177 -2
- package/templates/engagement/__tests__/invoice-doc.test.mjs +195 -0
- package/templates/engagement/engagement-preview.mjs +100 -0
- package/templates/engagement/engagement-schema.md +240 -0
- package/templates/engagement/engagement.mjs +391 -11
- package/templates/engagement/pib-db-patches/pib-db-lib.mjs +10 -1
- package/templates/engagement/pib-db-patches/pib-db-mcp-server.mjs +5 -44
- package/templates/engagement/pib-db-patches/pib-db.mjs +5 -2
- package/templates/hooks/action-completion-gate.sh +9 -3
- package/templates/hooks/memory-index-guard.sh +17 -11
- package/templates/hooks/watchtower-session-start.sh +24 -2
- package/templates/mcp/pib-db.json +1 -4
- package/templates/mux/__tests__/mux-fail-loud.fixture.sh +44 -0
- package/templates/mux/__tests__/station-liveness.fixture.sh +234 -0
- package/templates/mux/__tests__/station-liveness.test.mjs +47 -0
- package/templates/mux/bin/mux +289 -56
- package/templates/mux/config/help.txt +1 -0
- package/templates/rules/acknowledge-when-corrected.md +33 -0
- package/templates/rules/maintainability.md +11 -0
- package/templates/rules/memory-capture.md +26 -4
- package/templates/rules/plan-before-bulk-or-irreversible-actions.md +48 -0
- package/templates/rules/verify-before-asserting.md +79 -0
- package/templates/scripts/__tests__/advisor-pass.test.mjs +238 -0
- package/templates/scripts/__tests__/advisories.test.mjs +262 -0
- package/templates/scripts/__tests__/ahead-check-origin.test.mjs +355 -0
- package/templates/scripts/__tests__/batch-disposition.test.mjs +235 -0
- package/templates/scripts/__tests__/claude-churn-authored.test.mjs +90 -0
- package/templates/scripts/__tests__/cross-ring-reader.test.mjs +505 -0
- package/templates/scripts/__tests__/dx-captures-briefing.test.mjs +169 -0
- package/templates/scripts/__tests__/feedback-outbox-flush.test.mjs +232 -0
- package/templates/scripts/__tests__/hook-runner.test.mjs +247 -0
- package/templates/scripts/__tests__/narrative-corpus.test.mjs +247 -0
- package/templates/scripts/__tests__/phase-shim.test.mjs +112 -0
- package/templates/scripts/__tests__/qa-handoff-gate.test.mjs +68 -0
- package/templates/scripts/__tests__/qa-handoff-merge-state.test.mjs +162 -0
- package/templates/scripts/__tests__/resolve-cli.test.mjs +314 -0
- package/templates/scripts/__tests__/ring-state-ownership.test.mjs +228 -3
- package/templates/scripts/__tests__/ring1-content-detector.test.mjs +168 -0
- package/templates/scripts/__tests__/ring1-flagged-actions.test.mjs +135 -0
- package/templates/scripts/__tests__/ring1-script-drift.test.mjs +126 -0
- package/templates/scripts/__tests__/ring2-queue-reader.test.mjs +80 -0
- package/templates/scripts/__tests__/ring2-recall-canary.test.mjs +150 -0
- package/templates/scripts/__tests__/ring2-roster-review.test.mjs +311 -0
- package/templates/scripts/__tests__/ring2-thread-context.test.mjs +189 -0
- package/templates/scripts/__tests__/ring3-chunk-merge.test.mjs +122 -0
- package/templates/scripts/__tests__/ring3-close-lenses.test.mjs +359 -0
- package/templates/scripts/__tests__/ring3-dedup.test.mjs +467 -0
- package/templates/scripts/__tests__/ring3-memory-titles.test.mjs +187 -0
- package/templates/scripts/__tests__/ring3-novelty-rescue.test.mjs +148 -0
- package/templates/scripts/__tests__/ring3-recent-slice.test.mjs +101 -0
- package/templates/scripts/__tests__/ring4-reconcile.test.mjs +393 -0
- package/templates/scripts/__tests__/routine-dispatch.test.mjs +312 -0
- package/templates/scripts/__tests__/shared-thread-reader.test.mjs +187 -0
- package/templates/scripts/__tests__/suppression-ledger.test.mjs +148 -0
- package/templates/scripts/__tests__/verify-backfill.test.mjs +103 -0
- package/templates/scripts/__tests__/verify-coverage.test.mjs +80 -0
- package/templates/scripts/__tests__/watchtower-snapshot.test.mjs +261 -0
- package/templates/scripts/__tests__/watchtower-sync.test.mjs +311 -0
- package/templates/scripts/load-triage-history.js +5 -2
- package/templates/scripts/pib-db-mcp-server.mjs +5 -44
- package/templates/scripts/pib-db-path.mjs +61 -0
- package/templates/scripts/pib-db.mjs +5 -2
- package/templates/scripts/validate-memory.mjs +214 -16
- package/templates/scripts/watchtower-advisories.mjs +309 -0
- package/templates/scripts/watchtower-build-context.mjs +301 -18
- package/templates/scripts/watchtower-cross-ring-reader.mjs +700 -0
- package/templates/scripts/watchtower-hook-runner.mjs +422 -0
- package/templates/scripts/watchtower-lib.mjs +447 -2
- package/templates/scripts/watchtower-narrative-corpus.mjs +385 -0
- package/templates/scripts/watchtower-phase-shim.mjs +171 -0
- package/templates/scripts/watchtower-queue.mjs +484 -1
- package/templates/scripts/watchtower-ring1.mjs +533 -53
- package/templates/scripts/watchtower-ring2.mjs +787 -48
- package/templates/scripts/watchtower-ring3-close.mjs +1357 -99
- package/templates/scripts/watchtower-ring4-runner.sh +85 -0
- package/templates/scripts/watchtower-ring4.mjs +753 -0
- package/templates/scripts/watchtower-routines.mjs +358 -0
- package/templates/scripts/watchtower-snapshot.mjs +452 -0
- package/templates/scripts/watchtower-status.sh +1 -1
- package/templates/scripts/watchtower-sync.mjs +393 -0
- package/templates/skills/audit/SKILL.md +5 -1
- package/templates/skills/briefing/SKILL.md +584 -236
- package/templates/skills/cabinet-anthropic-insider/SKILL.md +14 -6
- package/templates/skills/cabinet-historian/SKILL.md +14 -11
- package/templates/skills/cabinet-process-therapist/SKILL.md +28 -0
- package/templates/skills/cabinet-security/SKILL.md +11 -0
- package/templates/skills/cabinet-system-advocate/SKILL.md +22 -21
- package/templates/skills/cabinet-user-advocate/SKILL.md +13 -7
- package/templates/skills/catch-up/SKILL.md +113 -0
- package/templates/skills/cc-publish/SKILL.md +148 -25
- package/templates/skills/cc-remember/SKILL.md +45 -0
- package/templates/skills/close/SKILL.md +107 -0
- package/templates/skills/collab-client/SKILL.md +22 -5
- package/templates/skills/collab-consultant/SKILL.md +110 -2
- package/templates/skills/debrief/SKILL.md +148 -16
- package/templates/skills/debrief-classic/SKILL.md +696 -0
- package/templates/skills/debrief-classic/calibration.md +44 -0
- package/templates/skills/debrief-classic/phases/audit-pattern-capture.md +78 -0
- package/templates/skills/debrief-classic/phases/auto-maintenance.md +48 -0
- package/templates/skills/debrief-classic/phases/checklist-feedback.md +123 -0
- package/templates/skills/debrief-classic/phases/close-work.md +163 -0
- package/templates/skills/debrief-classic/phases/health-checks.md +54 -0
- package/templates/skills/debrief-classic/phases/inventory.md +40 -0
- package/templates/skills/debrief-classic/phases/loose-ends.md +52 -0
- package/templates/skills/debrief-classic/phases/methodology-capture.md +223 -0
- package/templates/skills/debrief-classic/phases/qa-handoff-sweep.md +78 -0
- package/templates/skills/debrief-classic/phases/record-lessons.md +177 -0
- package/templates/skills/debrief-classic/phases/report.md +59 -0
- package/templates/skills/debrief-classic/phases/update-state.md +48 -0
- package/templates/skills/debrief-classic/phases/upstream-feedback.md +185 -0
- package/templates/skills/debrief-classic/phases/verify-coverage.md +101 -0
- package/templates/skills/execute/SKILL.md +77 -6
- package/templates/skills/execute/phases/commit-and-deploy.md +8 -0
- package/templates/skills/execute-group/SKILL.md +23 -26
- package/templates/skills/generate-plan-groups/SKILL.md +20 -0
- package/templates/skills/inbox/SKILL.md +124 -7
- package/templates/skills/memory/SKILL.md +22 -6
- package/templates/skills/orient/SKILL.md +140 -52
- package/templates/skills/orient-classic/SKILL.md +770 -0
- package/templates/skills/orient-classic/phases/auto-maintenance.md +52 -0
- package/templates/skills/orient-classic/phases/briefing.md +53 -0
- package/templates/skills/orient-classic/phases/cabinet.md +46 -0
- package/templates/skills/orient-classic/phases/checklist-status.md +54 -0
- package/templates/skills/orient-classic/phases/context.md +88 -0
- package/templates/skills/orient-classic/phases/data-sync.md +35 -0
- package/templates/skills/orient-classic/phases/deferred-check.md +55 -0
- package/templates/skills/orient-classic/phases/dx-captures.md +53 -0
- package/templates/skills/orient-classic/phases/health-checks.md +50 -0
- package/templates/skills/orient-classic/phases/verify-backfill.md +109 -0
- package/templates/skills/orient-classic/phases/work-scan.md +69 -0
- package/templates/skills/plan/SKILL.md +8 -0
- package/templates/skills/qa-drain/SKILL.md +157 -0
- package/templates/skills/qa-handoff/SKILL.md +287 -46
- package/templates/skills/session-handoff/SKILL.md +175 -6
- package/templates/skills/setup-accounts/SKILL.md +38 -16
- package/templates/skills/threads/SKILL.md +4 -0
- package/templates/skills/triage-audit/SKILL.md +6 -0
- package/templates/skills/validate/phases/validators.md +41 -0
- package/templates/skills/watchtower/SKILL.md +199 -2
- package/templates/verify-runtime/CONVENTIONS.md +9 -0
- package/templates/verify-runtime/README.md +37 -0
- package/templates/watchtower/config.json.template +10 -2
- package/templates/watchtower/queue/items/item.json.schema +1 -1
- package/templates/workflows/execute-group-complete.js +10 -1
package/lib/cli.js
CHANGED
|
@@ -3,15 +3,16 @@ const path = require('path');
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
|
-
const { copyTemplates, recordSkip } = require('./copy');
|
|
6
|
+
const { copyTemplates, recordSkip, isProjectOwnedSeed, freezeRetain } = require('./copy');
|
|
7
7
|
const { mergeSettings, healUserSettings, mergeWatchtowerHooks, mergeMuxHooks, mergeBashCompressHooks } = require('./settings-merge');
|
|
8
|
-
const { create: createMetadata, read: readMetadata } = require('./metadata');
|
|
9
|
-
const { setupDb } = require('./db-setup');
|
|
8
|
+
const { create: createMetadata, read: readMetadata, readFrozenModules, setFrozenModules } = require('./metadata');
|
|
9
|
+
const { setupDb, shouldSkipInit, readBundledSchemaVersion } = require('./db-setup');
|
|
10
10
|
const { setupVerifyRuntime } = require('./verify-setup');
|
|
11
11
|
const { setupSiteAuditRuntime } = require('./site-audit-setup');
|
|
12
12
|
const { setupEngagement } = require('./engagement-setup');
|
|
13
13
|
const { setupMux } = require('./mux-setup');
|
|
14
14
|
const { setupEngagementServer } = require('./engagement-server-setup');
|
|
15
|
+
const { refreshWatchtowerRuntime } = require('./watchtower-setup');
|
|
15
16
|
const { reset } = require('./reset');
|
|
16
17
|
|
|
17
18
|
const VERSION = require('../package.json').version;
|
|
@@ -467,6 +468,20 @@ const MODULES = {
|
|
|
467
468
|
'skills/debrief/phases/record-lessons.md',
|
|
468
469
|
'skills/debrief/phases/upstream-feedback.md',
|
|
469
470
|
'skills/debrief/phases/qa-handoff-sweep.md',
|
|
471
|
+
// Reversibility scaffolding (act:6fa60c7d) — standalone classic copies
|
|
472
|
+
// of orient/debrief, kept invokable so the orient/debrief retirement is
|
|
473
|
+
// reversible during the watchtower transition. They do NOT delegate to
|
|
474
|
+
// the live orient/debrief (which will change at retirement); they ARE
|
|
475
|
+
// the old skills under a new name. orient-classic ships SKILL.md only
|
|
476
|
+
// (its phases use skeleton defaults, like live orient); debrief-classic
|
|
477
|
+
// ships its SKILL.md + the same instruction phases as live debrief.
|
|
478
|
+
'skills/orient-classic',
|
|
479
|
+
'skills/debrief-classic',
|
|
480
|
+
'skills/debrief-classic/phases/audit-pattern-capture.md',
|
|
481
|
+
'skills/debrief-classic/phases/methodology-capture.md',
|
|
482
|
+
'skills/debrief-classic/phases/record-lessons.md',
|
|
483
|
+
'skills/debrief-classic/phases/upstream-feedback.md',
|
|
484
|
+
'skills/debrief-classic/phases/qa-handoff-sweep.md',
|
|
470
485
|
'skills/menu',
|
|
471
486
|
],
|
|
472
487
|
},
|
|
@@ -484,7 +499,7 @@ const MODULES = {
|
|
|
484
499
|
mandatory: false,
|
|
485
500
|
default: true,
|
|
486
501
|
lean: false,
|
|
487
|
-
templates: ['scripts/pib-db.mjs', 'scripts/pib-db-lib.mjs', 'scripts/pib-db-mcp-server.mjs', 'scripts/pib-db-schema.sql', 'scripts/work-tracker-server.mjs', 'scripts/work-tracker-ui.html', 'skills/work-tracker', 'skills/spring-clean'],
|
|
502
|
+
templates: ['scripts/pib-db.mjs', 'scripts/pib-db-path.mjs', 'scripts/pib-db-lib.mjs', 'scripts/pib-db-mcp-server.mjs', 'scripts/pib-db-schema.sql', 'scripts/work-tracker-server.mjs', 'scripts/work-tracker-ui.html', 'skills/work-tracker', 'skills/spring-clean'],
|
|
488
503
|
needsDb: true,
|
|
489
504
|
},
|
|
490
505
|
'planning': {
|
|
@@ -493,7 +508,7 @@ const MODULES = {
|
|
|
493
508
|
mandatory: false,
|
|
494
509
|
default: true,
|
|
495
510
|
lean: true,
|
|
496
|
-
templates: ['skills/plan', 'skills/execute', 'skills/execute/phases/post-impl-checklist.md', 'skills/debrief/phases/checklist-feedback.md', 'skills/checklist-discover', 'skills/generate-plan-groups', 'skills/execute-group', 'workflows/execute-group-implement.js', 'workflows/execute-group-complete.js', 'skills/investigate', 'cabinet/checkpoint-protocol.md', 'cabinet/elicitation-methods.md', 'cabinet/qa-dimensions-template.yaml', 'cabinet/checklist-stats-schema.md', 'scripts/qa-dimensions-validator.cjs', 'skills/orient/phases/checklist-status.md', 'skills/audit/phases/checklist-pruning.md'],
|
|
511
|
+
templates: ['skills/plan', 'skills/execute', 'skills/execute/phases/post-impl-checklist.md', 'skills/debrief/phases/checklist-feedback.md', 'skills/checklist-discover', 'skills/generate-plan-groups', 'skills/execute-group', 'workflows/execute-group-implement.js', 'workflows/execute-group-complete.js', 'skills/investigate', 'cabinet/checkpoint-protocol.md', 'cabinet/worktree-invocation-contract.md', 'cabinet/elicitation-methods.md', 'cabinet/qa-dimensions-template.yaml', 'cabinet/checklist-stats-schema.md', 'scripts/qa-dimensions-validator.cjs', 'skills/orient/phases/checklist-status.md', 'skills/audit/phases/checklist-pruning.md'],
|
|
497
512
|
},
|
|
498
513
|
'compliance': {
|
|
499
514
|
name: 'Compliance Stack (rules + enforcement)',
|
|
@@ -501,7 +516,7 @@ const MODULES = {
|
|
|
501
516
|
mandatory: false,
|
|
502
517
|
default: true,
|
|
503
518
|
lean: false,
|
|
504
|
-
templates: ['rules/enforcement-pipeline.md', 'rules/maintainability.md', 'rules/markdown-prose.md', 'skills/unwrap', 'memory/patterns/_pattern-template.md', 'memory/patterns/pattern-intelligence-first.md'],
|
|
519
|
+
templates: ['rules/enforcement-pipeline.md', 'rules/maintainability.md', 'rules/markdown-prose.md', 'rules/verify-before-asserting.md', 'rules/plan-before-bulk-or-irreversible-actions.md', 'rules/acknowledge-when-corrected.md', 'skills/unwrap', 'memory/patterns/_pattern-template.md', 'memory/patterns/pattern-intelligence-first.md'],
|
|
505
520
|
},
|
|
506
521
|
'memory': {
|
|
507
522
|
name: 'Built-In Memory (cc-remember + reader + validator)',
|
|
@@ -517,6 +532,7 @@ const MODULES = {
|
|
|
517
532
|
'skills/cc-remember',
|
|
518
533
|
'skills/memory',
|
|
519
534
|
'rules/memory-capture.md',
|
|
535
|
+
'cabinet/memory-lifecycle-contract.md',
|
|
520
536
|
'scripts/write-memory-file.mjs',
|
|
521
537
|
'scripts/validate-memory.mjs',
|
|
522
538
|
// project-context.cjs ships as a co-located sibling: the two .mjs
|
|
@@ -554,6 +570,7 @@ const MODULES = {
|
|
|
554
570
|
'skills/cabinet-narrative-architect', 'skills/cabinet-interactive-storyteller',
|
|
555
571
|
'skills/cabinet-elegance',
|
|
556
572
|
'scripts/merge-findings.js', 'scripts/load-triage-history.js',
|
|
573
|
+
'scripts/pib-db-path.mjs', // load-triage-history.js imports it (declared cross-module dep; also shipped by work-tracking)
|
|
557
574
|
'scripts/triage-server.mjs', 'scripts/triage-ui.html',
|
|
558
575
|
'scripts/finding-schema.json', 'scripts/resolve-committees.cjs',
|
|
559
576
|
'scripts/review-server.mjs', 'scripts/review-ui.html',
|
|
@@ -632,9 +649,18 @@ const MODULES = {
|
|
|
632
649
|
'cabinet/watchtower-contracts.md',
|
|
633
650
|
'scripts/watchtower-lib.mjs',
|
|
634
651
|
'scripts/watchtower-queue.mjs',
|
|
652
|
+
'scripts/watchtower-routines.mjs',
|
|
653
|
+
'scripts/watchtower-hook-runner.mjs',
|
|
654
|
+
'scripts/watchtower-phase-shim.mjs',
|
|
655
|
+
'scripts/watchtower-sync.mjs',
|
|
656
|
+
'scripts/watchtower-advisories.mjs',
|
|
657
|
+
'cabinet/advisories-state-schema.md',
|
|
635
658
|
'skills/inbox',
|
|
636
659
|
'hooks/watchtower-session-start.sh',
|
|
637
660
|
'scripts/watchtower-build-context.mjs',
|
|
661
|
+
'scripts/watchtower-snapshot.mjs',
|
|
662
|
+
'scripts/watchtower-narrative-corpus.mjs',
|
|
663
|
+
'scripts/watchtower-cross-ring-reader.mjs',
|
|
638
664
|
'scripts/watchtower-ring1.mjs',
|
|
639
665
|
'scripts/watchtower-ring1-runner.sh',
|
|
640
666
|
'skills/watchtower',
|
|
@@ -651,10 +677,15 @@ const MODULES = {
|
|
|
651
677
|
'watchtower/watchtower-ring2-slow.timer',
|
|
652
678
|
'hooks/watchtower-session-end.sh',
|
|
653
679
|
'scripts/watchtower-ring3-close.mjs',
|
|
680
|
+
'scripts/watchtower-ring4.mjs',
|
|
681
|
+
'scripts/watchtower-ring4-runner.sh',
|
|
654
682
|
'scripts/watchtower-status.sh',
|
|
655
683
|
'skills/briefing',
|
|
656
684
|
'skills/threads',
|
|
685
|
+
'skills/catch-up',
|
|
657
686
|
'skills/qa-handoff',
|
|
687
|
+
'skills/qa-drain',
|
|
688
|
+
'skills/close',
|
|
658
689
|
],
|
|
659
690
|
},
|
|
660
691
|
mux: {
|
|
@@ -703,6 +734,94 @@ function walkDir(dir, base) {
|
|
|
703
734
|
return results;
|
|
704
735
|
}
|
|
705
736
|
|
|
737
|
+
// Map a module template path to its install-time manifest key (the path
|
|
738
|
+
// relative to the project root). Top-level so both the install copy loop and
|
|
739
|
+
// computeRemovals derive the key the same way — single source of truth.
|
|
740
|
+
function manifestPath(tmpl) {
|
|
741
|
+
if (tmpl.startsWith('scripts/')) return tmpl;
|
|
742
|
+
return '.claude/' + tmpl;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Expand a declared frozen-module list by its `requires` closure (act:c1013967).
|
|
747
|
+
* Freezing a module must also pin the modules it depends on, or a frozen
|
|
748
|
+
* caller can be left bound to a refreshed callee contract (the cross-runtime
|
|
749
|
+
* version-skew failure: green in the dev tree, broken on a fresh install).
|
|
750
|
+
* E.g. freezing `engagement` (requires work-tracking) also pins work-tracking.
|
|
751
|
+
* Returns a Set of module keys. Unknown keys are ignored (validated elsewhere).
|
|
752
|
+
*/
|
|
753
|
+
function expandFrozenClosure(frozenKeys, modules = MODULES) {
|
|
754
|
+
const closure = new Set();
|
|
755
|
+
const queue = [...(frozenKeys || [])];
|
|
756
|
+
while (queue.length) {
|
|
757
|
+
const key = queue.shift();
|
|
758
|
+
if (closure.has(key)) continue;
|
|
759
|
+
closure.add(key);
|
|
760
|
+
const mod = modules[key];
|
|
761
|
+
for (const req of (mod && mod.requires) || []) {
|
|
762
|
+
if (!closure.has(req)) queue.push(req);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return closure;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Classify which manifest entries should be removed because they are no longer
|
|
770
|
+
* shipped upstream — the pure core of the cleanup/reconcile loop, extracted so
|
|
771
|
+
* it is unit-testable in isolation (the inline loop never was). Returns
|
|
772
|
+
* `{ toRemove, kept }`: `toRemove` is the list of project-relative paths whose
|
|
773
|
+
* files exist on disk and are safe to delete; `kept` carries `{path, reason}`
|
|
774
|
+
* for files deliberately spared, which the caller logs.
|
|
775
|
+
*
|
|
776
|
+
* The four safeguards from the original loop are preserved exactly: S1 only
|
|
777
|
+
* delete known-template paths; S2 only from selected modules; project-owned
|
|
778
|
+
* seeds and non-CC project skills are never deleted. A FROZEN file needs no
|
|
779
|
+
* special case here — it was RETAINED in `allManifest` (at its on-disk hash),
|
|
780
|
+
* so it never reaches the `!allManifest[oldPath]` test; a frozen file removed
|
|
781
|
+
* upstream is kept by S1 ("non-template").
|
|
782
|
+
*/
|
|
783
|
+
function computeRemovals({ existingManifest, allManifest, selectedModules, templateRoot, projectDir, modules = MODULES }) {
|
|
784
|
+
const collectPaths = (modKeys) => {
|
|
785
|
+
const set = new Set();
|
|
786
|
+
for (const modKey of modKeys) {
|
|
787
|
+
const mod = modules[modKey];
|
|
788
|
+
for (const tmpl of (mod && mod.templates) || []) {
|
|
789
|
+
const srcPath = path.join(templateRoot, tmpl);
|
|
790
|
+
if (fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory()) {
|
|
791
|
+
for (const rel of walkDir(srcPath)) set.add(manifestPath(tmpl) + '/' + rel);
|
|
792
|
+
} else if (fs.existsSync(srcPath)) {
|
|
793
|
+
set.add(manifestPath(tmpl));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return set;
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
const allTemplatePaths = collectPaths(Object.keys(modules));
|
|
801
|
+
const selectedTemplatePaths = collectPaths(selectedModules);
|
|
802
|
+
|
|
803
|
+
const ccSkillNames = new Set();
|
|
804
|
+
for (const mod of Object.values(modules)) {
|
|
805
|
+
for (const tmpl of mod.templates || []) {
|
|
806
|
+
if (tmpl.startsWith('skills/')) ccSkillNames.add(tmpl.split('/')[1]);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const toRemove = [];
|
|
811
|
+
const kept = [];
|
|
812
|
+
for (const oldPath of Object.keys(existingManifest)) {
|
|
813
|
+
if (allManifest[oldPath]) continue;
|
|
814
|
+
if (/\/phases\//.test(oldPath)) continue; // user-customizable
|
|
815
|
+
if (isProjectOwnedSeed(oldPath)) { kept.push({ path: oldPath, reason: 'project-owned seed' }); continue; }
|
|
816
|
+
if (!allTemplatePaths.has(oldPath)) { kept.push({ path: oldPath, reason: 'non-template file' }); continue; }
|
|
817
|
+
if (!selectedTemplatePaths.has(oldPath)) continue; // deselected module — leave it
|
|
818
|
+
const skillDirMatch = oldPath.match(/^\.claude\/skills\/([^/]+)\//);
|
|
819
|
+
if (skillDirMatch && !ccSkillNames.has(skillDirMatch[1])) { kept.push({ path: oldPath, reason: 'project skill' }); continue; }
|
|
820
|
+
if (fs.existsSync(path.join(projectDir, oldPath))) toRemove.push(oldPath);
|
|
821
|
+
}
|
|
822
|
+
return { toRemove, kept };
|
|
823
|
+
}
|
|
824
|
+
|
|
706
825
|
// Signals that a directory contains a real project (not just empty)
|
|
707
826
|
const PROJECT_SIGNALS = [
|
|
708
827
|
'package.json', 'Cargo.toml', 'requirements.txt', 'pyproject.toml',
|
|
@@ -725,6 +844,13 @@ function detectProjectState(dir) {
|
|
|
725
844
|
return 'empty';
|
|
726
845
|
}
|
|
727
846
|
|
|
847
|
+
// A `--freeze`/`--unfreeze` value token vs the positional target dir. Module
|
|
848
|
+
// keys are simple kebab-case identifiers; a flag or any path-like token
|
|
849
|
+
// (separator, or . / ..) is the target directory, never a module list.
|
|
850
|
+
function looksLikeModuleArg(s) {
|
|
851
|
+
return Boolean(s) && !s.startsWith('-') && !s.includes('/') && !s.includes('\\') && s !== '.' && s !== '..';
|
|
852
|
+
}
|
|
853
|
+
|
|
728
854
|
function parseArgs(argv) {
|
|
729
855
|
const args = argv.slice(2);
|
|
730
856
|
const flags = {
|
|
@@ -747,11 +873,32 @@ function parseArgs(argv) {
|
|
|
747
873
|
else if (arg === '--help' || arg === '-h') flags.help = true;
|
|
748
874
|
else if (arg === '--reset') flags.reset = true;
|
|
749
875
|
else if (arg === '--force') flags.force = true;
|
|
876
|
+
else if (arg === '--reindex') flags.reindex = true;
|
|
750
877
|
else if (arg === '--migrate-memory') flags.migrateMemory = true;
|
|
751
878
|
else if (arg === '--unmigrate-memory') flags.unmigrateMemory = true;
|
|
879
|
+
else if (arg === '--list-frozen') flags.listFrozen = true;
|
|
752
880
|
else if (arg === '--modules' && i + 1 < args.length) {
|
|
753
881
|
flags.modules = args[++i].split(',').map(s => s.trim()).filter(Boolean);
|
|
754
882
|
}
|
|
883
|
+
// --freeze <mods> / --unfreeze [mods]: edit .ccrc.json frozenModules and
|
|
884
|
+
// exit. Consume the next token as the module list ONLY when it looks like
|
|
885
|
+
// one — a flag, or a path-like token (contains a separator, or is . / ..),
|
|
886
|
+
// is the target directory, not a value. Module keys are simple kebab-case
|
|
887
|
+
// identifiers and never contain a path separator, so this can't swallow a
|
|
888
|
+
// real module while it does stop `--unfreeze /path/to/project` from being
|
|
889
|
+
// misread as a module (which would silently fail to clear-all). An empty
|
|
890
|
+
// --freeze is an error (dispatch warns, never a silent no-op); an empty
|
|
891
|
+
// --unfreeze clears all.
|
|
892
|
+
else if (arg === '--freeze') {
|
|
893
|
+
flags.freeze = looksLikeModuleArg(args[i + 1])
|
|
894
|
+
? args[++i].split(',').map(s => s.trim()).filter(Boolean)
|
|
895
|
+
: [];
|
|
896
|
+
}
|
|
897
|
+
else if (arg === '--unfreeze') {
|
|
898
|
+
flags.unfreeze = looksLikeModuleArg(args[i + 1])
|
|
899
|
+
? args[++i].split(',').map(s => s.trim()).filter(Boolean)
|
|
900
|
+
: []; // clear all
|
|
901
|
+
}
|
|
755
902
|
else if (arg === '--frontier-model' && i + 1 < args.length) {
|
|
756
903
|
// Empty/whitespace values are treated as absent: '' is a substring of
|
|
757
904
|
// every model ID, which would match everything and leave the watchdog
|
|
@@ -777,6 +924,16 @@ function printHelp() {
|
|
|
777
924
|
--modules <keys> Comma-separated module keys to install (e.g., 'verify').
|
|
778
925
|
Mandatory modules are always included.
|
|
779
926
|
--dry-run Show what would be copied without writing
|
|
927
|
+
--reindex Regenerate .claude/skills/_index.json in place (picks up
|
|
928
|
+
directives-project.yaml mandate/routine edits without a full
|
|
929
|
+
reinstall) and exit
|
|
930
|
+
--freeze <keys> Pin module(s) (comma-separated) in .ccrc.json so the
|
|
931
|
+
NEXT reinstall holds their CC-owned files at the current
|
|
932
|
+
version (e.g. --freeze engagement while a client engagement
|
|
933
|
+
is live). Also pins the requires-closure. Edits config, exits.
|
|
934
|
+
--unfreeze [keys] Clear the freeze for the given module(s), or all if no
|
|
935
|
+
keys. Re-run the installer to refresh the unfrozen module(s).
|
|
936
|
+
--list-frozen Show which modules are frozen (+ their closure) and exit.
|
|
780
937
|
--reset Remove Claude Cabinet files (uses manifest for safety)
|
|
781
938
|
--force With --reset: remove even customized files;
|
|
782
939
|
with --migrate-memory: override already-migrated guard
|
|
@@ -818,6 +975,80 @@ async function run() {
|
|
|
818
975
|
return;
|
|
819
976
|
}
|
|
820
977
|
|
|
978
|
+
// Standalone skill-index regen (act:98d41fa2). Advisor standing-mandates and
|
|
979
|
+
// routine declarations are read from .claude/skills/_index.json, which is
|
|
980
|
+
// otherwise regenerated only by a full install — coupling every overlay
|
|
981
|
+
// change (e.g. adding a session-close mandate to directives-project.yaml) to
|
|
982
|
+
// an upgrade cycle. This regenerates the index in place so a change is
|
|
983
|
+
// verifiable immediately, no reinstall needed.
|
|
984
|
+
if (flags.reindex) {
|
|
985
|
+
const projectDir = path.resolve(flags.targetDir);
|
|
986
|
+
if (!fs.existsSync(path.join(projectDir, '.claude', 'skills'))) {
|
|
987
|
+
console.log(` No .claude/skills/ in ${projectDir} — nothing to reindex.`);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
const n = generateSkillIndex(projectDir);
|
|
991
|
+
console.log(` 📇 Reindexed ${n} skill${n === 1 ? '' : 's'} in .claude/skills/_index.json`);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Standalone freeze management (act:c1013967) — edit .ccrc.json frozenModules
|
|
996
|
+
// and exit, no install. Freeze pins a module's CC-owned files across a
|
|
997
|
+
// reinstall (the "reinstall maginnis without disturbing Ed's engagement" fix);
|
|
998
|
+
// the install body reads frozenModules + expands the requires closure.
|
|
999
|
+
if (flags.listFrozen || flags.freeze || flags.unfreeze) {
|
|
1000
|
+
const projectDir = path.resolve(flags.targetDir);
|
|
1001
|
+
const current = readFrozenModules(projectDir);
|
|
1002
|
+
|
|
1003
|
+
const describe = (mods) => {
|
|
1004
|
+
if (mods.length === 0) { console.log(' No modules are frozen.'); return; }
|
|
1005
|
+
const closure = expandFrozenClosure(mods, MODULES);
|
|
1006
|
+
const pulledIn = [...closure].filter(k => !mods.includes(k));
|
|
1007
|
+
console.log(` ❄ Frozen modules: ${mods.join(', ')}`);
|
|
1008
|
+
if (pulledIn.length) console.log(` + required closure (also pinned): ${pulledIn.join(', ')}`);
|
|
1009
|
+
console.log(' A reinstall holds these modules’ CC-owned files at the current version.');
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
if (flags.listFrozen) {
|
|
1013
|
+
describe(current);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (flags.freeze) {
|
|
1018
|
+
if (flags.freeze.length === 0) {
|
|
1019
|
+
console.log(' ✗ --freeze needs at least one module key, e.g. --freeze engagement');
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const known = Object.keys(MODULES);
|
|
1023
|
+
const unknown = flags.freeze.filter(m => !known.includes(m));
|
|
1024
|
+
if (unknown.length) console.log(` ⚠ Unknown module key(s) ignored: ${unknown.join(', ')} (known: ${known.join(', ')})`);
|
|
1025
|
+
const valid = flags.freeze.filter(m => known.includes(m));
|
|
1026
|
+
const next = [...new Set([...current, ...valid])];
|
|
1027
|
+
if (flags.dryRun) {
|
|
1028
|
+
console.log(` [dry run] Would set frozenModules to: ${next.join(', ') || '(none)'}`);
|
|
1029
|
+
} else {
|
|
1030
|
+
setFrozenModules(projectDir, next);
|
|
1031
|
+
console.log(' ✓ Updated .ccrc.json frozenModules.');
|
|
1032
|
+
}
|
|
1033
|
+
describe(next);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (flags.unfreeze) {
|
|
1038
|
+
const next = flags.unfreeze.length === 0
|
|
1039
|
+
? [] // --unfreeze → clear all
|
|
1040
|
+
: current.filter(m => !flags.unfreeze.includes(m));
|
|
1041
|
+
if (flags.dryRun) {
|
|
1042
|
+
console.log(` [dry run] Would set frozenModules to: ${next.join(', ') || '(none)'}`);
|
|
1043
|
+
} else {
|
|
1044
|
+
setFrozenModules(projectDir, next);
|
|
1045
|
+
console.log(' ✓ Updated .ccrc.json frozenModules. Re-run the installer to refresh the unfrozen module(s).');
|
|
1046
|
+
}
|
|
1047
|
+
describe(next);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
821
1052
|
if (flags.migrateMemory) {
|
|
822
1053
|
const { migrateMemoryCmd } = require('./migrate-memory-cmd');
|
|
823
1054
|
const projectDir = path.resolve(flags.targetDir);
|
|
@@ -1146,18 +1377,24 @@ async function run() {
|
|
|
1146
1377
|
return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
1147
1378
|
}
|
|
1148
1379
|
|
|
1149
|
-
//
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1380
|
+
// Frozen modules (act:c1013967): the operator pinned these in .ccrc.json so
|
|
1381
|
+
// a reinstall holds their CC-owned files at the current version. Expand by
|
|
1382
|
+
// the `requires` closure (freezing engagement also pins work-tracking, so a
|
|
1383
|
+
// frozen caller is never left bound to a refreshed callee), then intersect
|
|
1384
|
+
// with what is actually installed. `effectiveFrozen` gates ALL freeze
|
|
1385
|
+
// behavior — never the raw declared list — so a typo'd / uninstalled / dead
|
|
1386
|
+
// key can't silently pin global state or claim protection it isn't giving.
|
|
1387
|
+
const declaredFrozen = readFrozenModules(projectDir);
|
|
1388
|
+
const effectiveFrozen = new Set(
|
|
1389
|
+
[...expandFrozenClosure(declaredFrozen, MODULES)].filter(
|
|
1390
|
+
(k) => MODULES[k] && selectedModules.includes(k)
|
|
1391
|
+
)
|
|
1392
|
+
);
|
|
1393
|
+
const frozenCounts = {}; // modKey -> # files held this install
|
|
1158
1394
|
|
|
1159
1395
|
for (const modKey of selectedModules) {
|
|
1160
1396
|
const mod = MODULES[modKey];
|
|
1397
|
+
const frozen = effectiveFrozen.has(modKey);
|
|
1161
1398
|
for (const tmpl of mod.templates) {
|
|
1162
1399
|
const srcPath = path.join(templateRoot, tmpl);
|
|
1163
1400
|
if (!fs.existsSync(srcPath)) {
|
|
@@ -1206,7 +1443,9 @@ async function run() {
|
|
|
1206
1443
|
projectRoot: projectDir,
|
|
1207
1444
|
existingManifest,
|
|
1208
1445
|
instructionPhases: instrPhases,
|
|
1446
|
+
frozen,
|
|
1209
1447
|
});
|
|
1448
|
+
if (results.frozen.length) frozenCounts[modKey] = (frozenCounts[modKey] || 0) + results.frozen.length;
|
|
1210
1449
|
totalCopied += results.copied.length;
|
|
1211
1450
|
totalSkipped += results.skipped.length;
|
|
1212
1451
|
totalOverwritten += results.overwritten.length;
|
|
@@ -1251,6 +1490,22 @@ async function run() {
|
|
|
1251
1490
|
continue;
|
|
1252
1491
|
}
|
|
1253
1492
|
|
|
1493
|
+
// Frozen module (act:c1013967): held instead of overwritten. Mirrors
|
|
1494
|
+
// copy.js's directory path via the shared freezeRetain rule — retain
|
|
1495
|
+
// at the on-disk hash only if already CC-owned; a project-owned file
|
|
1496
|
+
// falls through to the normal skip-and-omit path below.
|
|
1497
|
+
if (frozen) {
|
|
1498
|
+
const decision = freezeRetain({ existing: existingContent, isCcOwned: Boolean(existingManifest[mPath]) });
|
|
1499
|
+
if (decision.manifestHash) {
|
|
1500
|
+
totalSkipped++;
|
|
1501
|
+
frozenCounts[modKey] = (frozenCounts[modKey] || 0) + 1;
|
|
1502
|
+
allManifest[mPath] = decision.manifestHash;
|
|
1503
|
+
console.log(` ❄ Frozen (held): ${path.relative(projectDir, destPath)}`);
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
// decision.omit → project-owned; fall through.
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1254
1509
|
if (flags.yes || dirState === 'existing-install') {
|
|
1255
1510
|
// If file is in the old manifest, it's upstream-managed — overwrite.
|
|
1256
1511
|
// If not, it's project-created — skip.
|
|
@@ -1293,6 +1548,14 @@ async function run() {
|
|
|
1293
1548
|
}
|
|
1294
1549
|
}
|
|
1295
1550
|
} else {
|
|
1551
|
+
// New file in a frozen module: hold it back (don't create) so the
|
|
1552
|
+
// module stays internally consistent at its pinned version. Report
|
|
1553
|
+
// only — never a manifest entry (no on-disk content to hash).
|
|
1554
|
+
if (frozen) {
|
|
1555
|
+
frozenCounts[modKey] = (frozenCounts[modKey] || 0) + 1;
|
|
1556
|
+
console.log(` ❄ Frozen (held new): ${path.relative(projectDir, destPath)}`);
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1296
1559
|
if (!flags.dryRun) fs.copyFileSync(srcPath, destPath);
|
|
1297
1560
|
totalCopied++;
|
|
1298
1561
|
allManifest[mPath] = incomingHash;
|
|
@@ -1303,6 +1566,28 @@ async function run() {
|
|
|
1303
1566
|
|
|
1304
1567
|
console.log(` 📁 Files: ${totalCopied} copied, ${totalOverwritten} overwritten, ${totalSkipped} unchanged`);
|
|
1305
1568
|
|
|
1569
|
+
// --- Frozen-module report (act:c1013967) ---
|
|
1570
|
+
// Plain-English, per-module: surface WHICH modules were held and how many
|
|
1571
|
+
// files, so a gitignored freeze can never quietly change install behavior.
|
|
1572
|
+
// Note in plain words when the requires-closure pinned a module the operator
|
|
1573
|
+
// didn't name directly.
|
|
1574
|
+
if (effectiveFrozen.size > 0) {
|
|
1575
|
+
const declaredSet = new Set(declaredFrozen);
|
|
1576
|
+
for (const modKey of effectiveFrozen) {
|
|
1577
|
+
const n = frozenCounts[modKey] || 0;
|
|
1578
|
+
let reason = '';
|
|
1579
|
+
if (!declaredSet.has(modKey)) {
|
|
1580
|
+
const namedBy = [...declaredSet].find(d => expandFrozenClosure([d], MODULES).has(modKey) && d !== modKey);
|
|
1581
|
+
if (namedBy) reason = ` (pinned because ${namedBy} requires it)`;
|
|
1582
|
+
}
|
|
1583
|
+
if (n > 0) {
|
|
1584
|
+
console.log(` ❄ Frozen: ${n} file${n === 1 ? '' : 's'} in module '${modKey}'${reason} held at current version — unfreeze to update.`);
|
|
1585
|
+
} else {
|
|
1586
|
+
console.log(` ❄ Frozen: module '${modKey}'${reason} — no CC-owned files differed (nothing to hold this run).`);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1306
1591
|
// --- Make hook scripts executable ---
|
|
1307
1592
|
const hooksDir = path.join(claudeDir, 'hooks');
|
|
1308
1593
|
if (!flags.dryRun && fs.existsSync(hooksDir)) {
|
|
@@ -1373,10 +1658,34 @@ async function run() {
|
|
|
1373
1658
|
}
|
|
1374
1659
|
|
|
1375
1660
|
// --- Set up database ---
|
|
1661
|
+
// dbSchemaVersion is recorded into .ccrc.json so a future reinstall can
|
|
1662
|
+
// tell, WITHOUT loading better-sqlite3, that no migration is pending and
|
|
1663
|
+
// skip the init step entirely (lib/db-setup.js shouldSkipInit).
|
|
1664
|
+
let dbSchemaVersion;
|
|
1376
1665
|
if (includeDb && selectedModules.includes('work-tracking') && !flags.dryRun) {
|
|
1377
1666
|
try {
|
|
1378
|
-
|
|
1379
|
-
|
|
1667
|
+
// Freeze insurance (act:c1013967): when a module is frozen and a schema
|
|
1668
|
+
// migration is actually pending, back up pib.db before db-setup runs so
|
|
1669
|
+
// the operator has true one-file rollback for the store holding live
|
|
1670
|
+
// engagement data. Reuses db-setup's own skip decision (no logic
|
|
1671
|
+
// duplication): if work-tracking is itself frozen, its pib-db-lib.mjs
|
|
1672
|
+
// stayed old → bundled == recorded → no migration → no backup. The
|
|
1673
|
+
// existsSync guard means a fresh install (no pib.db yet) still creates
|
|
1674
|
+
// the db normally; there's just nothing to back up.
|
|
1675
|
+
if (effectiveFrozen.size > 0) {
|
|
1676
|
+
const dbPath = path.join(projectDir, 'pib.db');
|
|
1677
|
+
const recordedVersion = (readMetadata(projectDir) || {}).dbSchemaVersion;
|
|
1678
|
+
const bundledVersion = readBundledSchemaVersion(path.join(projectDir, 'scripts'));
|
|
1679
|
+
const willMigrate = !shouldSkipInit({ dbExists: fs.existsSync(dbPath), recordedVersion, bundledVersion });
|
|
1680
|
+
if (willMigrate && fs.existsSync(dbPath)) {
|
|
1681
|
+
const backupPath = `${dbPath}.cc-bak-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
1682
|
+
fs.copyFileSync(dbPath, backupPath);
|
|
1683
|
+
console.log(` ❄ Backed up pib.db → ${path.basename(backupPath)} before migration (freeze active).`);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
const dbResult = setupDb(projectDir);
|
|
1687
|
+
for (const r of dbResult.results) console.log(` 🗄️ ${r}`);
|
|
1688
|
+
if (dbResult.schemaVersion != null) dbSchemaVersion = dbResult.schemaVersion;
|
|
1380
1689
|
} catch (err) {
|
|
1381
1690
|
console.log(` ⚠ Database setup failed: ${err.message}`);
|
|
1382
1691
|
console.log(' You can set it up later: node scripts/pib-db.mjs init');
|
|
@@ -1411,6 +1720,27 @@ async function run() {
|
|
|
1411
1720
|
}
|
|
1412
1721
|
}
|
|
1413
1722
|
|
|
1723
|
+
// --- Refresh the GLOBAL watchtower runtime (content-aware) ---
|
|
1724
|
+
// The watchtower module copies its files into the PROJECT, but the global
|
|
1725
|
+
// runtime at ~/.claude-cabinet/watchtower/ is set up only by the one-time
|
|
1726
|
+
// `/watchtower install` SKILL.md step. Without this, a reinstall left the
|
|
1727
|
+
// global runtime scripts/docs/hooks stale and never delivered newly shipped
|
|
1728
|
+
// ones. refreshWatchtowerRuntime is REFRESH-ONLY: it no-ops (status
|
|
1729
|
+
// 'absent', zero writes) when no runtime exists yet, so it's safe to call
|
|
1730
|
+
// unconditionally whenever the module is selected.
|
|
1731
|
+
if (selectedModules.includes('watchtower')) {
|
|
1732
|
+
try {
|
|
1733
|
+
const result = refreshWatchtowerRuntime({ dryRun: !!flags.dryRun });
|
|
1734
|
+
if (result.status !== 'absent') {
|
|
1735
|
+
console.log('');
|
|
1736
|
+
for (const r of result.results || []) console.log(` 📋 ${r}`);
|
|
1737
|
+
}
|
|
1738
|
+
} catch (err) {
|
|
1739
|
+
console.log(` ⚠ watchtower runtime refresh failed: ${err.message}`);
|
|
1740
|
+
console.log(' Re-run the installer to retry.');
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1414
1744
|
// --- Manifest key migration (act:d1f16bee) ---
|
|
1415
1745
|
// When CC renames directories (e.g., perspectives/ → cabinet-*/), old manifest
|
|
1416
1746
|
// keys no longer match new template paths. Migrate keys BEFORE cleanup so the
|
|
@@ -1461,78 +1791,26 @@ async function run() {
|
|
|
1461
1791
|
// S3: Itemize — list all deletions and confirm before proceeding
|
|
1462
1792
|
// S4: Backup — copy files to .cc-backup/<timestamp>/ before deletion
|
|
1463
1793
|
if (Object.keys(existingManifest).length > 0) {
|
|
1464
|
-
//
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
const srcPath = path.join(templateRoot, tmpl);
|
|
1485
|
-
if (fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory()) {
|
|
1486
|
-
for (const rel of walkDir(srcPath)) {
|
|
1487
|
-
selectedTemplatePaths.add(manifestPath(tmpl) + '/' + rel);
|
|
1488
|
-
}
|
|
1489
|
-
} else if (fs.existsSync(srcPath)) {
|
|
1490
|
-
selectedTemplatePaths.add(manifestPath(tmpl));
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
// Build CC template skill name set for project-skill guard (act:4ac281ba)
|
|
1496
|
-
const ccSkillNames = new Set();
|
|
1497
|
-
for (const mod of Object.values(MODULES)) {
|
|
1498
|
-
for (const tmpl of mod.templates || []) {
|
|
1499
|
-
if (tmpl.startsWith('skills/')) {
|
|
1500
|
-
const skillName = tmpl.split('/')[1];
|
|
1501
|
-
ccSkillNames.add(skillName);
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
// Collect files that would be removed
|
|
1507
|
-
const toRemove = [];
|
|
1508
|
-
for (const oldPath of Object.keys(existingManifest)) {
|
|
1509
|
-
if (!allManifest[oldPath]) {
|
|
1510
|
-
// Skip phase files — they may be user-customized
|
|
1511
|
-
if (/\/phases\//.test(oldPath)) continue;
|
|
1512
|
-
|
|
1513
|
-
// S1: Only delete if the path maps to a known CC template
|
|
1514
|
-
if (!allTemplatePaths.has(oldPath)) {
|
|
1515
|
-
console.log(` Keeping non-template file: ${oldPath}`);
|
|
1516
|
-
continue;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
// S2: Only delete from selected modules (don't purge deselected modules)
|
|
1520
|
-
if (!selectedTemplatePaths.has(oldPath)) {
|
|
1521
|
-
continue;
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
// Project-skill guard: never delete skills CC didn't create
|
|
1525
|
-
const skillDirMatch = oldPath.match(/^\.claude\/skills\/([^/]+)\//);
|
|
1526
|
-
if (skillDirMatch && !ccSkillNames.has(skillDirMatch[1])) {
|
|
1527
|
-
console.log(` Keeping project skill: ${oldPath}`);
|
|
1528
|
-
continue;
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
const fullPath = path.join(projectDir, oldPath);
|
|
1532
|
-
if (fs.existsSync(fullPath)) {
|
|
1533
|
-
toRemove.push(oldPath);
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1794
|
+
// Classification is the pure `computeRemovals` (top-level, unit-tested): S1
|
|
1795
|
+
// only known-template paths, S2 only selected modules, plus the
|
|
1796
|
+
// project-owned-seed and project-skill guards. A FROZEN file needs no case
|
|
1797
|
+
// here — retain-in-manifest (the freeze copy path) kept it in allManifest,
|
|
1798
|
+
// so it never reaches the removal test; a frozen file removed upstream is
|
|
1799
|
+
// kept by S1 ("non-template"). The S3 itemize/confirm + S4 backup/delete
|
|
1800
|
+
// stay inline below.
|
|
1801
|
+
const { toRemove, kept } = computeRemovals({
|
|
1802
|
+
existingManifest,
|
|
1803
|
+
allManifest,
|
|
1804
|
+
selectedModules,
|
|
1805
|
+
templateRoot,
|
|
1806
|
+
projectDir,
|
|
1807
|
+
});
|
|
1808
|
+
for (const k of kept) {
|
|
1809
|
+
// 'deselected module' files are silently left (matching the prior loop's
|
|
1810
|
+
// bare `continue` for that S2 case); the rest log their keep reason.
|
|
1811
|
+
if (k.reason === 'project-owned seed') console.log(` Keeping project-owned seed: ${k.path}`);
|
|
1812
|
+
else if (k.reason === 'non-template file') console.log(` Keeping non-template file: ${k.path}`);
|
|
1813
|
+
else if (k.reason === 'project skill') console.log(` Keeping project skill: ${k.path}`);
|
|
1536
1814
|
}
|
|
1537
1815
|
|
|
1538
1816
|
// S3: Itemize and confirm before deleting
|
|
@@ -1603,6 +1881,7 @@ async function run() {
|
|
|
1603
1881
|
skipped: skippedModules,
|
|
1604
1882
|
version: VERSION,
|
|
1605
1883
|
manifest: allManifest,
|
|
1884
|
+
dbSchemaVersion,
|
|
1606
1885
|
});
|
|
1607
1886
|
console.log(' 📝 Created .ccrc.json');
|
|
1608
1887
|
}
|
|
@@ -1658,9 +1937,13 @@ async function run() {
|
|
|
1658
1937
|
} else {
|
|
1659
1938
|
console.log(' 📋 Registered in project registry');
|
|
1660
1939
|
}
|
|
1661
|
-
// Self-announcing: print the effective designation on every run
|
|
1940
|
+
// Self-announcing: print the effective designation on every run, and
|
|
1941
|
+
// the inert-state notice when none is set — a never-configured
|
|
1942
|
+
// watchdog must not read the same as a passing one (act:77f7b7b2).
|
|
1662
1943
|
if (registry.frontierModel) {
|
|
1663
1944
|
console.log(` 🛰 Frontier model: ${registry.frontierModel} (visibility watchdog — /orient + SessionStart warn on mismatch; nothing is pinned)`);
|
|
1945
|
+
} else {
|
|
1946
|
+
console.log(' 🛰 Frontier model: none designated — watchdog inert. Set with: npx create-claude-cabinet --frontier-model <value>');
|
|
1664
1947
|
}
|
|
1665
1948
|
} catch (err) {
|
|
1666
1949
|
// Non-fatal — registry is nice-to-have
|
|
@@ -1684,4 +1967,4 @@ async function run() {
|
|
|
1684
1967
|
console.log('');
|
|
1685
1968
|
}
|
|
1686
1969
|
|
|
1687
|
-
module.exports = { run, MODULES, generateAgentWrappers };
|
|
1970
|
+
module.exports = { run, MODULES, generateAgentWrappers, generateSkillIndex, expandFrozenClosure, computeRemovals, manifestPath };
|