worclaude 2.2.6 → 2.4.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 (54) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +114 -37
  3. package/package.json +7 -2
  4. package/src/commands/doctor.js +507 -104
  5. package/src/commands/init.js +103 -19
  6. package/src/core/detector.js +34 -10
  7. package/src/core/merger.js +48 -3
  8. package/src/core/scaffolder.js +58 -1
  9. package/src/data/agent-registry.js +17 -2
  10. package/src/data/agents.js +7 -0
  11. package/src/index.js +2 -1
  12. package/src/prompts/claude-md-merge.js +5 -0
  13. package/templates/agents/universal/build-validator.md +23 -0
  14. package/templates/agents/universal/code-simplifier.md +11 -0
  15. package/templates/agents/universal/plan-reviewer.md +39 -0
  16. package/templates/agents/universal/test-writer.md +25 -0
  17. package/templates/agents/universal/upstream-watcher.md +120 -0
  18. package/templates/agents/universal/verify-app.md +12 -0
  19. package/templates/commands/build-fix.md +5 -0
  20. package/templates/commands/commit-push-pr.md +6 -0
  21. package/templates/commands/compact-safe.md +5 -0
  22. package/templates/commands/conflict-resolver.md +5 -0
  23. package/templates/commands/end.md +10 -0
  24. package/templates/commands/learn.md +33 -0
  25. package/templates/commands/refactor-clean.md +10 -0
  26. package/templates/commands/review-changes.md +5 -0
  27. package/templates/commands/review-plan.md +5 -0
  28. package/templates/commands/setup.md +5 -0
  29. package/templates/commands/start.md +10 -0
  30. package/templates/commands/status.md +5 -0
  31. package/templates/commands/sync.md +5 -0
  32. package/templates/commands/techdebt.md +5 -0
  33. package/templates/commands/test-coverage.md +5 -0
  34. package/templates/commands/update-claude-md.md +5 -0
  35. package/templates/commands/upstream-check.md +85 -0
  36. package/templates/commands/verify.md +11 -0
  37. package/templates/core/agents-md.md +25 -0
  38. package/templates/core/claude-md.md +17 -0
  39. package/templates/hooks/README.md +106 -0
  40. package/templates/hooks/correction-detect.cjs +48 -0
  41. package/templates/hooks/examples/prompt-hook-commit-validator.json +16 -0
  42. package/templates/hooks/learn-capture.cjs +179 -0
  43. package/templates/hooks/pre-compact-save.cjs +60 -0
  44. package/templates/hooks/skill-hint.cjs +66 -0
  45. package/templates/memory/decisions.md +11 -0
  46. package/templates/memory/preferences.md +17 -0
  47. package/templates/settings/base.json +56 -8
  48. package/templates/skills/templates/backend-conventions.md +1 -0
  49. package/templates/skills/templates/frontend-design-system.md +1 -0
  50. package/templates/skills/templates/project-patterns.md +1 -0
  51. package/templates/skills/universal/coding-principles.md +60 -0
  52. package/templates/skills/universal/context-management.md +21 -0
  53. package/templates/skills/universal/planning-with-files.md +11 -0
  54. package/templates/skills/universal/verification.md +18 -0
@@ -1,7 +1,13 @@
1
1
  import path from 'node:path';
2
2
  import inquirer from 'inquirer';
3
3
  import ora from 'ora';
4
- import { scaffoldFile, updateGitignore } from '../core/scaffolder.js';
4
+ import {
5
+ scaffoldFile,
6
+ updateGitignore,
7
+ scaffoldHooks,
8
+ scaffoldPluginJson,
9
+ scaffoldMemoryDocs,
10
+ } from '../core/scaffolder.js';
5
11
  import {
6
12
  computeFileHashes,
7
13
  createWorkflowMeta,
@@ -211,11 +217,30 @@ async function runAgents(selections) {
211
217
  return { ...selections, selectedAgents };
212
218
  }
213
219
 
220
+ async function runOptionalExtras(selections) {
221
+ const { generatePluginJson, scaffoldGtdMemory } = await inquirer.prompt([
222
+ {
223
+ type: 'confirm',
224
+ name: 'generatePluginJson',
225
+ message: 'Generate .claude-plugin/plugin.json for marketplace compatibility?',
226
+ default: selections.generatePluginJson || false,
227
+ },
228
+ {
229
+ type: 'confirm',
230
+ name: 'scaffoldGtdMemory',
231
+ message: 'Scaffold structured memory files (decisions.md, preferences.md)?',
232
+ default: selections.scaffoldGtdMemory || false,
233
+ },
234
+ ]);
235
+ return { ...selections, generatePluginJson, scaffoldGtdMemory };
236
+ }
237
+
214
238
  const STEP_RUNNERS = {
215
239
  projectInfo: runProjectInfo,
216
240
  projectType: runProjectType,
217
241
  techStack: runTechStack,
218
242
  agents: runAgents,
243
+ optionalExtras: runOptionalExtras,
219
244
  };
220
245
 
221
246
  // --- Confirmation ---
@@ -251,6 +276,14 @@ async function showConfirmation(selections) {
251
276
  console.log(
252
277
  ` ${'Agents'.padEnd(10)}${display.white(`${universalCount} universal + ${optionalCount} optional`)} ${display.dimColor(`(${totalCount} total)`)}`
253
278
  );
279
+
280
+ const extrasLabels = [];
281
+ if (selections.generatePluginJson) extrasLabels.push('plugin.json');
282
+ if (selections.scaffoldGtdMemory) extrasLabels.push('memory docs');
283
+ if (extrasLabels.length > 0) {
284
+ console.log(` ${'Extras'.padEnd(10)}${display.white(extrasLabels.join(', '))}`);
285
+ }
286
+
254
287
  display.newline();
255
288
 
256
289
  const { confirmation } = await inquirer.prompt([
@@ -271,25 +304,37 @@ async function showConfirmation(selections) {
271
304
 
272
305
  // --- Shared functions ---
273
306
 
274
- async function runInteractivePrompts(projectRoot) {
275
- let selections = {
307
+ function createInitialSelections(projectRoot) {
308
+ return {
276
309
  projectName: path.basename(projectRoot),
277
310
  description: '',
278
311
  projectTypes: [],
279
312
  languages: [],
280
313
  useDocker: false,
281
314
  selectedAgents: [],
315
+ generatePluginJson: false,
316
+ scaffoldGtdMemory: false,
282
317
  };
318
+ }
319
+
320
+ async function runAllSteps(selections) {
321
+ selections = await runProjectInfo(selections);
322
+ selections = await runProjectType(selections);
323
+ selections = await runTechStack(selections);
324
+ selections = await runAgents(selections);
325
+ selections = await runOptionalExtras(selections);
326
+ return selections;
327
+ }
328
+
329
+ async function runInteractivePrompts(projectRoot) {
330
+ let selections = createInitialSelections(projectRoot);
283
331
 
284
332
  let confirmed = false;
285
333
  let firstRun = true;
286
334
 
287
335
  while (!confirmed) {
288
336
  if (firstRun) {
289
- selections = await runProjectInfo(selections);
290
- selections = await runProjectType(selections);
291
- selections = await runTechStack(selections);
292
- selections = await runAgents(selections);
337
+ selections = await runAllSteps(selections);
293
338
  firstRun = false;
294
339
  }
295
340
 
@@ -298,21 +343,11 @@ async function runInteractivePrompts(projectRoot) {
298
343
  if (confirmation === 'yes') {
299
344
  confirmed = true;
300
345
  } else if (confirmation === 'restart') {
301
- selections = {
302
- projectName: path.basename(projectRoot),
303
- description: '',
304
- projectTypes: [],
305
- languages: [],
306
- useDocker: false,
307
- selectedAgents: [],
308
- };
346
+ selections = createInitialSelections(projectRoot);
309
347
  display.newline();
310
348
  display.info('Starting over...');
311
349
  display.newline();
312
- selections = await runProjectInfo(selections);
313
- selections = await runProjectType(selections);
314
- selections = await runTechStack(selections);
315
- selections = await runAgents(selections);
350
+ selections = await runAllSteps(selections);
316
351
  } else if (confirmation === 'adjust') {
317
352
  const { step } = await inquirer.prompt([
318
353
  {
@@ -363,6 +398,10 @@ function buildTemplateVariables(selections) {
363
398
  );
364
399
  const skillsText = skillsLines.join('\n');
365
400
 
401
+ const memoryArchitectureExtras = selections.scaffoldGtdMemory
402
+ ? '\n- Team decisions: `docs/memory/decisions.md` (version-controlled, shared).\n- Team preferences: `docs/memory/preferences.md` (version-controlled, shared).'
403
+ : '';
404
+
366
405
  return {
367
406
  project_name: projectName,
368
407
  description: description || 'A project scaffolded with Worclaude',
@@ -372,6 +411,7 @@ function buildTemplateVariables(selections) {
372
411
  docker_row: dockerRow,
373
412
  commands_filled_during_init: commandsText,
374
413
  project_specific_skills: skillsText,
414
+ memory_architecture_extras: memoryArchitectureExtras,
375
415
  timestamp: new Date().toISOString(),
376
416
  };
377
417
  }
@@ -401,6 +441,9 @@ async function scaffoldFresh(projectRoot, selections, variables, settingsStr, ve
401
441
  await scaffoldFile('core/claude-md.md', 'CLAUDE.md', variables, projectRoot);
402
442
  spinner.text = 'Created CLAUDE.md';
403
443
 
444
+ await scaffoldFile('core/agents-md.md', 'AGENTS.md', variables, projectRoot);
445
+ spinner.text = 'Created AGENTS.md';
446
+
404
447
  await writeFile(path.join(projectRoot, '.claude', 'settings.json'), settingsStr);
405
448
  spinner.text = 'Created .claude/settings.json';
406
449
 
@@ -500,6 +543,26 @@ async function scaffoldFresh(projectRoot, selections, variables, settingsStr, ve
500
543
  await writeFile(path.join(projectRoot, '.claude', 'sessions', '.gitkeep'), '');
501
544
  spinner.text = 'Created .claude/sessions/';
502
545
 
546
+ // Copy hook scripts (.claude/hooks/)
547
+ await scaffoldHooks(projectRoot);
548
+ spinner.text = 'Created .claude/hooks/';
549
+
550
+ // Create learnings directory for correction capture
551
+ await writeFile(path.join(projectRoot, '.claude', 'learnings', '.gitkeep'), '');
552
+ spinner.text = 'Created .claude/learnings/';
553
+
554
+ // Opt-in: plugin.json for Claude Code marketplace compatibility
555
+ if (selections.generatePluginJson) {
556
+ await scaffoldPluginJson(projectRoot, selections);
557
+ spinner.text = 'Created .claude-plugin/plugin.json';
558
+ }
559
+
560
+ // Opt-in: GTD memory scaffold (docs/memory/decisions.md, preferences.md)
561
+ if (selections.scaffoldGtdMemory) {
562
+ await scaffoldMemoryDocs(projectRoot);
563
+ spinner.text = 'Created docs/memory/';
564
+ }
565
+
503
566
  await computeAndWriteWorkflowMeta(projectRoot, selections, version);
504
567
  spinner.text = 'Created .claude/workflow-meta.json';
505
568
 
@@ -518,12 +581,20 @@ function displayFreshSuccess(selections, skipped) {
518
581
 
519
582
  display.newline();
520
583
  display.success('CLAUDE.md');
584
+ display.success('AGENTS.md');
521
585
  display.success('.claude/settings.json');
522
586
  display.success('.claude/workflow-meta.json');
523
587
  display.success(`.claude/agents/${display.dimColor(` ${totalAgents} agents`)}`);
524
588
  display.success(`.claude/commands/${display.dimColor(` ${COMMAND_FILES.length} commands`)}`);
525
589
  display.success(`.claude/skills/${display.dimColor(` ${totalSkills} skills`)}`);
526
590
  display.success('.claude/sessions/');
591
+ display.success('.claude/hooks/');
592
+ if (selections.generatePluginJson) {
593
+ display.success('.claude-plugin/plugin.json');
594
+ }
595
+ if (selections.scaffoldGtdMemory) {
596
+ display.success('docs/memory/' + display.dimColor(' decisions.md, preferences.md'));
597
+ }
527
598
  display.success('.mcp.json');
528
599
  display.success('.gitignore');
529
600
  if (skipped.progressMd) {
@@ -648,6 +719,19 @@ function displayMergeReport(report, backupPath) {
648
719
  display.success('CLAUDE.md created');
649
720
  }
650
721
 
722
+ // Memory Architecture Tier 3 notice
723
+ if (report.memoryArchitectureSectionExists) {
724
+ display.newline();
725
+ display.info(
726
+ 'docs/memory/ scaffolded, but your CLAUDE.md already has a Memory Architecture section.'
727
+ );
728
+ display.dim(' Add these lines manually if you want pointer bullets:');
729
+ display.dim(' - Team decisions: `docs/memory/decisions.md` (version-controlled, shared).');
730
+ display.dim(
731
+ ' - Team preferences: `docs/memory/preferences.md` (version-controlled, shared).'
732
+ );
733
+ }
734
+
651
735
  // Skipped
652
736
  if (report.skipped.progressMd) {
653
737
  display.dim(' docs/spec/PROGRESS.md — already exists, skipped');
@@ -19,8 +19,31 @@ export async function detectScenario(projectRoot) {
19
19
  }
20
20
 
21
21
  export async function scanExistingSetup(projectRoot) {
22
- const hasClaudeDir = await dirExists(path.join(projectRoot, '.claude'));
23
- const hasClaudeMd = await fileExists(path.join(projectRoot, 'CLAUDE.md'));
22
+ const [
23
+ hasClaudeDir,
24
+ hasClaudeMd,
25
+ hasSettingsJson,
26
+ hasMcpJson,
27
+ existingSkills,
28
+ existingSkillDirs,
29
+ existingAgents,
30
+ existingCommands,
31
+ hasAgentsMd,
32
+ hasProgressMd,
33
+ hasSpecMd,
34
+ ] = await Promise.all([
35
+ dirExists(path.join(projectRoot, '.claude')),
36
+ fileExists(path.join(projectRoot, 'CLAUDE.md')),
37
+ fileExists(path.join(projectRoot, '.claude', 'settings.json')),
38
+ fileExists(path.join(projectRoot, '.mcp.json')),
39
+ listFiles(path.join(projectRoot, '.claude', 'skills')),
40
+ listSkillDirs(path.join(projectRoot, '.claude', 'skills')),
41
+ listFiles(path.join(projectRoot, '.claude', 'agents')),
42
+ listFiles(path.join(projectRoot, '.claude', 'commands')),
43
+ fileExists(path.join(projectRoot, 'AGENTS.md')),
44
+ fileExists(path.join(projectRoot, 'docs', 'spec', 'PROGRESS.md')),
45
+ fileExists(path.join(projectRoot, 'docs', 'spec', 'SPEC.md')),
46
+ ]);
24
47
 
25
48
  let claudeMdLineCount = 0;
26
49
  if (hasClaudeMd) {
@@ -32,13 +55,14 @@ export async function scanExistingSetup(projectRoot) {
32
55
  hasClaudeDir,
33
56
  hasClaudeMd,
34
57
  claudeMdLineCount,
35
- hasSettingsJson: await fileExists(path.join(projectRoot, '.claude', 'settings.json')),
36
- hasMcpJson: await fileExists(path.join(projectRoot, '.mcp.json')),
37
- existingSkills: await listFiles(path.join(projectRoot, '.claude', 'skills')),
38
- existingSkillDirs: await listSkillDirs(path.join(projectRoot, '.claude', 'skills')),
39
- existingAgents: await listFiles(path.join(projectRoot, '.claude', 'agents')),
40
- existingCommands: await listFiles(path.join(projectRoot, '.claude', 'commands')),
41
- hasProgressMd: await fileExists(path.join(projectRoot, 'docs', 'spec', 'PROGRESS.md')),
42
- hasSpecMd: await fileExists(path.join(projectRoot, 'docs', 'spec', 'SPEC.md')),
58
+ hasSettingsJson,
59
+ hasMcpJson,
60
+ existingSkills,
61
+ existingSkillDirs,
62
+ existingAgents,
63
+ existingCommands,
64
+ hasAgentsMd,
65
+ hasProgressMd,
66
+ hasSpecMd,
43
67
  };
44
68
  }
@@ -1,7 +1,15 @@
1
1
  import path from 'node:path';
2
2
  import os from 'node:os';
3
3
  import { readFile, writeFile } from '../utils/file.js';
4
- import { readTemplate, substituteVariables, scaffoldFile, mergeSettings } from './scaffolder.js';
4
+ import {
5
+ readTemplate,
6
+ substituteVariables,
7
+ scaffoldFile,
8
+ mergeSettings,
9
+ scaffoldHooks,
10
+ scaffoldPluginJson,
11
+ scaffoldMemoryDocs,
12
+ } from './scaffolder.js';
5
13
  import { promptHookConflict } from '../prompts/conflict-resolution.js';
6
14
  import {
7
15
  detectMissingSections,
@@ -374,7 +382,7 @@ async function mergeDocSpecs(projectRoot, existingScan, variables, selections, r
374
382
  report.skipped.specMd = existingScan.hasSpecMd;
375
383
  }
376
384
 
377
- async function handleClaudeMd(projectRoot, existingScan, variables, report) {
385
+ async function handleClaudeMd(projectRoot, existingScan, variables, selections, report) {
378
386
  if (!existingScan.hasClaudeMd) {
379
387
  // No CLAUDE.md — scaffold fresh
380
388
  await scaffoldFile('core/claude-md.md', 'CLAUDE.md', variables, projectRoot);
@@ -386,6 +394,13 @@ async function handleClaudeMd(projectRoot, existingScan, variables, report) {
386
394
  const renderedTemplate = substituteVariables(await readTemplate('core/claude-md.md'), variables);
387
395
  const missingSections = detectMissingSections(existingContent);
388
396
 
397
+ // Tier 3 notice: if user opted into GTD memory AND their CLAUDE.md already has
398
+ // a Memory Architecture section, the pointer bullets from the rendered template
399
+ // won't be merged in automatically. Surface this so the user can add them manually.
400
+ if (selections.scaffoldGtdMemory && !missingSections.includes('Memory Architecture')) {
401
+ report.memoryArchitectureSectionExists = true;
402
+ }
403
+
389
404
  if (missingSections.length === 0) {
390
405
  display.newline();
391
406
  display.info(`Your CLAUDE.md (${existingContent.split(/\r?\n/).length} lines) was detected.`);
@@ -411,6 +426,16 @@ async function handleClaudeMd(projectRoot, existingScan, variables, report) {
411
426
  }
412
427
  }
413
428
 
429
+ async function mergeAgentsMd(projectRoot, existingScan, variables, report) {
430
+ if (!existingScan.hasAgentsMd) {
431
+ await scaffoldFile('core/agents-md.md', 'AGENTS.md', variables, projectRoot);
432
+ report.agentsMdHandling = 'created';
433
+ } else {
434
+ await scaffoldFile('core/agents-md.md', 'AGENTS.md.workflow-ref', variables, projectRoot);
435
+ report.agentsMdHandling = 'saved-alongside';
436
+ }
437
+ }
438
+
414
439
  // --- Main merge function ---
415
440
 
416
441
  export async function performMerge(
@@ -425,6 +450,7 @@ export async function performMerge(
425
450
  conflicts: { skills: [], agents: [], commands: [] },
426
451
  skipped: { progressMd: false, specMd: false },
427
452
  claudeMdHandling: 'kept',
453
+ agentsMdHandling: 'kept',
428
454
  hookConflicts: [],
429
455
  };
430
456
 
@@ -442,11 +468,30 @@ export async function performMerge(
442
468
  // Ensure sessions directory exists for session persistence
443
469
  await writeFile(path.join(projectRoot, '.claude', 'sessions', '.gitkeep'), '');
444
470
 
471
+ // Copy hook scripts (preserves existing user modifications)
472
+ await scaffoldHooks(projectRoot);
473
+
474
+ // Create learnings directory for correction capture
475
+ await writeFile(path.join(projectRoot, '.claude', 'learnings', '.gitkeep'), '');
476
+
477
+ // Opt-in: plugin.json (idempotent — scaffolder skips if file exists)
478
+ if (selections.generatePluginJson) {
479
+ await scaffoldPluginJson(projectRoot, selections);
480
+ }
481
+
482
+ // Opt-in: GTD memory scaffold (idempotent per-file). Tier 3 notice for
483
+ // existing Memory Architecture section is handled inside handleClaudeMd,
484
+ // which already reads CLAUDE.md and runs section detection.
485
+ if (selections.scaffoldGtdMemory) {
486
+ await scaffoldMemoryDocs(projectRoot);
487
+ }
488
+
445
489
  await mergeDocSpecs(projectRoot, existingScan, variables, selections, report);
446
490
 
447
491
  // Stop spinner before CLAUDE.md merge — interactive prompts for section selection
448
492
  if (spinner) spinner.stop();
449
- await handleClaudeMd(projectRoot, existingScan, variables, report);
493
+ await handleClaudeMd(projectRoot, existingScan, variables, selections, report);
494
+ await mergeAgentsMd(projectRoot, existingScan, variables, report);
450
495
  if (spinner) spinner.start();
451
496
 
452
497
  return report;
@@ -1,7 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import fs from 'fs-extra';
4
- import { readFile, writeFile } from '../utils/file.js';
4
+ import { fileExists, readFile, writeFile } from '../utils/file.js';
5
+ import { UNIVERSAL_AGENTS } from '../data/agents.js';
5
6
 
6
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
8
 
@@ -52,6 +53,7 @@ export async function updateGitignore(projectDir) {
52
53
  '.claude/workflow-meta.json',
53
54
  '.claude/worktrees/',
54
55
  '.claude-backup-*/',
56
+ '.claude/learnings/',
55
57
  ];
56
58
  const header = '# Worclaude (generated workflow files)';
57
59
 
@@ -86,6 +88,61 @@ export async function updateGitignore(projectDir) {
86
88
  return true;
87
89
  }
88
90
 
91
+ export async function scaffoldHooks(projectRoot) {
92
+ const hooksTemplateDir = path.join(getTemplatesDir(), 'hooks');
93
+ const destDir = path.join(projectRoot, '.claude', 'hooks');
94
+ await fs.ensureDir(destDir);
95
+
96
+ const entries = await fs.readdir(hooksTemplateDir);
97
+ for (const entry of entries) {
98
+ if (!entry.endsWith('.cjs') && !entry.endsWith('.js')) continue;
99
+ await fs.copy(path.join(hooksTemplateDir, entry), path.join(destDir, entry), {
100
+ overwrite: false,
101
+ errorOnExist: false,
102
+ });
103
+ }
104
+ }
105
+
106
+ export function slugifyPluginName(projectName) {
107
+ const slug = String(projectName || '')
108
+ .toLowerCase()
109
+ .replace(/[^a-z0-9]+/g, '-')
110
+ .replace(/^-+|-+$/g, '');
111
+ return slug || 'worclaude-plugin';
112
+ }
113
+
114
+ export async function scaffoldPluginJson(projectRoot, selections) {
115
+ const destPath = path.join(projectRoot, '.claude-plugin', 'plugin.json');
116
+ if (await fileExists(destPath)) return;
117
+
118
+ const agents = [...UNIVERSAL_AGENTS, ...(selections.selectedAgents || [])].map(
119
+ (a) => `./.claude/agents/${a}.md`
120
+ );
121
+
122
+ const plugin = {
123
+ name: `${slugifyPluginName(selections.projectName)}-workflow`,
124
+ version: '0.1.0',
125
+ description: selections.description || `Claude Code workflow for ${selections.projectName}`,
126
+ keywords: ['claude-code', 'workflow', 'worclaude'],
127
+ agents,
128
+ skills: ['./.claude/skills/'],
129
+ commands: ['./.claude/commands/'],
130
+ };
131
+
132
+ await writeFile(destPath, JSON.stringify(plugin, null, 2) + '\n');
133
+ }
134
+
135
+ export async function scaffoldMemoryDocs(projectRoot) {
136
+ const destDir = path.join(projectRoot, 'docs', 'memory');
137
+ await fs.ensureDir(destDir);
138
+ for (const file of ['decisions.md', 'preferences.md']) {
139
+ const destPath = path.join(destDir, file);
140
+ if (await fileExists(destPath)) continue;
141
+ const content = await readTemplate(`memory/${file}`);
142
+ await writeFile(destPath, content);
143
+ }
144
+ }
145
+
89
146
  export function mergeSettings(base, ...stacks) {
90
147
  const merged = JSON.parse(JSON.stringify(base));
91
148
  const baseAllow = merged.permissions?.allow || [];
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Agent routing metadata for all 25 agents.
2
+ * Agent routing metadata for every agent in the registry.
3
3
  * Used by the agent-routing generator to produce the routing skill file.
4
4
  * Separate from agents.js because this data is only consumed by the generator,
5
5
  * not by CLI prompts or display logic.
6
6
  */
7
7
  export const AGENT_REGISTRY = {
8
- // --- Universal agents (5) ---
8
+ // --- Universal agents ---
9
9
 
10
10
  'plan-reviewer': {
11
11
  category: 'universal',
@@ -73,6 +73,21 @@ export const AGENT_REGISTRY = {
73
73
  expectBack: 'Detailed verification report. Blocking issues listed.',
74
74
  situationLabel: 'Finished a task, ready for PR',
75
75
  },
76
+ 'upstream-watcher': {
77
+ category: 'universal',
78
+ model: 'Sonnet',
79
+ isolation: 'none',
80
+ pipelineStage: 'Stage 1: Context',
81
+ triggerType: 'manual',
82
+ triggerCommand: '/upstream-check',
83
+ whenToUse:
84
+ 'At the start of a session to check for upstream Claude Code changes. After Claude Code updates. When behavior seems different from last session.',
85
+ whatItDoes:
86
+ "Fetches anthropic-watch feeds, cross-references upstream changes against the project's scaffolded agents/commands/hooks/skills, and produces an impact report.",
87
+ expectBack:
88
+ 'Impact report: which upstream changes affect this project, which are informational, and recommended actions.',
89
+ situationLabel: 'Want to check for upstream Claude Code changes',
90
+ },
76
91
 
77
92
  // --- Frontend agents (2) ---
78
93
 
@@ -4,6 +4,7 @@ export const UNIVERSAL_AGENTS = [
4
4
  'test-writer',
5
5
  'build-validator',
6
6
  'verify-app',
7
+ 'upstream-watcher',
7
8
  ];
8
9
 
9
10
  export const AGENT_CATALOG = {
@@ -194,6 +195,8 @@ export const COMMAND_FILES = [
194
195
  'build-fix',
195
196
  'refactor-clean',
196
197
  'test-coverage',
198
+ 'learn',
199
+ 'upstream-check',
197
200
  ];
198
201
 
199
202
  export const UNIVERSAL_SKILLS = [
@@ -205,6 +208,7 @@ export const UNIVERSAL_SKILLS = [
205
208
  'verification',
206
209
  'testing',
207
210
  'claude-md-maintenance',
211
+ 'coding-principles',
208
212
  'subagent-usage',
209
213
  'security-checklist',
210
214
  'coordinator-mode',
@@ -216,6 +220,8 @@ export const TEMPLATE_SKILLS = [
216
220
  'project-patterns',
217
221
  ];
218
222
 
223
+ export const HOOK_FILES = ['pre-compact-save', 'correction-detect', 'learn-capture', 'skill-hint'];
224
+
219
225
  export const PROJECT_TYPES = [
220
226
  'Full-stack web application',
221
227
  'Backend / API',
@@ -315,6 +321,7 @@ export const CONFIRMATION_STEPS = [
315
321
  { name: 'Project type', value: 'projectType' },
316
322
  { name: 'Tech stack', value: 'techStack' },
317
323
  { name: 'Agent selection', value: 'agents' },
324
+ { name: 'Optional extras (plugin.json, memory)', value: 'optionalExtras' },
318
325
  ];
319
326
 
320
327
  export const NOTIFICATION_COMMANDS = {
package/src/index.js CHANGED
@@ -56,6 +56,7 @@ program
56
56
  program
57
57
  .command('doctor')
58
58
  .description('Validate workflow installation health')
59
- .action(doctorCommand);
59
+ .option('--json', 'Output results as JSON')
60
+ .action((options) => doctorCommand(options));
60
61
 
61
62
  program.parse();
@@ -28,6 +28,11 @@ const SECTION_DETECTORS = [
28
28
  heading: '## Gotchas',
29
29
  test: (c) => /Gotchas/i.test(c),
30
30
  },
31
+ {
32
+ name: 'Memory Architecture',
33
+ heading: '## Memory Architecture',
34
+ test: (c) => /Memory Architecture/i.test(c) || /\.claude\/learnings\//i.test(c),
35
+ },
31
36
  ];
32
37
 
33
38
  export function detectMissingSections(existingContent) {
@@ -48,6 +48,29 @@ End with a clear verdict:
48
48
  - **WARNINGS**: Non-blocking issues (formatting, deprecation warnings) — can commit with caution
49
49
  - **BLOCKED**: Tests fail or build broken — must fix before committing
50
50
 
51
+ ## Severity Classification
52
+
53
+ Classify each finding:
54
+
55
+ | Severity | Meaning | Action |
56
+ |----------|---------|--------|
57
+ | CRITICAL | Build fails, tests fail, app won't start | BLOCKED — must fix |
58
+ | WARNING | Deprecation warnings, formatting issues, lint warnings | Can commit with caution |
59
+ | INFO | Performance suggestions, optional improvements | Note for later |
60
+
61
+ Report CRITICAL issues first. If no CRITICAL but some WARNINGS, verdict is WARNINGS.
62
+
63
+ ## Common False Positives
64
+
65
+ Do not report these as failures:
66
+ - **Optional peer dependencies** missing — only a failure if the feature is used
67
+ - **Platform-specific warnings** — warnings for platforms not targeted
68
+ - **Dev dependency deprecation** — note but don't block
69
+ - **Test timeouts in CI vs local** — environmental, not bugs
70
+ - **Snapshot updates needed** — expected after intentional visual changes
71
+
72
+ When in doubt, report as INFO, not CRITICAL.
73
+
51
74
  ## Rules
52
75
  - Run checks in the listed order — if build fails, still run the rest
53
76
  - Report ALL failures, not just the first one
@@ -65,6 +65,17 @@ After completing improvements, provide a summary:
65
65
  | 2 | src/api.js | Early return for null check | Reduced nesting from 4→2 levels |
66
66
  | 3 | src/config.js | Removed 12 unused imports | Dead code |
67
67
 
68
+ ## When NOT to Simplify
69
+
70
+ Do not refactor:
71
+ - **Hot paths** with performance-sensitive code — profiling data overrules readability
72
+ - **Stable legacy code** that works and has no active bugs — risk exceeds benefit
73
+ - **Framework boilerplate** — generated patterns that look verbose but are idiomatic
74
+ - **Security-critical code** — verbose explicit validation is safer than clever abstraction
75
+ - **Code you don't fully understand** — if you can't explain what it does, don't touch it
76
+
77
+ When in doubt: leave it. A working system is more valuable than a clean system.
78
+
68
79
  ## Rules
69
80
  - Never change behavior — if tests break, you changed behavior, revert
70
81
  - Never refactor code you don't understand — read the full context first
@@ -74,6 +74,45 @@ Structure your review as:
74
74
 
75
75
  **What's Good** (1-2 sentences — acknowledge strengths briefly):
76
76
 
77
+ ## Confidence Threshold
78
+
79
+ Only flag issues you are >80% confident are real problems.
80
+ If uncertain, phrase it as a question, not a finding.
81
+ "This might cause issues with X" wastes the planner's time.
82
+ "Will this handle concurrent requests? I see no locking in step 3." is actionable.
83
+
84
+ ## Must-Haves Verification
85
+
86
+ Every plan must define must-haves:
87
+ - **Truths**: Observable behaviors that must be true when done
88
+ - **Artifacts**: Files that must exist with real implementation
89
+ - **Key Links**: Connections between artifacts (imports, routes, configs)
90
+
91
+ If must-haves are missing, verdict is NEEDS REVISION regardless of other quality.
92
+
93
+ ## Bounded Revision
94
+
95
+ Maximum 3 revision cycles between reviewer and author:
96
+ - Round 1: All issues surfaced. Author addresses them.
97
+ - Round 2: Verify fixes, catch anything missed. Author addresses.
98
+ - Round 3: Final check. If issues remain, escalate to user.
99
+
100
+ If critical issues do not decrease between rounds, stop reviewing
101
+ and escalate: "This plan needs human architectural input because..."
102
+
103
+ ## Worked Example
104
+
105
+ **Plan:** "Add password reset feature"
106
+ **Step 1:** "Create reset endpoint in src/routes/auth.js"
107
+
108
+ **Review finding:** NEEDS REVISION
109
+ - Missing: exact HTTP method and path (POST /api/auth/reset-password?)
110
+ - Missing: what parameters does the request body accept?
111
+ - Missing: verification step — how do you confirm the endpoint works?
112
+ - Missing: how does the reset token get sent to the user?
113
+
114
+ This level of specificity is what you're checking for.
115
+
77
116
  ## Review Principles
78
117
  - Be direct — flag problems, suggest solutions, don't hedge
79
118
  - Be specific — "this could fail" is useless; "step 3 will fail if the users table has existing rows because of the NOT NULL constraint" is actionable