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/cli.js ADDED
@@ -0,0 +1,624 @@
1
+ const prompts = require('prompts');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const crypto = require('crypto');
6
+ const { copyTemplates } = require('./copy');
7
+ const { mergeSettings } = require('./settings-merge');
8
+ const { create: createMetadata, read: readMetadata } = require('./metadata');
9
+ const { setupDb } = require('./db-setup');
10
+ const { reset } = require('./reset');
11
+
12
+ const VERSION = require('../package.json').version;
13
+
14
+ const MODULES = {
15
+ 'session-loop': {
16
+ name: 'Session Loop (orient + debrief)',
17
+ description: 'The briefing cycle. Claude starts each session informed, ends by preparing the next briefing.',
18
+ mandatory: true,
19
+ templates: ['skills/orient', 'skills/orient-quick', 'skills/debrief', 'skills/debrief-quick', 'skills/debrief/phases/upstream-feedback.md', 'skills/menu', 'hooks/stop-hook.md'],
20
+ },
21
+ 'hooks': {
22
+ name: 'Git Guardrails + Telemetry',
23
+ description: 'Block destructive git ops (force push, hard reset). Track skill usage via JSONL telemetry.',
24
+ mandatory: false,
25
+ default: true,
26
+ lean: true,
27
+ templates: ['hooks/git-guardrails.sh', 'hooks/cor-upstream-guard.sh', 'hooks/skill-telemetry.sh', 'hooks/skill-tool-telemetry.sh', 'scripts/cor-drift-check.cjs'],
28
+ },
29
+ 'work-tracking': {
30
+ name: 'Work Tracking (pib-db or markdown)',
31
+ description: 'Track work items for orient/debrief. Default: SQLite (pib-db). Also supports markdown (tasks.md) or external systems. Choice made during /onboard.',
32
+ mandatory: false,
33
+ default: true,
34
+ lean: false,
35
+ templates: ['scripts/pib-db.js', 'scripts/pib-db-schema.sql'],
36
+ needsDb: true,
37
+ },
38
+ 'planning': {
39
+ name: 'Planning + Execution (plan + execute)',
40
+ description: 'Structured planning with cabinet critique โ€” members weigh in before you build.',
41
+ mandatory: false,
42
+ default: true,
43
+ lean: true,
44
+ templates: ['skills/plan', 'skills/execute', 'skills/investigate'],
45
+ },
46
+ 'compliance': {
47
+ name: 'Compliance Stack (rules + enforcement)',
48
+ description: 'Scoped instructions that load by path. Enforcement pipeline for promoting patterns to rules.',
49
+ mandatory: false,
50
+ default: true,
51
+ lean: false,
52
+ templates: ['rules/enforcement-pipeline.md', 'memory/patterns/_pattern-template.md', 'memory/patterns/pattern-intelligence-first.md'],
53
+ },
54
+ 'audit': {
55
+ name: 'Audit Loop (audit + triage + cabinet)',
56
+ description: '20 expert cabinet members review your project. Convene the full cabinet or just one committee.',
57
+ mandatory: false,
58
+ default: true,
59
+ lean: true,
60
+ templates: [
61
+ 'skills/audit', 'skills/pulse', 'skills/triage-audit',
62
+ 'cabinet', 'briefing',
63
+ 'skills/cabinet-accessibility', 'skills/cabinet-anti-confirmation',
64
+ 'skills/cabinet-architecture', 'skills/cabinet-boundary-man',
65
+ 'skills/cabinet-cor-health', 'skills/cabinet-data-integrity',
66
+ 'skills/cabinet-debugger', 'skills/cabinet-historian',
67
+ 'skills/cabinet-organized-mind', 'skills/cabinet-process-therapist',
68
+ 'skills/cabinet-qa', 'skills/cabinet-record-keeper',
69
+ 'skills/cabinet-roster-check', 'skills/cabinet-security',
70
+ 'skills/cabinet-small-screen', 'skills/cabinet-speed-freak',
71
+ 'skills/cabinet-system-advocate', 'skills/cabinet-technical-debt',
72
+ 'skills/cabinet-usability', 'skills/cabinet-workflow-cop',
73
+ 'scripts/merge-findings.js', 'scripts/load-triage-history.js',
74
+ 'scripts/triage-server.mjs', 'scripts/triage-ui.html',
75
+ 'scripts/finding-schema.json',
76
+ ],
77
+ },
78
+ 'lifecycle': {
79
+ name: 'Lifecycle (onboard + seed + cor-upgrade + link)',
80
+ description: 'Onboarding prepares the briefings. Seed proposes new cabinet members when you adopt new tech.',
81
+ mandatory: false,
82
+ default: true,
83
+ lean: true,
84
+ templates: ['skills/onboard', 'skills/seed', 'skills/cor-upgrade', 'skills/link', 'skills/unlink', 'skills/publish', 'skills/extract'],
85
+ },
86
+ 'validate': {
87
+ name: 'Validate',
88
+ description: 'Structural validation checks with unified summary. Define your own validators.',
89
+ mandatory: false,
90
+ default: true,
91
+ lean: false,
92
+ templates: ['skills/validate'],
93
+ },
94
+ };
95
+
96
+ // Signals that a directory contains a real project (not just empty)
97
+ const PROJECT_SIGNALS = [
98
+ 'package.json', 'Cargo.toml', 'requirements.txt', 'pyproject.toml',
99
+ 'go.mod', 'Gemfile', 'pom.xml', 'build.gradle', 'CMakeLists.txt',
100
+ 'Makefile', '.git', 'src', 'lib', 'app', 'main.py', 'index.js',
101
+ 'index.ts', 'README.md', 'CLAUDE.md', '.claude',
102
+ ];
103
+
104
+ function detectProjectState(dir) {
105
+ const entries = fs.readdirSync(dir);
106
+ const signals = entries.filter(e => PROJECT_SIGNALS.includes(e));
107
+ const hasClaude = entries.includes('.claude');
108
+ const hasPibrc = fs.existsSync(path.join(dir, '.corrc.json'));
109
+
110
+ if (hasPibrc) return 'existing-install';
111
+ if (signals.length > 0) return 'existing-project';
112
+ // Allow a few dotfiles (e.g. .git) without calling it a project
113
+ if (entries.filter(e => !e.startsWith('.')).length === 0) return 'empty';
114
+ return 'empty';
115
+ }
116
+
117
+ function parseArgs(argv) {
118
+ const args = argv.slice(2);
119
+ const flags = {
120
+ yes: false,
121
+ lean: false,
122
+ noDb: false,
123
+ dryRun: false,
124
+ help: false,
125
+ reset: false,
126
+ force: false,
127
+ targetDir: '.',
128
+ };
129
+
130
+ for (const arg of args) {
131
+ if (arg === '--yes' || arg === '-y') flags.yes = true;
132
+ else if (arg === '--lean') flags.lean = true;
133
+ else if (arg === '--no-db') flags.noDb = true;
134
+ else if (arg === '--dry-run') flags.dryRun = true;
135
+ else if (arg === '--help' || arg === '-h') flags.help = true;
136
+ else if (arg === '--reset') flags.reset = true;
137
+ else if (arg === '--force') flags.force = true;
138
+ else if (!arg.startsWith('-')) flags.targetDir = arg;
139
+ }
140
+
141
+ return flags;
142
+ }
143
+
144
+ function printHelp() {
145
+ console.log(`
146
+ Usage: npx create-claude-cabinet [directory] [options]
147
+
148
+ Options:
149
+ --yes, -y Accept all defaults, no prompts
150
+ --lean Install core modules only (no work-tracking, compliance, validate)
151
+ --no-db Skip work tracking database setup
152
+ --dry-run Show what would be copied without writing
153
+ --reset Remove Claude Cabinet files (uses manifest for safety)
154
+ --force With --reset: remove even customized files
155
+ --help, -h Show this help
156
+
157
+ Examples:
158
+ npx create-claude-cabinet Interactive setup in current dir
159
+ npx create-claude-cabinet my-project Set up in ./my-project/
160
+ npx create-claude-cabinet --yes Install everything, no questions
161
+ npx create-claude-cabinet --lean Session loop + planning + cabinet
162
+ npx create-claude-cabinet --yes --no-db Install everything except DB
163
+ npx create-claude-cabinet --dry-run Preview what would be installed
164
+ npx create-claude-cabinet --reset Remove CoR files safely
165
+ npx create-claude-cabinet --reset --dry-run Preview what --reset would do
166
+ `);
167
+ }
168
+
169
+ async function run() {
170
+ const flags = parseArgs(process.argv);
171
+
172
+ if (flags.help) {
173
+ printHelp();
174
+ return;
175
+ }
176
+
177
+ if (flags.reset) {
178
+ const projectDir = path.resolve(flags.targetDir);
179
+ await reset(projectDir, { dryRun: flags.dryRun, force: flags.force });
180
+ return;
181
+ }
182
+
183
+ console.log('');
184
+ console.log(' ๐Ÿ—„๏ธ Claude Cabinet v' + VERSION);
185
+ console.log(' A cabinet of experts for your Claude Code project');
186
+ console.log('');
187
+
188
+ if (flags.dryRun) {
189
+ console.log(' [dry run โ€” no files will be written]\n');
190
+ }
191
+
192
+ let projectDir = path.resolve(flags.targetDir);
193
+
194
+ // --- User identity + project registry ---
195
+ const claudeHome = path.join(os.homedir(), '.claude');
196
+ const registryPath = path.join(claudeHome, 'cor-registry.json');
197
+ const claudeMdPath = path.join(claudeHome, 'CLAUDE.md');
198
+
199
+ if (!flags.dryRun) {
200
+ if (!fs.existsSync(claudeHome)) fs.mkdirSync(claudeHome, { recursive: true });
201
+ }
202
+
203
+ // First-ever CoR install: set up user identity
204
+ const firstCorInstall = !fs.existsSync(registryPath);
205
+ if (firstCorInstall && !flags.yes && !flags.lean && !flags.dryRun) {
206
+ const claudeMdContent = fs.existsSync(claudeMdPath) ? fs.readFileSync(claudeMdPath, 'utf8') : '';
207
+ if (!claudeMdContent.includes('# About Me')) {
208
+ console.log(' This looks like your first time assembling a cabinet.');
209
+ console.log(' Quick question so Claude knows who you are across all projects.\n');
210
+ const { userName } = await prompts({ type: 'text', name: 'userName', message: "What's your name?" });
211
+ const { userRole } = await prompts({ type: 'text', name: 'userRole', message: 'What do you do? (e.g. "I run a bakery", "I\'m a freelance designer")' });
212
+ if (userName || userRole) {
213
+ let profile = '\n# About Me\n\n';
214
+ if (userName) profile += `Name: ${userName}\n`;
215
+ if (userRole) profile += `${userRole}\n`;
216
+ profile += '\n<!-- Added by Claude Cabinet. Claude sees this in every project. -->\n';
217
+ profile += '<!-- Edit ~/.claude/CLAUDE.md to update. -->\n';
218
+ fs.appendFileSync(claudeMdPath, profile);
219
+ console.log('\n โœ“ Saved to ~/.claude/CLAUDE.md โ€” Claude will know this everywhere.\n');
220
+ }
221
+ }
222
+ }
223
+
224
+ // --- Directory detection ---
225
+ const dirState = detectProjectState(projectDir);
226
+
227
+ let existingManifest = {};
228
+ if (dirState === 'existing-install') {
229
+ const existing = readMetadata(projectDir);
230
+ existingManifest = existing.manifest || {};
231
+ console.log(` Found existing installation (v${existing.version}, installed ${existing.installedAt.split('T')[0]})`);
232
+ console.log(' Updating upstream-managed files and adding new files.');
233
+ if (!flags.yes && !flags.lean) {
234
+ const { proceed } = await prompts({
235
+ type: 'confirm',
236
+ name: 'proceed',
237
+ message: 'Add new files from latest version?',
238
+ initial: true,
239
+ });
240
+ if (!proceed) {
241
+ console.log(' Cancelled.');
242
+ return;
243
+ }
244
+ }
245
+ } else if (dirState === 'existing-project') {
246
+ console.log(` Detected existing project in ${projectDir}`);
247
+ if (!flags.yes && !flags.lean) {
248
+ const { action } = await prompts({
249
+ type: 'select',
250
+ name: 'action',
251
+ message: 'What would you like to do?',
252
+ choices: [
253
+ { title: 'Add Claude Cabinet to this project', value: 'here' },
254
+ { title: 'Create a new project in a different directory', value: 'new' },
255
+ ],
256
+ });
257
+
258
+ if (!action) {
259
+ console.log(' Cancelled.');
260
+ return;
261
+ }
262
+
263
+ if (action === 'new') {
264
+ const { newDir } = await prompts({
265
+ type: 'text',
266
+ name: 'newDir',
267
+ message: 'Directory name for the new project:',
268
+ });
269
+ if (!newDir) {
270
+ console.log(' Cancelled.');
271
+ return;
272
+ }
273
+ projectDir = path.resolve(newDir);
274
+ if (!fs.existsSync(projectDir)) {
275
+ if (!flags.dryRun) fs.mkdirSync(projectDir, { recursive: true });
276
+ console.log(` Created ${projectDir}`);
277
+ }
278
+ }
279
+ }
280
+ // --yes with existing project: install here (the most useful default)
281
+ } else {
282
+ // Empty directory
283
+ console.log(` Setting up in ${projectDir}`);
284
+ }
285
+
286
+ // --- Module selection ---
287
+ let selectedModules = [];
288
+ let skippedModules = {};
289
+ let includeDb = !flags.noDb;
290
+
291
+ if (flags.lean) {
292
+ selectedModules = Object.entries(MODULES)
293
+ .filter(([, mod]) => mod.mandatory || mod.lean)
294
+ .map(([key]) => key);
295
+ includeDb = false;
296
+ const skippedKeys = Object.keys(MODULES).filter(k => !selectedModules.includes(k));
297
+ for (const k of skippedKeys) {
298
+ skippedModules[k] = 'Skipped by --lean install';
299
+ }
300
+ console.log(` Lean install: ${selectedModules.length} modules (session-loop, hooks, planning, audit + cabinet, lifecycle).`);
301
+ console.log(` Skipped: ${skippedKeys.join(', ')}.\n`);
302
+ } else if (flags.yes) {
303
+ selectedModules = Object.keys(MODULES);
304
+ if (flags.noDb) {
305
+ includeDb = false;
306
+ // work-tracking templates are still copied (pib-db.js, schema) but
307
+ // the DB isn't initialized. Mark it as skipped so /onboard knows
308
+ // to ask about the alternative work tracking system.
309
+ selectedModules = selectedModules.filter(m => m !== 'work-tracking');
310
+ skippedModules['work-tracking'] = 'Database setup skipped (--no-db)';
311
+ }
312
+ console.log(` Installing all ${selectedModules.length} modules.${flags.noDb ? ' (skipping work-tracking DB)' : ''}\n`);
313
+ } else {
314
+ const { installMode } = await prompts({
315
+ type: 'select',
316
+ name: 'installMode',
317
+ message: 'How much do you want to install?',
318
+ choices: [
319
+ { title: 'Everything โ€” all modules, full setup', value: 'full' },
320
+ { title: 'Lean โ€” session loop + planning + cabinet (no DB, compliance, validate)', value: 'lean' },
321
+ { title: 'Custom โ€” choose modules individually', value: 'custom' },
322
+ ],
323
+ });
324
+
325
+ if (!installMode) {
326
+ console.log(' Cancelled.');
327
+ return;
328
+ }
329
+
330
+ if (installMode === 'full') {
331
+ selectedModules = Object.keys(MODULES);
332
+ console.log(`\n Installing all ${selectedModules.length} modules.\n`);
333
+ } else if (installMode === 'lean') {
334
+ selectedModules = Object.entries(MODULES)
335
+ .filter(([, mod]) => mod.mandatory || mod.lean)
336
+ .map(([key]) => key);
337
+ includeDb = false;
338
+ const skippedKeys = Object.keys(MODULES).filter(k => !selectedModules.includes(k));
339
+ for (const k of skippedKeys) {
340
+ skippedModules[k] = 'Skipped by lean install';
341
+ }
342
+ console.log(`\n Lean install: ${selectedModules.length} modules.`);
343
+ console.log(` Skipped: ${skippedKeys.join(', ')}.\n`);
344
+ } else {
345
+ for (const [key, mod] of Object.entries(MODULES)) {
346
+ if (mod.mandatory) {
347
+ console.log(` โœ“ ${mod.name} (mandatory)`);
348
+ selectedModules.push(key);
349
+ continue;
350
+ }
351
+
352
+ const { include } = await prompts({
353
+ type: 'confirm',
354
+ name: 'include',
355
+ message: `${mod.name}\n ${mod.description}`,
356
+ initial: mod.default !== false,
357
+ });
358
+
359
+ if (include) {
360
+ selectedModules.push(key);
361
+ } else {
362
+ const { reason } = await prompts({
363
+ type: 'text',
364
+ name: 'reason',
365
+ message: ` Why skip ${mod.name}? (brief reason, or Enter to skip)`,
366
+ });
367
+ skippedModules[key] = reason || 'Not needed yet';
368
+ }
369
+ }
370
+
371
+ // DB prompt (only if work-tracking was selected and --no-db not set)
372
+ if (selectedModules.includes('work-tracking') && !flags.noDb) {
373
+ const { db } = await prompts({
374
+ type: 'confirm',
375
+ name: 'db',
376
+ message: 'Set up work tracking database? (requires better-sqlite3)',
377
+ initial: true,
378
+ });
379
+ includeDb = db;
380
+ } else if (!selectedModules.includes('work-tracking')) {
381
+ includeDb = false;
382
+ }
383
+ }
384
+ }
385
+
386
+ console.log(' Assembling your cabinet...\n');
387
+
388
+ // --- Copy template files ---
389
+ const templateRoot = path.join(__dirname, '..', 'templates');
390
+ const claudeDir = path.join(projectDir, '.claude');
391
+
392
+ let totalCopied = 0;
393
+ let totalSkipped = 0;
394
+ let totalOverwritten = 0;
395
+ const allManifest = {}; // relPath -> hash for all written files
396
+
397
+ function hashContent(content) {
398
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
399
+ }
400
+
401
+ // Compute the relative path from projectDir for manifest entries
402
+ function manifestPath(tmpl) {
403
+ if (tmpl.startsWith('skills/') || tmpl.startsWith('hooks/') || tmpl.startsWith('rules/')) {
404
+ return '.claude/' + tmpl;
405
+ } else if (tmpl.startsWith('scripts/')) {
406
+ return tmpl;
407
+ }
408
+ return '.claude/' + tmpl;
409
+ }
410
+
411
+ for (const modKey of selectedModules) {
412
+ const mod = MODULES[modKey];
413
+ for (const tmpl of mod.templates) {
414
+ const srcPath = path.join(templateRoot, tmpl);
415
+ if (!fs.existsSync(srcPath)) {
416
+ console.log(` โš  Template not found: ${tmpl}`);
417
+ continue;
418
+ }
419
+
420
+ let destPath;
421
+ if (tmpl.startsWith('skills/') || tmpl.startsWith('hooks/') || tmpl.startsWith('rules/')) {
422
+ destPath = path.join(claudeDir, tmpl);
423
+ } else if (tmpl.startsWith('scripts/')) {
424
+ destPath = path.join(projectDir, tmpl);
425
+ } else {
426
+ destPath = path.join(claudeDir, tmpl);
427
+ }
428
+
429
+ const stat = fs.statSync(srcPath);
430
+ if (stat.isDirectory()) {
431
+ // For most skill directories, skip phases/ โ€” absent phase files
432
+ // use skeleton defaults. Phase files are created by /onboard
433
+ // based on the project interview, not copied as generic templates.
434
+ // Exception: cabinet members (always copied), and skills whose phase
435
+ // files ARE the instructions (onboard, seed, cor-upgrade, publish,
436
+ // extract) โ€” these need their phases to function.
437
+ const alwaysCopyPhases = [
438
+ 'skills/onboard', 'skills/seed',
439
+ 'skills/cor-upgrade', 'skills/publish', 'skills/extract',
440
+ ];
441
+ const isSkill = tmpl.startsWith('skills/') && !alwaysCopyPhases.some(p => tmpl.startsWith(p));
442
+ const results = await copyTemplates(srcPath, destPath, {
443
+ dryRun: flags.dryRun,
444
+ skipConflicts: flags.yes || dirState === 'existing-install',
445
+ skipPhases: isSkill,
446
+ projectRoot: projectDir,
447
+ existingManifest,
448
+ });
449
+ totalCopied += results.copied.length;
450
+ totalSkipped += results.skipped.length;
451
+ totalOverwritten += results.overwritten.length;
452
+ // Collect manifest entries โ€” prefix with the dest-relative path
453
+ const prefix = manifestPath(tmpl);
454
+ for (const [relFile, hash] of Object.entries(results.manifest)) {
455
+ allManifest[prefix + '/' + relFile] = hash;
456
+ }
457
+ } else {
458
+ const destDir = path.dirname(destPath);
459
+ if (!flags.dryRun && !fs.existsSync(destDir)) {
460
+ fs.mkdirSync(destDir, { recursive: true });
461
+ }
462
+
463
+ const incoming = fs.readFileSync(srcPath, 'utf8');
464
+ const incomingHash = hashContent(incoming);
465
+ const mPath = manifestPath(tmpl);
466
+
467
+ if (fs.existsSync(destPath)) {
468
+ const existingContent = fs.readFileSync(destPath, 'utf8');
469
+ if (existingContent === incoming) {
470
+ totalSkipped++;
471
+ allManifest[mPath] = incomingHash;
472
+ continue;
473
+ }
474
+
475
+ if (flags.yes || dirState === 'existing-install') {
476
+ // If file is in the old manifest, it's upstream-managed โ€” overwrite.
477
+ // If not, it's project-created โ€” skip.
478
+ if (existingManifest[mPath]) {
479
+ if (!flags.dryRun) fs.copyFileSync(srcPath, destPath);
480
+ totalOverwritten++;
481
+ } else {
482
+ totalSkipped++;
483
+ }
484
+ allManifest[mPath] = incomingHash;
485
+ } else {
486
+ const response = await prompts({
487
+ type: 'select',
488
+ name: 'action',
489
+ message: `File exists: ${tmpl}`,
490
+ choices: [
491
+ { title: 'Keep existing', value: 'keep' },
492
+ { title: 'Overwrite with template', value: 'overwrite' },
493
+ ],
494
+ });
495
+
496
+ if (response.action === 'overwrite') {
497
+ if (!flags.dryRun) fs.copyFileSync(srcPath, destPath);
498
+ totalOverwritten++;
499
+ } else {
500
+ totalSkipped++;
501
+ }
502
+ allManifest[mPath] = incomingHash;
503
+ }
504
+ } else {
505
+ if (!flags.dryRun) fs.copyFileSync(srcPath, destPath);
506
+ totalCopied++;
507
+ allManifest[mPath] = incomingHash;
508
+ }
509
+ }
510
+ }
511
+ }
512
+
513
+ console.log(` ๐Ÿ“ Files: ${totalCopied} copied, ${totalOverwritten} overwritten, ${totalSkipped} unchanged`);
514
+
515
+ // --- Make hook scripts executable ---
516
+ const hooksDir = path.join(claudeDir, 'hooks');
517
+ if (!flags.dryRun && fs.existsSync(hooksDir)) {
518
+ const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh'));
519
+ for (const f of hookFiles) {
520
+ fs.chmodSync(path.join(hooksDir, f), 0o755);
521
+ }
522
+ if (hookFiles.length > 0) {
523
+ console.log(` ๐Ÿ”ง Made ${hookFiles.length} hook scripts executable`);
524
+ }
525
+ }
526
+
527
+ // --- Merge hooks into settings.json ---
528
+ if (selectedModules.includes('hooks') && !flags.dryRun) {
529
+ const settingsPath = mergeSettings(projectDir, { includeDb });
530
+ console.log(` โš™๏ธ Merged hooks into ${path.relative(projectDir, settingsPath)}`);
531
+ }
532
+
533
+ // --- Set up database ---
534
+ if (includeDb && selectedModules.includes('work-tracking') && !flags.dryRun) {
535
+ try {
536
+ const dbResults = setupDb(projectDir);
537
+ for (const r of dbResults) console.log(` ๐Ÿ—„๏ธ ${r}`);
538
+ } catch (err) {
539
+ console.log(` โš  Database setup failed: ${err.message}`);
540
+ console.log(' You can set it up later: node scripts/pib-db.js init');
541
+ }
542
+ }
543
+
544
+ // --- Clean up files removed upstream ---
545
+ if (Object.keys(existingManifest).length > 0) {
546
+ let totalRemoved = 0;
547
+ for (const oldPath of Object.keys(existingManifest)) {
548
+ if (!allManifest[oldPath]) {
549
+ const fullPath = path.join(projectDir, oldPath);
550
+ if (fs.existsSync(fullPath)) {
551
+ if (!flags.dryRun) fs.unlinkSync(fullPath);
552
+ totalRemoved++;
553
+ }
554
+ }
555
+ }
556
+ if (totalRemoved > 0) {
557
+ console.log(` ๐Ÿงน Removed ${totalRemoved} file${totalRemoved === 1 ? '' : 's'} no longer in upstream`);
558
+ }
559
+ }
560
+
561
+ // --- Write metadata ---
562
+ if (!flags.dryRun) {
563
+ createMetadata(projectDir, {
564
+ modules: selectedModules,
565
+ skipped: skippedModules,
566
+ version: VERSION,
567
+ manifest: allManifest,
568
+ });
569
+ console.log(' ๐Ÿ“ Created .corrc.json');
570
+ }
571
+
572
+ // --- Update project registry ---
573
+ if (!flags.dryRun) {
574
+ try {
575
+ let registry = { projects: [] };
576
+ if (fs.existsSync(registryPath)) {
577
+ registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
578
+ }
579
+ const existingIdx = registry.projects.findIndex(p => p.path === projectDir);
580
+ const entry = {
581
+ path: projectDir,
582
+ name: path.basename(projectDir),
583
+ description: '',
584
+ version: VERSION,
585
+ updatedAt: new Date().toISOString(),
586
+ };
587
+ if (existingIdx >= 0) {
588
+ entry.name = registry.projects[existingIdx].name || entry.name;
589
+ entry.description = registry.projects[existingIdx].description || '';
590
+ registry.projects[existingIdx] = entry;
591
+ } else {
592
+ // Register with folder name. /onboard fills in name and description later.
593
+ registry.projects.push(entry);
594
+ }
595
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\n');
596
+ const otherCount = registry.projects.filter(p => p.path !== projectDir).length;
597
+ if (otherCount > 0) {
598
+ console.log(` ๐Ÿ“‹ Registered in project registry (${otherCount} other project${otherCount === 1 ? '' : 's'})`);
599
+ } else {
600
+ console.log(' ๐Ÿ“‹ Registered in project registry');
601
+ }
602
+ } catch (err) {
603
+ // Non-fatal โ€” registry is nice-to-have
604
+ }
605
+ }
606
+
607
+ // --- Summary ---
608
+ console.log('\n โœ… Cabinet assembled!\n');
609
+ console.log(' Next steps:');
610
+ console.log(' 1. Run /onboard in Claude Code โ€” Claude will interview you and prepare the briefings');
611
+ console.log(' 2. Start each session with /orient โ€” get briefed');
612
+ console.log(' 3. End each session with /debrief โ€” close the loop for next time');
613
+ const skippedKeys = Object.keys(skippedModules);
614
+ if (skippedKeys.length > 0) {
615
+ console.log(`\n Skipped modules: ${skippedKeys.join(', ')}`);
616
+ console.log(' Re-run `npx create-claude-cabinet` to add them later.');
617
+ }
618
+ if (flags.dryRun) {
619
+ console.log('\n [dry run โ€” nothing was written to disk]');
620
+ }
621
+ console.log('');
622
+ }
623
+
624
+ module.exports = { run, MODULES };