create-claude-cabinet 0.46.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 +2 -6
- package/lib/cli.js +348 -91
- package/lib/copy.js +108 -6
- package/lib/db-setup.js +122 -17
- package/lib/metadata.js +51 -2
- package/lib/settings-merge.js +52 -10
- package/lib/watchtower-setup.js +22 -2
- package/package.json +1 -1
- package/templates/cabinet/advisories-state-schema.md +1 -1
- package/templates/cabinet/checklist-stats-schema.md +15 -1
- package/templates/cabinet/memory-lifecycle-contract.md +135 -0
- package/templates/cabinet/pib-db-access.md +13 -0
- package/templates/cabinet/watchtower-contracts.md +414 -0
- 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-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/bin/mux +8 -1
- 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__/ahead-check-origin.test.mjs +355 -0
- package/templates/scripts/__tests__/batch-disposition.test.mjs +98 -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__/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-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__/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__/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-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 +7 -3
- package/templates/scripts/watchtower-build-context.mjs +192 -8
- 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 +273 -4
- 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 +405 -45
- package/templates/scripts/watchtower-ring2.mjs +710 -68
- package/templates/scripts/watchtower-ring3-close.mjs +988 -147
- 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-sync.mjs +393 -0
- package/templates/skills/briefing/SKILL.md +281 -41
- 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 +53 -16
- 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-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 +84 -28
- package/templates/skills/memory/SKILL.md +22 -6
- package/templates/skills/orient/SKILL.md +71 -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/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/threads/SKILL.md +4 -0
- package/templates/skills/validate/phases/validators.md +41 -0
- package/templates/skills/watchtower/SKILL.md +160 -8
- 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/execute-group-complete.js +10 -1
package/README.md
CHANGED
|
@@ -129,13 +129,9 @@ you already use GitHub Issues, Linear, or something else.
|
|
|
129
129
|
Claude Code has built-in file memory, but no guardrails around it.
|
|
130
130
|
The memory module adds structure:
|
|
131
131
|
|
|
132
|
-
- **`/cc-remember`** — write a new memory with automatic indexing.
|
|
133
|
-
Every memory gets its own file and an entry in `MEMORY.md` so
|
|
134
|
-
`/orient` can find it next session.
|
|
132
|
+
- **`/cc-remember`** — write a new memory with automatic indexing. Every memory gets its own file and is made reachable from `MEMORY.md` — by a direct entry or a region pointer — so `/orient` can find it next session.
|
|
135
133
|
- **`/memory`** — browse and search what Claude remembers.
|
|
136
|
-
- **Validation** — `validate-memory.mjs` checks that the index stays
|
|
137
|
-
within Claude Code's session-start budget and that every file is
|
|
138
|
-
indexed. A PostToolUse hook flags unindexed writes in real time.
|
|
134
|
+
- **Validation** — `validate-memory.mjs` checks that the index stays within Claude Code's session-start budget and that every memory file is reachable from `MEMORY.md`. A PostToolUse hook flags unreachable writes in real time.
|
|
139
135
|
|
|
140
136
|
### Compliance Stack (full install)
|
|
141
137
|
|
package/lib/cli.js
CHANGED
|
@@ -3,10 +3,10 @@ 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');
|
|
@@ -468,6 +468,20 @@ const MODULES = {
|
|
|
468
468
|
'skills/debrief/phases/record-lessons.md',
|
|
469
469
|
'skills/debrief/phases/upstream-feedback.md',
|
|
470
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',
|
|
471
485
|
'skills/menu',
|
|
472
486
|
],
|
|
473
487
|
},
|
|
@@ -485,7 +499,7 @@ const MODULES = {
|
|
|
485
499
|
mandatory: false,
|
|
486
500
|
default: true,
|
|
487
501
|
lean: false,
|
|
488
|
-
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'],
|
|
489
503
|
needsDb: true,
|
|
490
504
|
},
|
|
491
505
|
'planning': {
|
|
@@ -494,7 +508,7 @@ const MODULES = {
|
|
|
494
508
|
mandatory: false,
|
|
495
509
|
default: true,
|
|
496
510
|
lean: true,
|
|
497
|
-
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'],
|
|
498
512
|
},
|
|
499
513
|
'compliance': {
|
|
500
514
|
name: 'Compliance Stack (rules + enforcement)',
|
|
@@ -502,7 +516,7 @@ const MODULES = {
|
|
|
502
516
|
mandatory: false,
|
|
503
517
|
default: true,
|
|
504
518
|
lean: false,
|
|
505
|
-
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'],
|
|
506
520
|
},
|
|
507
521
|
'memory': {
|
|
508
522
|
name: 'Built-In Memory (cc-remember + reader + validator)',
|
|
@@ -518,6 +532,7 @@ const MODULES = {
|
|
|
518
532
|
'skills/cc-remember',
|
|
519
533
|
'skills/memory',
|
|
520
534
|
'rules/memory-capture.md',
|
|
535
|
+
'cabinet/memory-lifecycle-contract.md',
|
|
521
536
|
'scripts/write-memory-file.mjs',
|
|
522
537
|
'scripts/validate-memory.mjs',
|
|
523
538
|
// project-context.cjs ships as a co-located sibling: the two .mjs
|
|
@@ -555,6 +570,7 @@ const MODULES = {
|
|
|
555
570
|
'skills/cabinet-narrative-architect', 'skills/cabinet-interactive-storyteller',
|
|
556
571
|
'skills/cabinet-elegance',
|
|
557
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)
|
|
558
574
|
'scripts/triage-server.mjs', 'scripts/triage-ui.html',
|
|
559
575
|
'scripts/finding-schema.json', 'scripts/resolve-committees.cjs',
|
|
560
576
|
'scripts/review-server.mjs', 'scripts/review-ui.html',
|
|
@@ -634,11 +650,17 @@ const MODULES = {
|
|
|
634
650
|
'scripts/watchtower-lib.mjs',
|
|
635
651
|
'scripts/watchtower-queue.mjs',
|
|
636
652
|
'scripts/watchtower-routines.mjs',
|
|
653
|
+
'scripts/watchtower-hook-runner.mjs',
|
|
654
|
+
'scripts/watchtower-phase-shim.mjs',
|
|
655
|
+
'scripts/watchtower-sync.mjs',
|
|
637
656
|
'scripts/watchtower-advisories.mjs',
|
|
638
657
|
'cabinet/advisories-state-schema.md',
|
|
639
658
|
'skills/inbox',
|
|
640
659
|
'hooks/watchtower-session-start.sh',
|
|
641
660
|
'scripts/watchtower-build-context.mjs',
|
|
661
|
+
'scripts/watchtower-snapshot.mjs',
|
|
662
|
+
'scripts/watchtower-narrative-corpus.mjs',
|
|
663
|
+
'scripts/watchtower-cross-ring-reader.mjs',
|
|
642
664
|
'scripts/watchtower-ring1.mjs',
|
|
643
665
|
'scripts/watchtower-ring1-runner.sh',
|
|
644
666
|
'skills/watchtower',
|
|
@@ -655,11 +677,15 @@ const MODULES = {
|
|
|
655
677
|
'watchtower/watchtower-ring2-slow.timer',
|
|
656
678
|
'hooks/watchtower-session-end.sh',
|
|
657
679
|
'scripts/watchtower-ring3-close.mjs',
|
|
680
|
+
'scripts/watchtower-ring4.mjs',
|
|
681
|
+
'scripts/watchtower-ring4-runner.sh',
|
|
658
682
|
'scripts/watchtower-status.sh',
|
|
659
683
|
'skills/briefing',
|
|
660
684
|
'skills/threads',
|
|
685
|
+
'skills/catch-up',
|
|
661
686
|
'skills/qa-handoff',
|
|
662
687
|
'skills/qa-drain',
|
|
688
|
+
'skills/close',
|
|
663
689
|
],
|
|
664
690
|
},
|
|
665
691
|
mux: {
|
|
@@ -708,6 +734,94 @@ function walkDir(dir, base) {
|
|
|
708
734
|
return results;
|
|
709
735
|
}
|
|
710
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
|
+
|
|
711
825
|
// Signals that a directory contains a real project (not just empty)
|
|
712
826
|
const PROJECT_SIGNALS = [
|
|
713
827
|
'package.json', 'Cargo.toml', 'requirements.txt', 'pyproject.toml',
|
|
@@ -730,6 +844,13 @@ function detectProjectState(dir) {
|
|
|
730
844
|
return 'empty';
|
|
731
845
|
}
|
|
732
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
|
+
|
|
733
854
|
function parseArgs(argv) {
|
|
734
855
|
const args = argv.slice(2);
|
|
735
856
|
const flags = {
|
|
@@ -752,11 +873,32 @@ function parseArgs(argv) {
|
|
|
752
873
|
else if (arg === '--help' || arg === '-h') flags.help = true;
|
|
753
874
|
else if (arg === '--reset') flags.reset = true;
|
|
754
875
|
else if (arg === '--force') flags.force = true;
|
|
876
|
+
else if (arg === '--reindex') flags.reindex = true;
|
|
755
877
|
else if (arg === '--migrate-memory') flags.migrateMemory = true;
|
|
756
878
|
else if (arg === '--unmigrate-memory') flags.unmigrateMemory = true;
|
|
879
|
+
else if (arg === '--list-frozen') flags.listFrozen = true;
|
|
757
880
|
else if (arg === '--modules' && i + 1 < args.length) {
|
|
758
881
|
flags.modules = args[++i].split(',').map(s => s.trim()).filter(Boolean);
|
|
759
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
|
+
}
|
|
760
902
|
else if (arg === '--frontier-model' && i + 1 < args.length) {
|
|
761
903
|
// Empty/whitespace values are treated as absent: '' is a substring of
|
|
762
904
|
// every model ID, which would match everything and leave the watchdog
|
|
@@ -782,6 +924,16 @@ function printHelp() {
|
|
|
782
924
|
--modules <keys> Comma-separated module keys to install (e.g., 'verify').
|
|
783
925
|
Mandatory modules are always included.
|
|
784
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.
|
|
785
937
|
--reset Remove Claude Cabinet files (uses manifest for safety)
|
|
786
938
|
--force With --reset: remove even customized files;
|
|
787
939
|
with --migrate-memory: override already-migrated guard
|
|
@@ -823,6 +975,80 @@ async function run() {
|
|
|
823
975
|
return;
|
|
824
976
|
}
|
|
825
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
|
+
|
|
826
1052
|
if (flags.migrateMemory) {
|
|
827
1053
|
const { migrateMemoryCmd } = require('./migrate-memory-cmd');
|
|
828
1054
|
const projectDir = path.resolve(flags.targetDir);
|
|
@@ -1151,18 +1377,24 @@ async function run() {
|
|
|
1151
1377
|
return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
1152
1378
|
}
|
|
1153
1379
|
|
|
1154
|
-
//
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
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
|
|
1163
1394
|
|
|
1164
1395
|
for (const modKey of selectedModules) {
|
|
1165
1396
|
const mod = MODULES[modKey];
|
|
1397
|
+
const frozen = effectiveFrozen.has(modKey);
|
|
1166
1398
|
for (const tmpl of mod.templates) {
|
|
1167
1399
|
const srcPath = path.join(templateRoot, tmpl);
|
|
1168
1400
|
if (!fs.existsSync(srcPath)) {
|
|
@@ -1211,7 +1443,9 @@ async function run() {
|
|
|
1211
1443
|
projectRoot: projectDir,
|
|
1212
1444
|
existingManifest,
|
|
1213
1445
|
instructionPhases: instrPhases,
|
|
1446
|
+
frozen,
|
|
1214
1447
|
});
|
|
1448
|
+
if (results.frozen.length) frozenCounts[modKey] = (frozenCounts[modKey] || 0) + results.frozen.length;
|
|
1215
1449
|
totalCopied += results.copied.length;
|
|
1216
1450
|
totalSkipped += results.skipped.length;
|
|
1217
1451
|
totalOverwritten += results.overwritten.length;
|
|
@@ -1256,6 +1490,22 @@ async function run() {
|
|
|
1256
1490
|
continue;
|
|
1257
1491
|
}
|
|
1258
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
|
+
|
|
1259
1509
|
if (flags.yes || dirState === 'existing-install') {
|
|
1260
1510
|
// If file is in the old manifest, it's upstream-managed — overwrite.
|
|
1261
1511
|
// If not, it's project-created — skip.
|
|
@@ -1298,6 +1548,14 @@ async function run() {
|
|
|
1298
1548
|
}
|
|
1299
1549
|
}
|
|
1300
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
|
+
}
|
|
1301
1559
|
if (!flags.dryRun) fs.copyFileSync(srcPath, destPath);
|
|
1302
1560
|
totalCopied++;
|
|
1303
1561
|
allManifest[mPath] = incomingHash;
|
|
@@ -1308,6 +1566,28 @@ async function run() {
|
|
|
1308
1566
|
|
|
1309
1567
|
console.log(` 📁 Files: ${totalCopied} copied, ${totalOverwritten} overwritten, ${totalSkipped} unchanged`);
|
|
1310
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
|
+
|
|
1311
1591
|
// --- Make hook scripts executable ---
|
|
1312
1592
|
const hooksDir = path.join(claudeDir, 'hooks');
|
|
1313
1593
|
if (!flags.dryRun && fs.existsSync(hooksDir)) {
|
|
@@ -1378,10 +1658,34 @@ async function run() {
|
|
|
1378
1658
|
}
|
|
1379
1659
|
|
|
1380
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;
|
|
1381
1665
|
if (includeDb && selectedModules.includes('work-tracking') && !flags.dryRun) {
|
|
1382
1666
|
try {
|
|
1383
|
-
|
|
1384
|
-
|
|
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;
|
|
1385
1689
|
} catch (err) {
|
|
1386
1690
|
console.log(` ⚠ Database setup failed: ${err.message}`);
|
|
1387
1691
|
console.log(' You can set it up later: node scripts/pib-db.mjs init');
|
|
@@ -1487,78 +1791,26 @@ async function run() {
|
|
|
1487
1791
|
// S3: Itemize — list all deletions and confirm before proceeding
|
|
1488
1792
|
// S4: Backup — copy files to .cc-backup/<timestamp>/ before deletion
|
|
1489
1793
|
if (Object.keys(existingManifest).length > 0) {
|
|
1490
|
-
//
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
const srcPath = path.join(templateRoot, tmpl);
|
|
1511
|
-
if (fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory()) {
|
|
1512
|
-
for (const rel of walkDir(srcPath)) {
|
|
1513
|
-
selectedTemplatePaths.add(manifestPath(tmpl) + '/' + rel);
|
|
1514
|
-
}
|
|
1515
|
-
} else if (fs.existsSync(srcPath)) {
|
|
1516
|
-
selectedTemplatePaths.add(manifestPath(tmpl));
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
// Build CC template skill name set for project-skill guard (act:4ac281ba)
|
|
1522
|
-
const ccSkillNames = new Set();
|
|
1523
|
-
for (const mod of Object.values(MODULES)) {
|
|
1524
|
-
for (const tmpl of mod.templates || []) {
|
|
1525
|
-
if (tmpl.startsWith('skills/')) {
|
|
1526
|
-
const skillName = tmpl.split('/')[1];
|
|
1527
|
-
ccSkillNames.add(skillName);
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
// Collect files that would be removed
|
|
1533
|
-
const toRemove = [];
|
|
1534
|
-
for (const oldPath of Object.keys(existingManifest)) {
|
|
1535
|
-
if (!allManifest[oldPath]) {
|
|
1536
|
-
// Skip phase files — they may be user-customized
|
|
1537
|
-
if (/\/phases\//.test(oldPath)) continue;
|
|
1538
|
-
|
|
1539
|
-
// S1: Only delete if the path maps to a known CC template
|
|
1540
|
-
if (!allTemplatePaths.has(oldPath)) {
|
|
1541
|
-
console.log(` Keeping non-template file: ${oldPath}`);
|
|
1542
|
-
continue;
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
// S2: Only delete from selected modules (don't purge deselected modules)
|
|
1546
|
-
if (!selectedTemplatePaths.has(oldPath)) {
|
|
1547
|
-
continue;
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
// Project-skill guard: never delete skills CC didn't create
|
|
1551
|
-
const skillDirMatch = oldPath.match(/^\.claude\/skills\/([^/]+)\//);
|
|
1552
|
-
if (skillDirMatch && !ccSkillNames.has(skillDirMatch[1])) {
|
|
1553
|
-
console.log(` Keeping project skill: ${oldPath}`);
|
|
1554
|
-
continue;
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
const fullPath = path.join(projectDir, oldPath);
|
|
1558
|
-
if (fs.existsSync(fullPath)) {
|
|
1559
|
-
toRemove.push(oldPath);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
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}`);
|
|
1562
1814
|
}
|
|
1563
1815
|
|
|
1564
1816
|
// S3: Itemize and confirm before deleting
|
|
@@ -1629,6 +1881,7 @@ async function run() {
|
|
|
1629
1881
|
skipped: skippedModules,
|
|
1630
1882
|
version: VERSION,
|
|
1631
1883
|
manifest: allManifest,
|
|
1884
|
+
dbSchemaVersion,
|
|
1632
1885
|
});
|
|
1633
1886
|
console.log(' 📝 Created .ccrc.json');
|
|
1634
1887
|
}
|
|
@@ -1684,9 +1937,13 @@ async function run() {
|
|
|
1684
1937
|
} else {
|
|
1685
1938
|
console.log(' 📋 Registered in project registry');
|
|
1686
1939
|
}
|
|
1687
|
-
// 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).
|
|
1688
1943
|
if (registry.frontierModel) {
|
|
1689
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>');
|
|
1690
1947
|
}
|
|
1691
1948
|
} catch (err) {
|
|
1692
1949
|
// Non-fatal — registry is nice-to-have
|
|
@@ -1710,4 +1967,4 @@ async function run() {
|
|
|
1710
1967
|
console.log('');
|
|
1711
1968
|
}
|
|
1712
1969
|
|
|
1713
|
-
module.exports = { run, MODULES, generateAgentWrappers };
|
|
1970
|
+
module.exports = { run, MODULES, generateAgentWrappers, generateSkillIndex, expandFrozenClosure, computeRemovals, manifestPath };
|