create-claude-cabinet 0.6.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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/bin/create-claude-cabinet.js +8 -0
  4. package/lib/cli.js +624 -0
  5. package/lib/copy.js +152 -0
  6. package/lib/db-setup.js +51 -0
  7. package/lib/metadata.js +42 -0
  8. package/lib/reset.js +193 -0
  9. package/lib/settings-merge.js +93 -0
  10. package/package.json +29 -0
  11. package/templates/EXTENSIONS.md +311 -0
  12. package/templates/README.md +485 -0
  13. package/templates/briefing/_briefing-api-template.md +21 -0
  14. package/templates/briefing/_briefing-architecture-template.md +16 -0
  15. package/templates/briefing/_briefing-cabinet-template.md +20 -0
  16. package/templates/briefing/_briefing-identity-template.md +18 -0
  17. package/templates/briefing/_briefing-scopes-template.md +39 -0
  18. package/templates/briefing/_briefing-template.md +148 -0
  19. package/templates/briefing/_briefing-work-tracking-template.md +18 -0
  20. package/templates/cabinet/committees-template.yaml +49 -0
  21. package/templates/cabinet/composition-patterns.md +240 -0
  22. package/templates/cabinet/eval-protocol.md +208 -0
  23. package/templates/cabinet/lifecycle.md +93 -0
  24. package/templates/cabinet/output-contract.md +148 -0
  25. package/templates/cabinet/prompt-guide.md +266 -0
  26. package/templates/hooks/cor-upstream-guard.sh +79 -0
  27. package/templates/hooks/git-guardrails.sh +67 -0
  28. package/templates/hooks/skill-telemetry.sh +66 -0
  29. package/templates/hooks/skill-tool-telemetry.sh +54 -0
  30. package/templates/hooks/stop-hook.md +56 -0
  31. package/templates/memory/patterns/_pattern-template.md +119 -0
  32. package/templates/memory/patterns/pattern-intelligence-first.md +41 -0
  33. package/templates/rules/enforcement-pipeline.md +151 -0
  34. package/templates/scripts/cor-drift-check.cjs +84 -0
  35. package/templates/scripts/finding-schema.json +94 -0
  36. package/templates/scripts/load-triage-history.js +151 -0
  37. package/templates/scripts/merge-findings.js +126 -0
  38. package/templates/scripts/pib-db-schema.sql +68 -0
  39. package/templates/scripts/pib-db.js +365 -0
  40. package/templates/scripts/triage-server.mjs +98 -0
  41. package/templates/scripts/triage-ui.html +536 -0
  42. package/templates/skills/audit/SKILL.md +273 -0
  43. package/templates/skills/audit/phases/finding-output.md +56 -0
  44. package/templates/skills/audit/phases/member-execution.md +83 -0
  45. package/templates/skills/audit/phases/member-selection.md +44 -0
  46. package/templates/skills/audit/phases/structural-checks.md +54 -0
  47. package/templates/skills/audit/phases/triage-history.md +45 -0
  48. package/templates/skills/cabinet-accessibility/SKILL.md +180 -0
  49. package/templates/skills/cabinet-anti-confirmation/SKILL.md +172 -0
  50. package/templates/skills/cabinet-architecture/SKILL.md +279 -0
  51. package/templates/skills/cabinet-boundary-man/SKILL.md +265 -0
  52. package/templates/skills/cabinet-cor-health/SKILL.md +342 -0
  53. package/templates/skills/cabinet-data-integrity/SKILL.md +157 -0
  54. package/templates/skills/cabinet-debugger/SKILL.md +221 -0
  55. package/templates/skills/cabinet-historian/SKILL.md +253 -0
  56. package/templates/skills/cabinet-organized-mind/SKILL.md +338 -0
  57. package/templates/skills/cabinet-process-therapist/SKILL.md +261 -0
  58. package/templates/skills/cabinet-qa/SKILL.md +205 -0
  59. package/templates/skills/cabinet-record-keeper/SKILL.md +168 -0
  60. package/templates/skills/cabinet-roster-check/SKILL.md +297 -0
  61. package/templates/skills/cabinet-security/SKILL.md +181 -0
  62. package/templates/skills/cabinet-small-screen/SKILL.md +154 -0
  63. package/templates/skills/cabinet-speed-freak/SKILL.md +169 -0
  64. package/templates/skills/cabinet-system-advocate/SKILL.md +194 -0
  65. package/templates/skills/cabinet-technical-debt/SKILL.md +115 -0
  66. package/templates/skills/cabinet-usability/SKILL.md +189 -0
  67. package/templates/skills/cabinet-workflow-cop/SKILL.md +238 -0
  68. package/templates/skills/cor-upgrade/SKILL.md +302 -0
  69. package/templates/skills/debrief/SKILL.md +409 -0
  70. package/templates/skills/debrief/phases/auto-maintenance.md +48 -0
  71. package/templates/skills/debrief/phases/close-work.md +88 -0
  72. package/templates/skills/debrief/phases/health-checks.md +54 -0
  73. package/templates/skills/debrief/phases/inventory.md +40 -0
  74. package/templates/skills/debrief/phases/loose-ends.md +52 -0
  75. package/templates/skills/debrief/phases/record-lessons.md +67 -0
  76. package/templates/skills/debrief/phases/report.md +59 -0
  77. package/templates/skills/debrief/phases/update-state.md +48 -0
  78. package/templates/skills/debrief/phases/upstream-feedback.md +129 -0
  79. package/templates/skills/debrief-quick/SKILL.md +12 -0
  80. package/templates/skills/execute/SKILL.md +293 -0
  81. package/templates/skills/execute/phases/cabinet.md +49 -0
  82. package/templates/skills/execute/phases/commit-and-deploy.md +66 -0
  83. package/templates/skills/execute/phases/load-plan.md +49 -0
  84. package/templates/skills/execute/phases/validators.md +50 -0
  85. package/templates/skills/execute/phases/verification-tools.md +67 -0
  86. package/templates/skills/extract/SKILL.md +168 -0
  87. package/templates/skills/investigate/SKILL.md +160 -0
  88. package/templates/skills/link/SKILL.md +52 -0
  89. package/templates/skills/menu/SKILL.md +61 -0
  90. package/templates/skills/onboard/SKILL.md +356 -0
  91. package/templates/skills/onboard/phases/detect-state.md +79 -0
  92. package/templates/skills/onboard/phases/generate-briefing.md +127 -0
  93. package/templates/skills/onboard/phases/generate-session-loop.md +87 -0
  94. package/templates/skills/onboard/phases/interview.md +233 -0
  95. package/templates/skills/onboard/phases/modularity-menu.md +162 -0
  96. package/templates/skills/onboard/phases/options.md +98 -0
  97. package/templates/skills/onboard/phases/post-onboard-audit.md +121 -0
  98. package/templates/skills/onboard/phases/summary.md +122 -0
  99. package/templates/skills/onboard/phases/work-tracking.md +231 -0
  100. package/templates/skills/orient/SKILL.md +251 -0
  101. package/templates/skills/orient/phases/auto-maintenance.md +48 -0
  102. package/templates/skills/orient/phases/briefing.md +53 -0
  103. package/templates/skills/orient/phases/cabinet.md +46 -0
  104. package/templates/skills/orient/phases/context.md +63 -0
  105. package/templates/skills/orient/phases/data-sync.md +35 -0
  106. package/templates/skills/orient/phases/health-checks.md +50 -0
  107. package/templates/skills/orient/phases/work-scan.md +69 -0
  108. package/templates/skills/orient-quick/SKILL.md +12 -0
  109. package/templates/skills/plan/SKILL.md +358 -0
  110. package/templates/skills/plan/phases/cabinet-critique.md +47 -0
  111. package/templates/skills/plan/phases/calibration-examples.md +75 -0
  112. package/templates/skills/plan/phases/completeness-check.md +44 -0
  113. package/templates/skills/plan/phases/composition-check.md +36 -0
  114. package/templates/skills/plan/phases/overlap-check.md +62 -0
  115. package/templates/skills/plan/phases/plan-template.md +69 -0
  116. package/templates/skills/plan/phases/present.md +60 -0
  117. package/templates/skills/plan/phases/research.md +43 -0
  118. package/templates/skills/plan/phases/work-tracker.md +95 -0
  119. package/templates/skills/publish/SKILL.md +74 -0
  120. package/templates/skills/pulse/SKILL.md +242 -0
  121. package/templates/skills/pulse/phases/auto-fix-scope.md +40 -0
  122. package/templates/skills/pulse/phases/checks.md +58 -0
  123. package/templates/skills/pulse/phases/output.md +54 -0
  124. package/templates/skills/seed/SKILL.md +257 -0
  125. package/templates/skills/seed/phases/build-member.md +93 -0
  126. package/templates/skills/seed/phases/evaluate-existing.md +61 -0
  127. package/templates/skills/seed/phases/maintain.md +92 -0
  128. package/templates/skills/seed/phases/scan-signals.md +86 -0
  129. package/templates/skills/triage-audit/SKILL.md +251 -0
  130. package/templates/skills/triage-audit/phases/apply-verdicts.md +90 -0
  131. package/templates/skills/triage-audit/phases/load-findings.md +38 -0
  132. package/templates/skills/triage-audit/phases/triage-ui.md +66 -0
  133. package/templates/skills/unlink/SKILL.md +35 -0
  134. package/templates/skills/validate/SKILL.md +116 -0
  135. package/templates/skills/validate/phases/validators.md +53 -0
package/lib/copy.js ADDED
@@ -0,0 +1,152 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const prompts = require('prompts');
5
+
6
+ function hashContent(content) {
7
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
8
+ }
9
+
10
+ /**
11
+ * Recursively copy files from src to dest, surfacing conflicts.
12
+ * Returns { copied: string[], skipped: string[], overwritten: string[] }
13
+ */
14
+ async function copyTemplates(src, dest, { dryRun = false, skipConflicts = false, skipPhases = false, projectRoot = null, existingManifest = {} } = {}) {
15
+ const results = { copied: [], skipped: [], overwritten: [], manifest: {} };
16
+ await walkAndCopy(src, dest, src, results, dryRun, skipConflicts, skipPhases, projectRoot, existingManifest);
17
+ return results;
18
+ }
19
+
20
+ async function walkAndCopy(srcRoot, destRoot, currentSrc, results, dryRun, skipConflicts, skipPhases, projectRoot, existingManifest) {
21
+ const entries = fs.readdirSync(currentSrc, { withFileTypes: true });
22
+
23
+ for (const entry of entries) {
24
+ const srcPath = path.join(currentSrc, entry.name);
25
+ const relPath = path.relative(srcRoot, srcPath);
26
+ const destPath = path.join(destRoot, relPath);
27
+ // Display path relative to project root for clearer conflict prompts
28
+ const displayPath = projectRoot ? path.relative(projectRoot, destPath) : relPath;
29
+
30
+ if (entry.isDirectory()) {
31
+ // Skip phases/ directories — absent phase files use skeleton defaults,
32
+ // which is the correct behavior for most adopters. Phase files are
33
+ // only created when the user customizes behavior via /onboard.
34
+ if (skipPhases && entry.name === 'phases') {
35
+ continue;
36
+ }
37
+ if (!dryRun && !fs.existsSync(destPath)) {
38
+ fs.mkdirSync(destPath, { recursive: true });
39
+ }
40
+ await walkAndCopy(srcRoot, destRoot, srcPath, results, dryRun, skipConflicts, skipPhases, projectRoot, existingManifest);
41
+ } else {
42
+ const incoming = fs.readFileSync(srcPath, 'utf8');
43
+ const incomingHash = hashContent(incoming);
44
+
45
+ if (fs.existsSync(destPath)) {
46
+ const existing = fs.readFileSync(destPath, 'utf8');
47
+
48
+ if (existing === incoming) {
49
+ results.skipped.push(relPath);
50
+ results.manifest[relPath] = incomingHash;
51
+ continue;
52
+ }
53
+
54
+ if (skipConflicts) {
55
+ // Check if file is upstream-managed (in the old manifest).
56
+ // If so, overwrite — it can't be customized (hook enforces this).
57
+ // If not, skip — it's project-created content.
58
+ const manifestKey = projectRoot
59
+ ? path.relative(projectRoot, destPath)
60
+ : relPath;
61
+ if (existingManifest[manifestKey]) {
62
+ if (!dryRun) fs.copyFileSync(srcPath, destPath);
63
+ results.overwritten.push(relPath);
64
+ results.manifest[relPath] = incomingHash;
65
+ } else {
66
+ results.skipped.push(relPath);
67
+ results.manifest[relPath] = incomingHash;
68
+ }
69
+ continue;
70
+ }
71
+
72
+ const response = await prompts({
73
+ type: 'select',
74
+ name: 'action',
75
+ message: `File exists: ${displayPath}`,
76
+ choices: [
77
+ { title: 'Keep existing', value: 'keep' },
78
+ { title: 'Overwrite with template', value: 'overwrite' },
79
+ { title: 'Show diff', value: 'diff' },
80
+ ],
81
+ });
82
+
83
+ if (!response.action) {
84
+ // User cancelled
85
+ results.skipped.push(relPath);
86
+ results.manifest[relPath] = incomingHash;
87
+ continue;
88
+ }
89
+
90
+ if (response.action === 'diff') {
91
+ showDiff(existing, incoming, relPath);
92
+ const followUp = await prompts({
93
+ type: 'confirm',
94
+ name: 'overwrite',
95
+ message: `Overwrite ${relPath}?`,
96
+ initial: false,
97
+ });
98
+ if (followUp.overwrite && !dryRun) {
99
+ fs.copyFileSync(srcPath, destPath);
100
+ results.overwritten.push(relPath);
101
+ } else {
102
+ results.skipped.push(relPath);
103
+ }
104
+ results.manifest[relPath] = incomingHash;
105
+ } else if (response.action === 'overwrite') {
106
+ if (!dryRun) fs.copyFileSync(srcPath, destPath);
107
+ results.overwritten.push(relPath);
108
+ results.manifest[relPath] = incomingHash;
109
+ } else {
110
+ results.skipped.push(relPath);
111
+ results.manifest[relPath] = incomingHash;
112
+ }
113
+ } else {
114
+ if (!dryRun) {
115
+ const dir = path.dirname(destPath);
116
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
117
+ fs.copyFileSync(srcPath, destPath);
118
+ }
119
+ results.copied.push(relPath);
120
+ results.manifest[relPath] = incomingHash;
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ function showDiff(existing, incoming, relPath) {
127
+ const existingLines = existing.split('\n');
128
+ const incomingLines = incoming.split('\n');
129
+
130
+ console.log(`\n--- ${relPath} (existing)`);
131
+ console.log(`+++ ${relPath} (template)`);
132
+ console.log('');
133
+
134
+ // Simple line-by-line comparison
135
+ const maxLines = Math.max(existingLines.length, incomingLines.length);
136
+ for (let i = 0; i < maxLines; i++) {
137
+ const a = existingLines[i];
138
+ const b = incomingLines[i];
139
+ if (a === b) continue;
140
+ if (a !== undefined && b !== undefined) {
141
+ console.log(` ${i + 1}: - ${a}`);
142
+ console.log(` ${i + 1}: + ${b}`);
143
+ } else if (a !== undefined) {
144
+ console.log(` ${i + 1}: - ${a}`);
145
+ } else {
146
+ console.log(` ${i + 1}: + ${b}`);
147
+ }
148
+ }
149
+ console.log('');
150
+ }
151
+
152
+ module.exports = { copyTemplates };
@@ -0,0 +1,51 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ /**
6
+ * Set up the PIB database in the target project.
7
+ * - Copies pib-db.js and schema to scripts/
8
+ * - Runs npm init if no package.json
9
+ * - Installs better-sqlite3
10
+ * - Runs pib-db init
11
+ */
12
+ function setupDb(projectDir) {
13
+ const scriptsDir = path.join(projectDir, 'scripts');
14
+ const results = [];
15
+
16
+ // Check if package.json exists; if not, init
17
+ const pkgPath = path.join(projectDir, 'package.json');
18
+ if (!fs.existsSync(pkgPath)) {
19
+ console.log(' Initializing package.json...');
20
+ execSync('npm init -y', { cwd: projectDir, stdio: 'pipe' });
21
+ results.push('Created package.json');
22
+ }
23
+
24
+ // pib-db.js uses ESM imports — ensure package.json has "type": "module"
25
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
26
+ if (pkg.type !== 'module') {
27
+ pkg.type = 'module';
28
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
29
+ results.push('Set package.json type to "module"');
30
+ }
31
+
32
+ // Install better-sqlite3 if not already installed
33
+ const nodeModules = path.join(projectDir, 'node_modules', 'better-sqlite3');
34
+ if (!fs.existsSync(nodeModules)) {
35
+ console.log(' Installing better-sqlite3...');
36
+ execSync('npm install better-sqlite3', { cwd: projectDir, stdio: 'pipe' });
37
+ results.push('Installed better-sqlite3');
38
+ }
39
+
40
+ // Initialize the database
41
+ const dbScript = path.join(scriptsDir, 'pib-db.js');
42
+ if (fs.existsSync(dbScript)) {
43
+ console.log(' Initializing database...');
44
+ execSync(`node ${dbScript} init`, { cwd: projectDir, stdio: 'pipe' });
45
+ results.push('Initialized pib.db');
46
+ }
47
+
48
+ return results;
49
+ }
50
+
51
+ module.exports = { setupDb };
@@ -0,0 +1,42 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const METADATA_FILE = '.corrc.json';
5
+
6
+ function metadataPath(projectDir) {
7
+ return path.join(projectDir, METADATA_FILE);
8
+ }
9
+
10
+ function read(projectDir) {
11
+ const file = metadataPath(projectDir);
12
+ if (!fs.existsSync(file)) return null;
13
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
14
+ }
15
+
16
+ function write(projectDir, data) {
17
+ const file = metadataPath(projectDir);
18
+ fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n');
19
+ }
20
+
21
+ function create(projectDir, { modules, skipped, version, manifest = {} }) {
22
+ const data = {
23
+ version,
24
+ installedAt: new Date().toISOString(),
25
+ upstreamPackage: 'create-claude-cabinet',
26
+ modules: {},
27
+ skipped: {},
28
+ manifest,
29
+ };
30
+
31
+ for (const mod of modules) {
32
+ data.modules[mod] = true;
33
+ }
34
+ for (const [mod, reason] of Object.entries(skipped)) {
35
+ data.skipped[mod] = reason;
36
+ }
37
+
38
+ write(projectDir, data);
39
+ return data;
40
+ }
41
+
42
+ module.exports = { read, write, create, metadataPath, METADATA_FILE };
package/lib/reset.js ADDED
@@ -0,0 +1,193 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const { read: readMetadata, METADATA_FILE } = require('./metadata');
5
+ const { DEFAULT_HOOKS } = require('./settings-merge');
6
+
7
+ // CoR-managed hook command patterns — used to identify hooks to remove
8
+ const COR_HOOK_PATTERNS = [
9
+ '.claude/hooks/git-guardrails.sh',
10
+ '.claude/hooks/skill-telemetry.sh',
11
+ '.claude/hooks/skill-tool-telemetry.sh',
12
+ ];
13
+
14
+ function hashContent(content) {
15
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
16
+ }
17
+
18
+ /**
19
+ * Reconstruct manifest from module list for 0.1.x installs that lack one.
20
+ * Maps module names to their expected template paths using the MODULES
21
+ * definition from cli.js. Returns a best-effort manifest with no hashes.
22
+ */
23
+ function reconstructManifest(metadata) {
24
+ // We don't import MODULES to avoid circular deps — use a static mapping
25
+ // that covers the known 0.1.x template structure.
26
+ console.log(' ⚠ No manifest found (0.1.x install). Reconstructing from modules...');
27
+ console.log(' All files will be treated as unmodified (no hash data).\n');
28
+ return {};
29
+ }
30
+
31
+ /**
32
+ * Remove CoR files from a project using the manifest for safety.
33
+ *
34
+ * For each manifest entry:
35
+ * - Hash matches → remove (unmodified CoR file)
36
+ * - Hash differs → skip with [CUSTOMIZED] warning (unless --force)
37
+ * - File missing → skip (already removed)
38
+ *
39
+ * Files NOT in the manifest are left alone (user-created, onboard-generated).
40
+ */
41
+ async function reset(projectDir, { dryRun = false, force = false } = {}) {
42
+ console.log('');
43
+ console.log(' 🗄️ Claude Cabinet — Reset');
44
+ console.log('');
45
+
46
+ if (dryRun) {
47
+ console.log(' [dry run — no files will be removed]\n');
48
+ }
49
+
50
+ const metadata = readMetadata(projectDir);
51
+ if (!metadata) {
52
+ console.log(' No .corrc.json found — nothing to reset.');
53
+ return;
54
+ }
55
+
56
+ console.log(` Found installation (v${metadata.version}, installed ${metadata.installedAt.split('T')[0]})`);
57
+ console.log('');
58
+
59
+ let manifest = metadata.manifest;
60
+ if (!manifest || Object.keys(manifest).length === 0) {
61
+ manifest = reconstructManifest(metadata);
62
+ if (Object.keys(manifest).length === 0) {
63
+ console.log(' Could not reconstruct manifest. Use --force to remove all CoR directories.\n');
64
+ if (!force) return;
65
+ }
66
+ }
67
+
68
+ const removed = [];
69
+ const customized = [];
70
+ const missing = [];
71
+ const forced = [];
72
+
73
+ // Process each manifest entry
74
+ for (const [relPath, installedHash] of Object.entries(manifest)) {
75
+ const fullPath = path.join(projectDir, relPath);
76
+
77
+ if (!fs.existsSync(fullPath)) {
78
+ missing.push(relPath);
79
+ continue;
80
+ }
81
+
82
+ const currentContent = fs.readFileSync(fullPath, 'utf8');
83
+ const currentHash = hashContent(currentContent);
84
+
85
+ if (currentHash === installedHash) {
86
+ // Unmodified — safe to remove
87
+ if (!dryRun) {
88
+ fs.unlinkSync(fullPath);
89
+ cleanEmptyDirs(path.dirname(fullPath), projectDir);
90
+ }
91
+ removed.push(relPath);
92
+ } else if (force) {
93
+ // Modified but --force used
94
+ if (!dryRun) {
95
+ fs.unlinkSync(fullPath);
96
+ cleanEmptyDirs(path.dirname(fullPath), projectDir);
97
+ }
98
+ forced.push(relPath);
99
+ } else {
100
+ customized.push(relPath);
101
+ }
102
+ }
103
+
104
+ // Clean CoR hooks from settings.json
105
+ const settingsPath = path.join(projectDir, '.claude', 'settings.json');
106
+ let hooksRemoved = 0;
107
+ if (fs.existsSync(settingsPath)) {
108
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
109
+ if (settings.hooks) {
110
+ for (const [event, hookGroups] of Object.entries(settings.hooks)) {
111
+ const filtered = hookGroups.filter(group => {
112
+ const commands = group.hooks.map(h => h.command);
113
+ return !commands.some(cmd => COR_HOOK_PATTERNS.includes(cmd));
114
+ });
115
+ const removedCount = hookGroups.length - filtered.length;
116
+ hooksRemoved += removedCount;
117
+ if (filtered.length === 0) {
118
+ delete settings.hooks[event];
119
+ } else {
120
+ settings.hooks[event] = filtered;
121
+ }
122
+ }
123
+ if (Object.keys(settings.hooks).length === 0) {
124
+ delete settings.hooks;
125
+ }
126
+ if (!dryRun) {
127
+ if (Object.keys(settings).length === 0) {
128
+ fs.unlinkSync(settingsPath);
129
+ } else {
130
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ // Remove .corrc.json last
137
+ const pibrcPath = path.join(projectDir, METADATA_FILE);
138
+ if (!dryRun && fs.existsSync(pibrcPath)) {
139
+ fs.unlinkSync(pibrcPath);
140
+ }
141
+
142
+ // Print summary
143
+ if (removed.length > 0) {
144
+ console.log(` ✅ Removed ${removed.length} unmodified file${removed.length === 1 ? '' : 's'}`);
145
+ for (const f of removed) console.log(` [REMOVED] ${f}`);
146
+ }
147
+
148
+ if (forced.length > 0) {
149
+ console.log(` ⚠ Force-removed ${forced.length} customized file${forced.length === 1 ? '' : 's'}`);
150
+ for (const f of forced) console.log(` [FORCED] ${f}`);
151
+ }
152
+
153
+ if (customized.length > 0) {
154
+ console.log(` ⏭ Skipped ${customized.length} customized file${customized.length === 1 ? '' : 's'} (use --force to remove)`);
155
+ for (const f of customized) console.log(` [CUSTOMIZED] ${f}`);
156
+ }
157
+
158
+ if (missing.length > 0) {
159
+ console.log(` ℹ ${missing.length} manifest file${missing.length === 1 ? '' : 's'} already removed`);
160
+ }
161
+
162
+ if (hooksRemoved > 0) {
163
+ console.log(` 🔧 Removed ${hooksRemoved} CoR hook${hooksRemoved === 1 ? '' : 's'} from settings.json`);
164
+ }
165
+
166
+ if (!dryRun) {
167
+ console.log(` 📝 Removed ${METADATA_FILE}`);
168
+ }
169
+
170
+ console.log('\n Reset complete.\n');
171
+ }
172
+
173
+ /**
174
+ * Remove empty directories up to (but not including) the stop directory.
175
+ */
176
+ function cleanEmptyDirs(dir, stopDir) {
177
+ const resolved = path.resolve(dir);
178
+ const stop = path.resolve(stopDir);
179
+
180
+ if (resolved === stop || !resolved.startsWith(stop)) return;
181
+
182
+ try {
183
+ const entries = fs.readdirSync(resolved);
184
+ if (entries.length === 0) {
185
+ fs.rmdirSync(resolved);
186
+ cleanEmptyDirs(path.dirname(resolved), stopDir);
187
+ }
188
+ } catch {
189
+ // Directory doesn't exist or not empty — stop
190
+ }
191
+ }
192
+
193
+ module.exports = { reset };
@@ -0,0 +1,93 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const DEFAULT_HOOKS = {
5
+ PreToolUse: [
6
+ {
7
+ matcher: 'Bash',
8
+ hooks: [
9
+ {
10
+ type: 'command',
11
+ command: '.claude/hooks/git-guardrails.sh',
12
+ },
13
+ ],
14
+ },
15
+ {
16
+ matcher: 'Edit|Write',
17
+ hooks: [
18
+ {
19
+ type: 'command',
20
+ command: '.claude/hooks/cor-upstream-guard.sh',
21
+ },
22
+ ],
23
+ },
24
+ ],
25
+ UserPromptSubmit: [
26
+ {
27
+ matcher: '',
28
+ hooks: [
29
+ {
30
+ type: 'command',
31
+ command: '.claude/hooks/skill-telemetry.sh',
32
+ },
33
+ ],
34
+ },
35
+ ],
36
+ PostToolUse: [
37
+ {
38
+ matcher: 'Skill',
39
+ hooks: [
40
+ {
41
+ type: 'command',
42
+ command: '.claude/hooks/skill-tool-telemetry.sh',
43
+ },
44
+ ],
45
+ },
46
+ ],
47
+ };
48
+
49
+ /**
50
+ * Merge PIB hooks into the project's .claude/settings.json.
51
+ * Creates the file if it doesn't exist. Preserves existing hooks.
52
+ */
53
+ function mergeSettings(projectDir, { includeDb = true } = {}) {
54
+ const settingsDir = path.join(projectDir, '.claude');
55
+ const settingsPath = path.join(settingsDir, 'settings.json');
56
+
57
+ if (!fs.existsSync(settingsDir)) {
58
+ fs.mkdirSync(settingsDir, { recursive: true });
59
+ }
60
+
61
+ let settings = {};
62
+ if (fs.existsSync(settingsPath)) {
63
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
64
+ }
65
+
66
+ if (!settings.hooks) settings.hooks = {};
67
+
68
+ // Merge each hook event type
69
+ for (const [event, newHooks] of Object.entries(DEFAULT_HOOKS)) {
70
+ if (!settings.hooks[event]) {
71
+ settings.hooks[event] = newHooks;
72
+ } else {
73
+ // Add hooks that don't already exist (check by command path)
74
+ for (const newHook of newHooks) {
75
+ const existingCommands = settings.hooks[event].flatMap(h =>
76
+ h.hooks.map(hh => hh.command)
77
+ );
78
+ const newCommands = newHook.hooks.map(h => h.command);
79
+ const alreadyExists = newCommands.every(cmd =>
80
+ existingCommands.includes(cmd)
81
+ );
82
+ if (!alreadyExists) {
83
+ settings.hooks[event].push(newHook);
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
90
+ return settingsPath;
91
+ }
92
+
93
+ module.exports = { mergeSettings, DEFAULT_HOOKS };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "create-claude-cabinet",
3
+ "version": "0.6.0",
4
+ "description": "Claude Cabinet — opinionated process scaffolding for Claude Code projects",
5
+ "bin": {
6
+ "create-claude-cabinet": "bin/create-claude-cabinet.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "lib/",
11
+ "templates/"
12
+ ],
13
+ "keywords": [
14
+ "claude",
15
+ "claude-code",
16
+ "scaffolding",
17
+ "process",
18
+ "ai",
19
+ "workflow"
20
+ ],
21
+ "author": "Oren Magid",
22
+ "license": "MIT",
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "dependencies": {
27
+ "prompts": "^2.4.2"
28
+ }
29
+ }