worclaude 1.0.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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +278 -0
  3. package/package.json +62 -0
  4. package/src/commands/backup.js +55 -0
  5. package/src/commands/diff.js +76 -0
  6. package/src/commands/init.js +628 -0
  7. package/src/commands/restore.js +95 -0
  8. package/src/commands/status.js +141 -0
  9. package/src/commands/upgrade.js +208 -0
  10. package/src/core/backup.js +94 -0
  11. package/src/core/config.js +54 -0
  12. package/src/core/detector.js +43 -0
  13. package/src/core/file-categorizer.js +177 -0
  14. package/src/core/merger.js +413 -0
  15. package/src/core/scaffolder.js +60 -0
  16. package/src/data/agents.js +164 -0
  17. package/src/index.js +51 -0
  18. package/src/prompts/agent-selection.js +99 -0
  19. package/src/prompts/claude-md-merge.js +153 -0
  20. package/src/prompts/conflict-resolution.js +24 -0
  21. package/src/prompts/project-type.js +75 -0
  22. package/src/prompts/tech-stack.js +35 -0
  23. package/src/utils/display.js +41 -0
  24. package/src/utils/file.js +70 -0
  25. package/src/utils/hash.js +13 -0
  26. package/src/utils/time.js +22 -0
  27. package/templates/agents/optional/backend/api-designer.md +61 -0
  28. package/templates/agents/optional/backend/auth-auditor.md +63 -0
  29. package/templates/agents/optional/backend/database-analyst.md +61 -0
  30. package/templates/agents/optional/data/data-pipeline-reviewer.md +68 -0
  31. package/templates/agents/optional/data/ml-experiment-tracker.md +67 -0
  32. package/templates/agents/optional/data/prompt-engineer.md +75 -0
  33. package/templates/agents/optional/devops/ci-fixer.md +64 -0
  34. package/templates/agents/optional/devops/dependency-manager.md +55 -0
  35. package/templates/agents/optional/devops/deploy-validator.md +68 -0
  36. package/templates/agents/optional/devops/docker-helper.md +63 -0
  37. package/templates/agents/optional/docs/changelog-generator.md +69 -0
  38. package/templates/agents/optional/docs/doc-writer.md +60 -0
  39. package/templates/agents/optional/frontend/style-enforcer.md +47 -0
  40. package/templates/agents/optional/frontend/ui-reviewer.md +51 -0
  41. package/templates/agents/optional/quality/bug-fixer.md +54 -0
  42. package/templates/agents/optional/quality/performance-auditor.md +65 -0
  43. package/templates/agents/optional/quality/refactorer.md +61 -0
  44. package/templates/agents/optional/quality/security-reviewer.md +74 -0
  45. package/templates/agents/universal/build-validator.md +15 -0
  46. package/templates/agents/universal/code-simplifier.md +17 -0
  47. package/templates/agents/universal/plan-reviewer.md +20 -0
  48. package/templates/agents/universal/test-writer.md +17 -0
  49. package/templates/agents/universal/verify-app.md +16 -0
  50. package/templates/claude-md.md +40 -0
  51. package/templates/commands/commit-push-pr.md +9 -0
  52. package/templates/commands/compact-safe.md +8 -0
  53. package/templates/commands/end.md +9 -0
  54. package/templates/commands/review-plan.md +10 -0
  55. package/templates/commands/setup.md +112 -0
  56. package/templates/commands/start.md +3 -0
  57. package/templates/commands/status.md +6 -0
  58. package/templates/commands/techdebt.md +9 -0
  59. package/templates/commands/update-claude-md.md +9 -0
  60. package/templates/commands/verify.md +8 -0
  61. package/templates/mcp-json.json +3 -0
  62. package/templates/progress-md.md +21 -0
  63. package/templates/settings/base.json +64 -0
  64. package/templates/settings/docker.json +9 -0
  65. package/templates/settings/go.json +10 -0
  66. package/templates/settings/node.json +17 -0
  67. package/templates/settings/python.json +16 -0
  68. package/templates/settings/rust.json +11 -0
  69. package/templates/skills/templates/backend-conventions.md +57 -0
  70. package/templates/skills/templates/frontend-design-system.md +48 -0
  71. package/templates/skills/templates/project-patterns.md +48 -0
  72. package/templates/skills/universal/claude-md-maintenance.md +110 -0
  73. package/templates/skills/universal/context-management.md +71 -0
  74. package/templates/skills/universal/git-conventions.md +95 -0
  75. package/templates/skills/universal/planning-with-files.md +114 -0
  76. package/templates/skills/universal/prompt-engineering.md +97 -0
  77. package/templates/skills/universal/review-and-handoff.md +106 -0
  78. package/templates/skills/universal/subagent-usage.md +108 -0
  79. package/templates/skills/universal/testing.md +116 -0
  80. package/templates/skills/universal/verification.md +120 -0
  81. package/templates/spec-md-backend.md +85 -0
  82. package/templates/spec-md-cli.md +79 -0
  83. package/templates/spec-md-data.md +74 -0
  84. package/templates/spec-md-devops.md +87 -0
  85. package/templates/spec-md-frontend.md +81 -0
  86. package/templates/spec-md-fullstack.md +81 -0
  87. package/templates/spec-md-library.md +87 -0
  88. package/templates/spec-md.md +22 -0
  89. package/templates/workflow-meta.json +10 -0
@@ -0,0 +1,628 @@
1
+ import path from 'node:path';
2
+ import inquirer from 'inquirer';
3
+ import ora from 'ora';
4
+ import { scaffoldFile } from '../core/scaffolder.js';
5
+ import {
6
+ createWorkflowMeta,
7
+ getPackageVersion,
8
+ readWorkflowMeta,
9
+ writeWorkflowMeta,
10
+ } from '../core/config.js';
11
+ import { fileExists, writeFile, listFilesRecursive } from '../utils/file.js';
12
+ import { hashFile } from '../utils/hash.js';
13
+ import * as display from '../utils/display.js';
14
+ import { promptProjectType } from '../prompts/project-type.js';
15
+ import { promptTechStack } from '../prompts/tech-stack.js';
16
+ import { promptAgentSelection } from '../prompts/agent-selection.js';
17
+ import { detectScenario, scanExistingSetup } from '../core/detector.js';
18
+ import { createBackup } from '../core/backup.js';
19
+ import { performMerge, buildSettingsJson } from '../core/merger.js';
20
+ import {
21
+ UNIVERSAL_AGENTS,
22
+ AGENT_CATALOG,
23
+ COMMAND_FILES,
24
+ UNIVERSAL_SKILLS,
25
+ TEMPLATE_SKILLS,
26
+ TECH_STACKS,
27
+ CONFIRMATION_STEPS,
28
+ SPEC_MD_TEMPLATE_MAP,
29
+ } from '../data/agents.js';
30
+
31
+ // --- Helper functions ---
32
+
33
+ function buildCommandsBlock(languages, useDocker) {
34
+ const lines = ['```bash'];
35
+ if (languages.includes('python')) {
36
+ lines.push('# Python');
37
+ lines.push('python -m pytest # Run tests');
38
+ lines.push('ruff check . # Lint');
39
+ lines.push('ruff format . # Format');
40
+ }
41
+ if (languages.includes('node')) {
42
+ if (lines.length > 1) lines.push('');
43
+ lines.push('# Node.js / TypeScript');
44
+ lines.push('npm test # Run tests');
45
+ lines.push('npx eslint . # Lint');
46
+ lines.push('npx prettier --write . # Format');
47
+ }
48
+ if (languages.includes('rust')) {
49
+ if (lines.length > 1) lines.push('');
50
+ lines.push('# Rust');
51
+ lines.push('cargo test # Run tests');
52
+ lines.push('cargo clippy # Lint');
53
+ lines.push('cargo fmt # Format');
54
+ }
55
+ if (languages.includes('go')) {
56
+ if (lines.length > 1) lines.push('');
57
+ lines.push('# Go');
58
+ lines.push('go test ./... # Run tests');
59
+ lines.push('golangci-lint run # Lint');
60
+ lines.push('gofmt -w . # Format');
61
+ }
62
+ if (useDocker) {
63
+ if (lines.length > 1) lines.push('');
64
+ lines.push('# Docker');
65
+ lines.push('docker compose up -d # Start services');
66
+ lines.push('docker compose down # Stop services');
67
+ }
68
+ if (lines.length === 1) {
69
+ lines.push('# Add your project-specific commands here');
70
+ }
71
+ lines.push('```');
72
+ return lines.join('\n');
73
+ }
74
+
75
+ // --- Step runner functions ---
76
+
77
+ async function runProjectInfo(selections) {
78
+ const { projectName, description } = await inquirer.prompt([
79
+ {
80
+ type: 'input',
81
+ name: 'projectName',
82
+ message: 'Project name:',
83
+ default: selections.projectName || path.basename(process.cwd()),
84
+ },
85
+ {
86
+ type: 'input',
87
+ name: 'description',
88
+ message: 'One-line description:',
89
+ default: selections.description || undefined,
90
+ },
91
+ ]);
92
+ return { ...selections, projectName, description };
93
+ }
94
+
95
+ async function runProjectType(selections) {
96
+ const projectTypes = await promptProjectType();
97
+ return { ...selections, projectTypes };
98
+ }
99
+
100
+ async function runTechStack(selections) {
101
+ const { languages, useDocker } = await promptTechStack(selections.projectTypes);
102
+ return { ...selections, languages, useDocker };
103
+ }
104
+
105
+ async function runAgents(selections) {
106
+ const selectedAgents = await promptAgentSelection(selections.projectTypes);
107
+ return { ...selections, selectedAgents };
108
+ }
109
+
110
+ const STEP_RUNNERS = {
111
+ projectInfo: runProjectInfo,
112
+ projectType: runProjectType,
113
+ techStack: runTechStack,
114
+ agents: runAgents,
115
+ };
116
+
117
+ // --- Confirmation ---
118
+
119
+ async function showConfirmation(selections) {
120
+ const stackLabels = selections.languages
121
+ .filter((l) => l !== 'other')
122
+ .map((l) => {
123
+ const entry = TECH_STACKS.find((s) => s.value === l);
124
+ return entry ? entry.name : l;
125
+ });
126
+ if (selections.languages.includes('other') && stackLabels.length === 0) {
127
+ stackLabels.push('Other / None');
128
+ }
129
+ if (selections.useDocker) stackLabels.push('Docker');
130
+ const stackText = stackLabels.join(', ') || 'None specified';
131
+
132
+ const universalCount = UNIVERSAL_AGENTS.length;
133
+ const optionalCount = selections.selectedAgents.length;
134
+ const totalCount = universalCount + optionalCount;
135
+
136
+ display.reviewBox([
137
+ `Project: ${selections.projectName} — ${selections.description || 'No description'}`,
138
+ `Type: ${selections.projectTypes.join(', ')}`,
139
+ `Stack: ${stackText}`,
140
+ `Agents: ${universalCount} universal + ${optionalCount} optional (${totalCount} total)`,
141
+ ]);
142
+
143
+ const { confirmation } = await inquirer.prompt([
144
+ {
145
+ type: 'list',
146
+ name: 'confirmation',
147
+ message: 'Everything look right?',
148
+ choices: [
149
+ { name: 'Yes, install the workflow', value: 'yes' },
150
+ { name: 'No, let me start over', value: 'restart' },
151
+ { name: 'Let me adjust a specific step', value: 'adjust' },
152
+ ],
153
+ },
154
+ ]);
155
+
156
+ return confirmation;
157
+ }
158
+
159
+ // --- Shared functions ---
160
+
161
+ async function runInteractivePrompts(projectRoot) {
162
+ let selections = {
163
+ projectName: path.basename(projectRoot),
164
+ description: '',
165
+ projectTypes: [],
166
+ languages: [],
167
+ useDocker: false,
168
+ selectedAgents: [],
169
+ };
170
+
171
+ let confirmed = false;
172
+ let firstRun = true;
173
+
174
+ while (!confirmed) {
175
+ if (firstRun) {
176
+ selections = await runProjectInfo(selections);
177
+ selections = await runProjectType(selections);
178
+ selections = await runTechStack(selections);
179
+ selections = await runAgents(selections);
180
+ firstRun = false;
181
+ }
182
+
183
+ const confirmation = await showConfirmation(selections);
184
+
185
+ if (confirmation === 'yes') {
186
+ confirmed = true;
187
+ } else if (confirmation === 'restart') {
188
+ selections = {
189
+ projectName: path.basename(projectRoot),
190
+ description: '',
191
+ projectTypes: [],
192
+ languages: [],
193
+ useDocker: false,
194
+ selectedAgents: [],
195
+ };
196
+ display.newline();
197
+ display.info('Starting over...');
198
+ display.newline();
199
+ selections = await runProjectInfo(selections);
200
+ selections = await runProjectType(selections);
201
+ selections = await runTechStack(selections);
202
+ selections = await runAgents(selections);
203
+ } else if (confirmation === 'adjust') {
204
+ const { step } = await inquirer.prompt([
205
+ {
206
+ type: 'list',
207
+ name: 'step',
208
+ message: 'Which step do you want to adjust?',
209
+ choices: CONFIRMATION_STEPS,
210
+ },
211
+ ]);
212
+ selections = await STEP_RUNNERS[step](selections);
213
+ }
214
+ }
215
+
216
+ return selections;
217
+ }
218
+
219
+ function buildTemplateVariables(selections) {
220
+ const { projectName, description, languages, useDocker } = selections;
221
+
222
+ const techStackLines = languages
223
+ .filter((l) => l !== 'other')
224
+ .map((l) => {
225
+ const entry = TECH_STACKS.find((s) => s.value === l);
226
+ return `- ${entry ? entry.name : l}`;
227
+ });
228
+ if (languages.includes('other') && techStackLines.length === 0) {
229
+ techStackLines.push('- Not specified');
230
+ }
231
+ if (useDocker) techStackLines.push('- Docker');
232
+ const techStackText = techStackLines.join('\n');
233
+
234
+ const techStackTableItems = languages
235
+ .filter((l) => l !== 'other')
236
+ .map((l) => {
237
+ const entry = TECH_STACKS.find((s) => s.value === l);
238
+ return entry ? entry.name : l;
239
+ });
240
+ if (languages.includes('other') && techStackTableItems.length === 0) {
241
+ techStackTableItems.push('Not specified');
242
+ }
243
+ const techStackTable = techStackTableItems.join(', ');
244
+ const dockerRow = useDocker
245
+ ? '\n| Containers | Docker |'
246
+ : '';
247
+
248
+ const commandsText = buildCommandsBlock(languages, useDocker);
249
+
250
+ const skillsLines = TEMPLATE_SKILLS.map(
251
+ (s) => `- ${s}.md — Run /setup to fill automatically`
252
+ );
253
+ const skillsText = skillsLines.join('\n');
254
+
255
+ return {
256
+ project_name: projectName,
257
+ description: description || 'A project scaffolded with Worclaude',
258
+ tech_stack_filled_during_init: techStackText,
259
+ tech_stack: techStackText,
260
+ tech_stack_table: techStackTable,
261
+ docker_row: dockerRow,
262
+ commands_filled_during_init: commandsText,
263
+ project_specific_skills: skillsText,
264
+ timestamp: new Date().toISOString(),
265
+ };
266
+ }
267
+
268
+ async function computeAndWriteWorkflowMeta(projectRoot, selections, version) {
269
+ const fileHashes = {};
270
+ const claudeFiles = await listFilesRecursive(path.join(projectRoot, '.claude'));
271
+ for (const filePath of claudeFiles) {
272
+ const relativePath = path.relative(path.join(projectRoot, '.claude'), filePath).split(path.sep).join('/');
273
+ if (relativePath !== 'workflow-meta.json' && relativePath !== 'settings.json') {
274
+ fileHashes[relativePath] = await hashFile(filePath);
275
+ }
276
+ }
277
+
278
+ const meta = createWorkflowMeta({
279
+ version,
280
+ projectTypes: selections.projectTypes,
281
+ techStack: selections.languages,
282
+ universalAgents: UNIVERSAL_AGENTS,
283
+ optionalAgents: selections.selectedAgents,
284
+ useDocker: selections.useDocker || false,
285
+ fileHashes,
286
+ });
287
+ await writeWorkflowMeta(projectRoot, meta);
288
+ }
289
+
290
+ // --- Scenario A: Fresh scaffolding ---
291
+
292
+ async function scaffoldFresh(projectRoot, selections, variables, settingsStr, version) {
293
+ const { selectedAgents, projectTypes } = selections;
294
+ const spinner = ora('Creating workflow structure...').start();
295
+
296
+ try {
297
+ await scaffoldFile('claude-md.md', 'CLAUDE.md', variables, projectRoot);
298
+ spinner.text = 'Created CLAUDE.md';
299
+
300
+ await writeFile(path.join(projectRoot, '.claude', 'settings.json'), settingsStr);
301
+ spinner.text = 'Created .claude/settings.json';
302
+
303
+ for (const agent of UNIVERSAL_AGENTS) {
304
+ await scaffoldFile(
305
+ `agents/universal/${agent}.md`,
306
+ path.join('.claude', 'agents', `${agent}.md`),
307
+ {},
308
+ projectRoot
309
+ );
310
+ }
311
+ spinner.text = `Created ${UNIVERSAL_AGENTS.length} universal agents`;
312
+
313
+ for (const agent of selectedAgents) {
314
+ const category = AGENT_CATALOG[agent].category;
315
+ await scaffoldFile(
316
+ `agents/optional/${category}/${agent}.md`,
317
+ path.join('.claude', 'agents', `${agent}.md`),
318
+ {},
319
+ projectRoot
320
+ );
321
+ }
322
+ spinner.text = `Created ${selectedAgents.length} optional agents`;
323
+
324
+ for (const cmd of COMMAND_FILES) {
325
+ await scaffoldFile(
326
+ `commands/${cmd}.md`,
327
+ path.join('.claude', 'commands', `${cmd}.md`),
328
+ {},
329
+ projectRoot
330
+ );
331
+ }
332
+ spinner.text = `Created ${COMMAND_FILES.length} commands`;
333
+
334
+ for (const skill of UNIVERSAL_SKILLS) {
335
+ await scaffoldFile(
336
+ `skills/universal/${skill}.md`,
337
+ path.join('.claude', 'skills', `${skill}.md`),
338
+ {},
339
+ projectRoot
340
+ );
341
+ }
342
+ spinner.text = `Created ${UNIVERSAL_SKILLS.length} universal skills`;
343
+
344
+ for (const skill of TEMPLATE_SKILLS) {
345
+ await scaffoldFile(
346
+ `skills/templates/${skill}.md`,
347
+ path.join('.claude', 'skills', `${skill}.md`),
348
+ variables,
349
+ projectRoot
350
+ );
351
+ }
352
+ spinner.text = `Created ${TEMPLATE_SKILLS.length} template skills`;
353
+
354
+ await scaffoldFile('mcp-json.json', '.mcp.json', {}, projectRoot);
355
+ spinner.text = 'Created .mcp.json';
356
+
357
+ const progressPath = path.join(projectRoot, 'docs', 'spec', 'PROGRESS.md');
358
+ const specPath = path.join(projectRoot, 'docs', 'spec', 'SPEC.md');
359
+ const skipped = { progressMd: false, specMd: false };
360
+
361
+ if (!(await fileExists(progressPath))) {
362
+ await scaffoldFile(
363
+ 'progress-md.md',
364
+ path.join('docs', 'spec', 'PROGRESS.md'),
365
+ variables,
366
+ projectRoot
367
+ );
368
+ } else {
369
+ skipped.progressMd = true;
370
+ }
371
+ if (!(await fileExists(specPath))) {
372
+ const primaryType = projectTypes[0];
373
+ const specTemplate = SPEC_MD_TEMPLATE_MAP[primaryType] || 'spec-md.md';
374
+ await scaffoldFile(specTemplate, path.join('docs', 'spec', 'SPEC.md'), variables, projectRoot);
375
+ } else {
376
+ skipped.specMd = true;
377
+ }
378
+ spinner.text = 'Created docs/spec/';
379
+
380
+ await computeAndWriteWorkflowMeta(projectRoot, selections, version);
381
+ spinner.text = 'Created .claude/workflow-meta.json';
382
+
383
+ spinner.succeed('Workflow installed successfully!');
384
+ return skipped;
385
+ } catch (err) {
386
+ spinner.fail('Failed to create workflow structure');
387
+ display.error(err.message);
388
+ process.exit(1);
389
+ }
390
+ }
391
+
392
+ function displayFreshSuccess(selections, skipped) {
393
+ display.newline();
394
+ display.success('CLAUDE.md');
395
+ display.success('.claude/settings.json');
396
+ display.success('.claude/workflow-meta.json');
397
+ display.success(
398
+ `.claude/agents/ (${UNIVERSAL_AGENTS.length} universal + ${selections.selectedAgents.length} optional)`
399
+ );
400
+ display.success(`.claude/commands/ (${COMMAND_FILES.length})`);
401
+ display.success(
402
+ `.claude/skills/ (${UNIVERSAL_SKILLS.length} universal + ${TEMPLATE_SKILLS.length} templates)`
403
+ );
404
+ display.success('.mcp.json');
405
+ if (skipped.progressMd) {
406
+ display.dim(' docs/spec/PROGRESS.md — already exists, skipped');
407
+ } else {
408
+ display.success('docs/spec/PROGRESS.md');
409
+ }
410
+ if (skipped.specMd) {
411
+ display.dim(' docs/spec/SPEC.md — already exists, skipped');
412
+ } else {
413
+ display.success('docs/spec/SPEC.md');
414
+ }
415
+ display.newline();
416
+ display.info('What to do next:');
417
+ display.newline();
418
+ display.dim(' 1. Start a Claude Code session in this project');
419
+ display.dim(' 2. Run /setup — Claude will interview you about your project');
420
+ display.dim(' and fill in all configuration files automatically');
421
+ display.dim(' 3. Review CLAUDE.md and adjust if needed');
422
+ display.dim(' 4. Start building!');
423
+ display.newline();
424
+ display.info('Tip: The /setup command is the fastest way to configure');
425
+ display.dim(' your project. It takes about 5 minutes.');
426
+ display.newline();
427
+ }
428
+
429
+ // --- Scenario B: Detection report and merge report ---
430
+
431
+ function displayDetectionReport(scan) {
432
+ display.info('Detected existing Claude Code setup:');
433
+ display.newline();
434
+
435
+ const dot = (label, width = 26) => label + ' ' + '.'.repeat(width - label.length) + ' ';
436
+
437
+ display.dim(
438
+ ` ${dot('CLAUDE.md')}${scan.hasClaudeMd ? `exists (${scan.claudeMdLineCount} lines)` : 'not found'}`
439
+ );
440
+ display.dim(
441
+ ` ${dot('.claude/settings.json')}${scan.hasSettingsJson ? 'exists' : 'not found'}`
442
+ );
443
+ display.dim(
444
+ ` ${dot('.claude/skills/')}${scan.existingSkills.length > 0 ? `${scan.existingSkills.length} files found` : 'not found'}`
445
+ );
446
+ display.dim(
447
+ ` ${dot('.claude/agents/')}${scan.existingAgents.length > 0 ? `${scan.existingAgents.length} files found` : 'not found'}`
448
+ );
449
+ display.dim(
450
+ ` ${dot('.claude/commands/')}${scan.existingCommands.length > 0 ? `${scan.existingCommands.length} files found` : 'not found'}`
451
+ );
452
+ display.dim(
453
+ ` ${dot('.mcp.json')}${scan.hasMcpJson ? 'exists' : 'not found'}`
454
+ );
455
+ display.newline();
456
+ display.info('A backup will be created before any changes.');
457
+ display.newline();
458
+ }
459
+
460
+ function displayMergeReport(report, backupPath) {
461
+ display.newline();
462
+
463
+ // Tier 1 — Added
464
+ if (
465
+ report.added.agents.length > 0 ||
466
+ report.added.commands.length > 0 ||
467
+ report.added.skills.length > 0
468
+ ) {
469
+ display.info('Added automatically:');
470
+ if (report.added.agents.length > 0) {
471
+ display.success(`${report.added.agents.length} agents added`);
472
+ }
473
+ if (report.added.commands.length > 0) {
474
+ display.success(`${report.added.commands.length} commands added`);
475
+ }
476
+ if (report.added.skills.length > 0) {
477
+ display.success(
478
+ `${report.added.skills.length} skills added${report.conflicts.skills.length > 0 ? ` (${report.conflicts.skills.length} conflicts saved as .workflow-ref.md)` : ''}`
479
+ );
480
+ }
481
+ if (report.added.permissions > 0) {
482
+ display.success(`${report.added.permissions} permission rules appended to settings.json`);
483
+ }
484
+ if (report.added.hooks > 0) {
485
+ display.success(`${report.added.hooks} hooks added to settings.json`);
486
+ }
487
+ display.newline();
488
+ }
489
+
490
+ // Tier 2 — Conflicts
491
+ const allConflicts = [
492
+ ...report.conflicts.skills,
493
+ ...report.conflicts.agents,
494
+ ...report.conflicts.commands,
495
+ ];
496
+ if (allConflicts.length > 0) {
497
+ display.info('Conflicts (saved alongside for review):');
498
+ for (const file of allConflicts) {
499
+ const refName = file.replace('.md', '.workflow-ref.md');
500
+ display.warn(`${file} → ${refName}`);
501
+ }
502
+ display.newline();
503
+ }
504
+
505
+ // Tier 3 — Hook conflicts
506
+ if (report.hookConflicts.length > 0) {
507
+ display.info('Hook conflicts resolved:');
508
+ for (const desc of report.hookConflicts) {
509
+ display.dim(` ${desc}`);
510
+ }
511
+ display.newline();
512
+ }
513
+
514
+ // CLAUDE.md handling
515
+ if (report.claudeMdHandling === 'suggestions-generated') {
516
+ display.success('Suggestions saved to CLAUDE.md.workflow-suggestions');
517
+ } else if (report.claudeMdHandling === 'merged-sections') {
518
+ display.success('CLAUDE.md updated with selected sections');
519
+ } else if (report.claudeMdHandling === 'created') {
520
+ display.success('CLAUDE.md created');
521
+ }
522
+
523
+ // Skipped
524
+ if (report.skipped.progressMd) {
525
+ display.dim(' docs/spec/PROGRESS.md — already exists, skipped');
526
+ }
527
+ if (report.skipped.specMd) {
528
+ display.dim(' docs/spec/SPEC.md — already exists, skipped');
529
+ }
530
+
531
+ // Summary
532
+ display.newline();
533
+ if (backupPath) {
534
+ display.dim(` Backup: ${path.basename(backupPath)}/`);
535
+ }
536
+ display.newline();
537
+ display.info('What to do next:');
538
+ display.newline();
539
+ if (allConflicts.length > 0) {
540
+ display.dim(' 1. Review .workflow-ref.md files and merge what\'s useful');
541
+ }
542
+ if (report.claudeMdHandling === 'suggestions-generated') {
543
+ display.dim(' 2. Review CLAUDE.md.workflow-suggestions');
544
+ display.dim(' 3. Delete .workflow-ref.md and .workflow-suggestions files when done');
545
+ }
546
+ display.dim(' Run /setup in Claude Code for project-specific configuration');
547
+ display.newline();
548
+ }
549
+
550
+ // --- Main command ---
551
+
552
+ export async function initCommand() {
553
+ const projectRoot = process.cwd();
554
+
555
+ // Step 1: Detect scenario
556
+ const scenario = await detectScenario(projectRoot);
557
+
558
+ if (scenario === 'upgrade') {
559
+ const meta = await readWorkflowMeta(projectRoot);
560
+ display.info(
561
+ `This project was initialized with Worclaude v${meta?.version || 'unknown'}.`
562
+ );
563
+ display.info('Use `worclaude upgrade` to update.');
564
+ return;
565
+ }
566
+
567
+ // Step 2: Welcome
568
+ const version = await getPackageVersion();
569
+ display.header(`Worclaude v${version}`);
570
+ display.newline();
571
+
572
+ // Step 3: If existing project, show detection report and confirm
573
+ let existingScan = null;
574
+ let backupPath = null;
575
+
576
+ if (scenario === 'existing') {
577
+ existingScan = await scanExistingSetup(projectRoot);
578
+ displayDetectionReport(existingScan);
579
+
580
+ const { proceed } = await inquirer.prompt([
581
+ {
582
+ type: 'list',
583
+ name: 'proceed',
584
+ message: 'Proceed with workflow installation?',
585
+ choices: [
586
+ { name: 'Yes', value: true },
587
+ { name: 'No', value: false },
588
+ ],
589
+ },
590
+ ]);
591
+
592
+ if (!proceed) {
593
+ display.info('Installation cancelled.');
594
+ return;
595
+ }
596
+
597
+ const spinner = ora('Creating backup...').start();
598
+ backupPath = await createBackup(projectRoot);
599
+ spinner.succeed(`Backed up to ${path.basename(backupPath)}/`);
600
+ display.newline();
601
+ }
602
+
603
+ // Step 4: Interactive prompts (shared between A and B)
604
+ const selections = await runInteractivePrompts(projectRoot);
605
+
606
+ // Step 5: Build template variables
607
+ const variables = buildTemplateVariables(selections);
608
+
609
+ // Step 6: Branch by scenario
610
+ if (scenario === 'fresh') {
611
+ const { settingsStr } = await buildSettingsJson(selections.languages, selections.useDocker);
612
+ const skipped = await scaffoldFresh(projectRoot, selections, variables, settingsStr, version);
613
+ displayFreshSuccess(selections, skipped);
614
+ } else {
615
+ // Scenario B: merge
616
+ const spinner = ora('Merging workflow...').start();
617
+ try {
618
+ const report = await performMerge(projectRoot, existingScan, selections, variables, { spinner });
619
+ await computeAndWriteWorkflowMeta(projectRoot, selections, version);
620
+ spinner.succeed('Workflow merged successfully!');
621
+ displayMergeReport(report, backupPath);
622
+ } catch (err) {
623
+ spinner.fail('Failed to merge workflow');
624
+ display.error(err.message);
625
+ process.exit(1);
626
+ }
627
+ }
628
+ }