@hivehub/rulebook 3.4.2 → 4.1.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 (173) hide show
  1. package/.claude/commands/continue.md +33 -0
  2. package/.claude-plugin/marketplace.json +28 -29
  3. package/.claude-plugin/plugin.json +8 -8
  4. package/README.md +66 -144
  5. package/dist/agents/ralph-parser.d.ts +41 -1
  6. package/dist/agents/ralph-parser.d.ts.map +1 -1
  7. package/dist/agents/ralph-parser.js +202 -14
  8. package/dist/agents/ralph-parser.js.map +1 -1
  9. package/dist/cli/commands.d.ts +65 -0
  10. package/dist/cli/commands.d.ts.map +1 -1
  11. package/dist/cli/commands.js +837 -61
  12. package/dist/cli/commands.js.map +1 -1
  13. package/dist/core/agent-manager.d.ts +7 -0
  14. package/dist/core/agent-manager.d.ts.map +1 -1
  15. package/dist/core/agent-manager.js +150 -3
  16. package/dist/core/agent-manager.js.map +1 -1
  17. package/dist/core/claude-mcp.d.ts +17 -0
  18. package/dist/core/claude-mcp.d.ts.map +1 -1
  19. package/dist/core/claude-mcp.js +90 -6
  20. package/dist/core/claude-mcp.js.map +1 -1
  21. package/dist/core/config-manager.d.ts.map +1 -1
  22. package/dist/core/config-manager.js +40 -0
  23. package/dist/core/config-manager.js.map +1 -1
  24. package/dist/core/cursor-mdc-generator.d.ts +30 -0
  25. package/dist/core/cursor-mdc-generator.d.ts.map +1 -0
  26. package/dist/core/cursor-mdc-generator.js +98 -0
  27. package/dist/core/cursor-mdc-generator.js.map +1 -0
  28. package/dist/core/detector.d.ts +25 -1
  29. package/dist/core/detector.d.ts.map +1 -1
  30. package/dist/core/detector.js +321 -1
  31. package/dist/core/detector.js.map +1 -1
  32. package/dist/core/generator.d.ts +10 -0
  33. package/dist/core/generator.d.ts.map +1 -1
  34. package/dist/core/generator.js +177 -3
  35. package/dist/core/generator.js.map +1 -1
  36. package/dist/core/github-issues-importer.d.ts +82 -0
  37. package/dist/core/github-issues-importer.d.ts.map +1 -0
  38. package/dist/core/github-issues-importer.js +161 -0
  39. package/dist/core/github-issues-importer.js.map +1 -0
  40. package/dist/core/health-scorer.d.ts +39 -0
  41. package/dist/core/health-scorer.d.ts.map +1 -1
  42. package/dist/core/health-scorer.js +256 -13
  43. package/dist/core/health-scorer.js.map +1 -1
  44. package/dist/core/indexer/background-indexer.d.ts +27 -0
  45. package/dist/core/indexer/background-indexer.d.ts.map +1 -0
  46. package/dist/core/indexer/background-indexer.js +135 -0
  47. package/dist/core/indexer/background-indexer.js.map +1 -0
  48. package/dist/core/indexer/file-parser.d.ts +28 -0
  49. package/dist/core/indexer/file-parser.d.ts.map +1 -0
  50. package/dist/core/indexer/file-parser.js +171 -0
  51. package/dist/core/indexer/file-parser.js.map +1 -0
  52. package/dist/core/indexer/indexer-types.d.ts +35 -0
  53. package/dist/core/indexer/indexer-types.d.ts.map +1 -0
  54. package/dist/core/indexer/indexer-types.js +8 -0
  55. package/dist/core/indexer/indexer-types.js.map +1 -0
  56. package/dist/core/iteration-tracker.d.ts +28 -0
  57. package/dist/core/iteration-tracker.d.ts.map +1 -1
  58. package/dist/core/iteration-tracker.js +86 -0
  59. package/dist/core/iteration-tracker.js.map +1 -1
  60. package/dist/core/multi-tool-generator.d.ts +59 -0
  61. package/dist/core/multi-tool-generator.d.ts.map +1 -0
  62. package/dist/core/multi-tool-generator.js +157 -0
  63. package/dist/core/multi-tool-generator.js.map +1 -0
  64. package/dist/core/override-manager.d.ts +23 -0
  65. package/dist/core/override-manager.d.ts.map +1 -0
  66. package/dist/core/override-manager.js +82 -0
  67. package/dist/core/override-manager.js.map +1 -0
  68. package/dist/core/plans-manager.d.ts +46 -0
  69. package/dist/core/plans-manager.d.ts.map +1 -0
  70. package/dist/core/plans-manager.js +158 -0
  71. package/dist/core/plans-manager.js.map +1 -0
  72. package/dist/core/prd-generator.d.ts +12 -0
  73. package/dist/core/prd-generator.d.ts.map +1 -1
  74. package/dist/core/prd-generator.js +91 -2
  75. package/dist/core/prd-generator.js.map +1 -1
  76. package/dist/core/ralph-manager.d.ts +47 -1
  77. package/dist/core/ralph-manager.d.ts.map +1 -1
  78. package/dist/core/ralph-manager.js +107 -0
  79. package/dist/core/ralph-manager.js.map +1 -1
  80. package/dist/core/ralph-parallel.d.ts +55 -0
  81. package/dist/core/ralph-parallel.d.ts.map +1 -0
  82. package/dist/core/ralph-parallel.js +201 -0
  83. package/dist/core/ralph-parallel.js.map +1 -0
  84. package/dist/core/ralph-plan-checkpoint.d.ts +58 -0
  85. package/dist/core/ralph-plan-checkpoint.d.ts.map +1 -0
  86. package/dist/core/ralph-plan-checkpoint.js +154 -0
  87. package/dist/core/ralph-plan-checkpoint.js.map +1 -0
  88. package/dist/core/ralph-scripts.d.ts +12 -0
  89. package/dist/core/ralph-scripts.d.ts.map +1 -0
  90. package/dist/core/ralph-scripts.js +49 -0
  91. package/dist/core/ralph-scripts.js.map +1 -0
  92. package/dist/core/review-manager.d.ts +74 -0
  93. package/dist/core/review-manager.d.ts.map +1 -0
  94. package/dist/core/review-manager.js +371 -0
  95. package/dist/core/review-manager.js.map +1 -0
  96. package/dist/index.js +94 -2
  97. package/dist/index.js.map +1 -1
  98. package/dist/mcp/rulebook-server.d.ts.map +1 -1
  99. package/dist/mcp/rulebook-server.js +117 -8
  100. package/dist/mcp/rulebook-server.js.map +1 -1
  101. package/dist/memory/memory-manager.d.ts +4 -1
  102. package/dist/memory/memory-manager.d.ts.map +1 -1
  103. package/dist/memory/memory-manager.js +33 -4
  104. package/dist/memory/memory-manager.js.map +1 -1
  105. package/dist/memory/memory-search.d.ts +2 -2
  106. package/dist/memory/memory-search.d.ts.map +1 -1
  107. package/dist/memory/memory-search.js +19 -0
  108. package/dist/memory/memory-search.js.map +1 -1
  109. package/dist/memory/memory-store.d.ts +13 -0
  110. package/dist/memory/memory-store.d.ts.map +1 -1
  111. package/dist/memory/memory-store.js +92 -1
  112. package/dist/memory/memory-store.js.map +1 -1
  113. package/dist/memory/memory-types.d.ts +15 -0
  114. package/dist/memory/memory-types.d.ts.map +1 -1
  115. package/dist/types.d.ts +55 -2
  116. package/dist/types.d.ts.map +1 -1
  117. package/package.json +1 -1
  118. package/templates/agents/implementer.md +35 -0
  119. package/templates/agents/researcher.md +34 -0
  120. package/templates/agents/team-lead.md +34 -0
  121. package/templates/agents/tester.md +42 -0
  122. package/templates/ci/rulebook-review.yml +26 -0
  123. package/templates/commands/rulebook-task-archive.md +24 -0
  124. package/templates/core/AGENTS_LEAN.md +25 -0
  125. package/templates/core/AGENTS_OVERRIDE.md +16 -0
  126. package/templates/core/MULTI_AGENT.md +74 -0
  127. package/templates/core/PLANS.md +28 -0
  128. package/templates/core/RALPH.md +45 -4
  129. package/templates/core/RULEBOOK.md +42 -0
  130. package/templates/ides/CONTINUE_RULES.md +16 -0
  131. package/templates/ides/COPILOT_INSTRUCTIONS.md +23 -0
  132. package/templates/ides/GEMINI_RULES.md +17 -0
  133. package/templates/ides/WINDSURF_RULES.md +14 -0
  134. package/templates/ides/cursor-mdc/go.mdc +24 -0
  135. package/templates/ides/cursor-mdc/python.mdc +24 -0
  136. package/templates/ides/cursor-mdc/quality.mdc +25 -0
  137. package/templates/ides/cursor-mdc/ralph.mdc +39 -0
  138. package/templates/ides/cursor-mdc/rulebook.mdc +38 -0
  139. package/templates/ides/cursor-mdc/rust.mdc +24 -0
  140. package/templates/ides/cursor-mdc/typescript.mdc +25 -0
  141. package/templates/modules/sequential-thinking.md +42 -0
  142. package/templates/ralph/ralph-history.bat +4 -0
  143. package/templates/ralph/ralph-history.sh +5 -0
  144. package/templates/ralph/ralph-init.bat +5 -0
  145. package/templates/ralph/ralph-init.sh +5 -0
  146. package/templates/ralph/ralph-pause.bat +5 -0
  147. package/templates/ralph/ralph-pause.sh +5 -0
  148. package/templates/ralph/ralph-run.bat +5 -0
  149. package/templates/ralph/ralph-run.sh +5 -0
  150. package/templates/ralph/ralph-status.bat +4 -0
  151. package/templates/ralph/ralph-status.sh +5 -0
  152. package/templates/services/DATADOG.md +26 -0
  153. package/templates/services/DOCKER.md +124 -0
  154. package/templates/services/DOCKER_COMPOSE.md +168 -0
  155. package/templates/services/HELM.md +194 -0
  156. package/templates/services/KUBERNETES.md +208 -0
  157. package/templates/services/OPENTELEMETRY.md +25 -0
  158. package/templates/services/PINO.md +24 -0
  159. package/templates/services/PROMETHEUS.md +33 -0
  160. package/templates/services/SENTRY.md +23 -0
  161. package/templates/services/WINSTON.md +30 -0
  162. package/dist/core/openspec-manager.d.ts +0 -133
  163. package/dist/core/openspec-manager.d.ts.map +0 -1
  164. package/dist/core/openspec-manager.js +0 -596
  165. package/dist/core/openspec-manager.js.map +0 -1
  166. package/dist/core/openspec-migrator.d.ts +0 -27
  167. package/dist/core/openspec-migrator.d.ts.map +0 -1
  168. package/dist/core/openspec-migrator.js +0 -262
  169. package/dist/core/openspec-migrator.js.map +0 -1
  170. package/dist/core/test-task-manager.d.ts +0 -49
  171. package/dist/core/test-task-manager.d.ts.map +0 -1
  172. package/dist/core/test-task-manager.js +0 -121
  173. package/dist/core/test-task-manager.js.map +0 -1
@@ -1,11 +1,10 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { detectProject } from '../core/detector.js';
4
- import { promptProjectConfig, promptSimplifiedConfig, promptMergeStrategy } from './prompts.js';
5
4
  import { generateFullAgents } from '../core/generator.js';
6
5
  import { mergeFullAgents } from '../core/merger.js';
7
6
  import { generateWorkflows, generateIDEFiles, generateAICLIFiles, } from '../core/workflow-generator.js';
8
- import { writeFile, createBackup } from '../utils/file-system.js';
7
+ import { writeFile, createBackup, ensureDir } from '../utils/file-system.js';
9
8
  import { existsSync } from 'fs';
10
9
  import { parseRulesIgnore } from '../utils/rulesignore.js';
11
10
  import { installGitHooks } from '../utils/git-hooks.js';
@@ -67,6 +66,24 @@ export async function initCommand(options) {
67
66
  console.log(` - ${module.module} (${module.source})`);
68
67
  }
69
68
  }
69
+ // Show monorepo detection
70
+ if (detection.monorepo?.detected) {
71
+ const mono = detection.monorepo;
72
+ console.log(chalk.green(`\n✓ Monorepo detected: ${chalk.bold(mono.tool ?? 'manual')}`));
73
+ if (mono.packages.length > 0) {
74
+ console.log(` Packages (${mono.packages.length}): ${mono.packages.slice(0, 5).join(', ')}${mono.packages.length > 5 ? ` +${mono.packages.length - 5} more` : ''}`);
75
+ }
76
+ if (options.package) {
77
+ console.log(chalk.cyan(` → Initializing package: ${options.package}`));
78
+ }
79
+ }
80
+ // Recommend sequential-thinking MCP if not detected
81
+ const seqThinking = detection.modules.find((m) => m.module === 'sequential_thinking');
82
+ if (seqThinking && !seqThinking.detected) {
83
+ console.log(chalk.yellow('\n💡 Tip: Install sequential-thinking MCP for structured problem solving:\n' +
84
+ ' npx @modelcontextprotocol/create-server sequential-thinking\n' +
85
+ ' or add to .mcp.json: { "mcpServers": { "sequential-thinking": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"] } } }'));
86
+ }
70
87
  const detectedFrameworks = detection.frameworks.filter((f) => f.detected);
71
88
  if (detectedFrameworks.length > 0) {
72
89
  console.log(chalk.green('\n✓ Detected frameworks:'));
@@ -83,45 +100,27 @@ export async function initCommand(options) {
83
100
  console.log(` - ${block.name}`);
84
101
  }
85
102
  }
86
- // Get project configuration
87
- let config;
103
+ // Get project configuration — auto-setup from detection, no prompts
88
104
  const cliMinimal = Boolean(options.minimal);
89
105
  const cliLight = Boolean(options.light);
90
- const cliQuick = Boolean(options.quick);
91
- if (options.yes) {
92
- // Full auto mode - no prompts at all
93
- config = {
94
- languages: detection.languages.map((l) => l.language),
95
- modules: cliMinimal ? [] : detection.modules.filter((m) => m.detected).map((m) => m.module),
96
- frameworks: detection.frameworks.filter((f) => f.detected).map((f) => f.framework),
97
- ides: cliMinimal ? [] : ['cursor'],
98
- projectType: 'application',
99
- coverageThreshold: 95,
100
- strictDocs: true,
101
- generateWorkflows: true,
102
- includeGitWorkflow: true,
103
- gitPushMode: 'manual',
104
- installGitHooks: false,
105
- minimal: cliMinimal,
106
- lightMode: cliLight,
107
- modular: true, // Enable modular /rulebook directory by default
108
- };
109
- console.log(chalk.blue('\nUsing detected defaults...'));
110
- }
111
- else if (cliQuick) {
112
- // Quick mode - minimal prompts (language, MCP, hooks only)
113
- config = await promptSimplifiedConfig(detection);
114
- config.lightMode = cliLight;
115
- config.minimal = cliMinimal;
116
- }
117
- else {
118
- // Full interactive mode
119
- console.log('');
120
- config = await promptProjectConfig(detection, {
121
- defaultMode: cliMinimal ? 'minimal' : 'full',
122
- });
123
- config.lightMode = cliLight;
124
- }
106
+ const cliLean = Boolean(options.lean);
107
+ const config = {
108
+ languages: detection.languages.map((l) => l.language),
109
+ modules: cliMinimal ? [] : detection.modules.filter((m) => m.detected).map((m) => m.module),
110
+ frameworks: detection.frameworks.filter((f) => f.detected).map((f) => f.framework),
111
+ ides: cliMinimal ? [] : ['cursor'],
112
+ projectType: 'application',
113
+ coverageThreshold: 95,
114
+ strictDocs: true,
115
+ generateWorkflows: true,
116
+ includeGitWorkflow: true,
117
+ gitPushMode: 'manual',
118
+ installGitHooks: false,
119
+ minimal: cliMinimal,
120
+ lightMode: cliLight,
121
+ modular: true,
122
+ };
123
+ console.log(chalk.blue('\nAuto-configuring from detection results...'));
125
124
  const minimalMode = config.minimal ?? cliMinimal;
126
125
  config.minimal = minimalMode;
127
126
  config.modules = minimalMode ? [] : config.modules || [];
@@ -130,6 +129,9 @@ export async function initCommand(options) {
130
129
  config.includeGitWorkflow = config.includeGitWorkflow ?? true;
131
130
  config.generateWorkflows = config.generateWorkflows ?? true;
132
131
  config.modular = config.modular ?? true; // Enable modular by default
132
+ if (cliLean) {
133
+ config.agentsMode = 'lean';
134
+ }
133
135
  let minimalArtifacts = [];
134
136
  if (minimalMode) {
135
137
  minimalArtifacts = await scaffoldMinimalProject(cwd, {
@@ -183,6 +185,8 @@ export async function initCommand(options) {
183
185
  const dirMigrationSpinner = ora('Migrating directory structure...').start();
184
186
  await configManager.migrateDirectoryStructure(cwd);
185
187
  dirMigrationSpinner.succeed('Directory structure migrated');
188
+ // Ensure .rulebook/memory/ directory exists for per-project memory persistence
189
+ await ensureDir(path.join(cwd, '.rulebook', 'memory'));
186
190
  // Ensure .gitignore has .rulebook entries (keep specs/ and tasks/ tracked)
187
191
  await configManager.ensureGitignore();
188
192
  // Auto-detect and enable skills based on project detection (v2.0)
@@ -217,10 +221,19 @@ export async function initCommand(options) {
217
221
  services: config.services,
218
222
  modular: config.modular ?? true,
219
223
  rulebookDir: config.rulebookDir || '.rulebook',
224
+ ...(config.agentsMode ? { agentsMode: config.agentsMode } : {}),
220
225
  skills: enabledSkills.length > 0 ? { enabled: enabledSkills } : undefined,
221
226
  ralph: existingConfig.ralph,
222
227
  memory: existingConfig.memory,
223
228
  });
229
+ // --package: generate only the specified package's AGENTS.md and exit
230
+ if (options.package) {
231
+ const packageRoot = path.join(cwd, options.package);
232
+ const { generatePackageAgentsMd } = await import('../core/generator.js');
233
+ await generatePackageAgentsMd(packageRoot, config, cwd);
234
+ console.log(chalk.green(`\n✅ AGENTS.md generated for package: ${options.package}`));
235
+ return;
236
+ }
224
237
  // Generate or merge AGENTS.md
225
238
  const agentsPath = path.join(cwd, 'AGENTS.md');
226
239
  let finalContent;
@@ -236,22 +249,11 @@ export async function initCommand(options) {
236
249
  }
237
250
  }
238
251
  }
239
- const strategy = options.yes ? 'merge' : await promptMergeStrategy();
240
- if (strategy === 'merge') {
241
- const mergeSpinner = ora('Merging with existing AGENTS.md...').start();
242
- finalContent = await mergeFullAgents(detection.existingAgents, config, cwd);
243
- // Create backup
244
- const backupPath = await createBackup(agentsPath);
245
- mergeSpinner.succeed(`Backup created: ${path.basename(backupPath)}`);
246
- }
247
- else {
248
- const backupSpinner = ora('Creating backup...').start();
249
- const backupPath = await createBackup(agentsPath);
250
- backupSpinner.succeed(`Backup created: ${path.basename(backupPath)}`);
251
- const genSpinner = ora('Generating new AGENTS.md...').start();
252
- finalContent = await generateFullAgents(config, cwd);
253
- genSpinner.succeed('AGENTS.md generated');
254
- }
252
+ // Always merge preserve existing customizations
253
+ const mergeSpinner = ora('Merging with existing AGENTS.md...').start();
254
+ finalContent = await mergeFullAgents(detection.existingAgents, config, cwd);
255
+ const backupPath = await createBackup(agentsPath);
256
+ mergeSpinner.succeed(`Backup created: ${path.basename(backupPath)}`);
255
257
  }
256
258
  else {
257
259
  const genSpinner = ora('Generating AGENTS.md...').start();
@@ -261,6 +263,28 @@ export async function initCommand(options) {
261
263
  // Write AGENTS.md
262
264
  await writeFile(agentsPath, finalContent);
263
265
  console.log(chalk.green(`\n✅ AGENTS.md written to ${agentsPath}`));
266
+ // Show Cursor MDC feedback
267
+ if (detection.cursor?.detected) {
268
+ if (detection.cursor.hasMdcRules) {
269
+ console.log(chalk.gray(' • Cursor .mdc rules updated in .cursor/rules/'));
270
+ }
271
+ else {
272
+ console.log(chalk.gray(' • Cursor .mdc rules generated in .cursor/rules/'));
273
+ }
274
+ }
275
+ // Show multi-tool config feedback
276
+ if (detection.geminiCli?.detected) {
277
+ console.log(chalk.gray(' • Gemini CLI config generated: GEMINI.md'));
278
+ }
279
+ if (detection.continueDev?.detected) {
280
+ console.log(chalk.gray(' • Continue.dev rules generated in .continue/rules/'));
281
+ }
282
+ if (detection.windsurf?.detected) {
283
+ console.log(chalk.gray(' • Windsurf rules generated: .windsurfrules'));
284
+ }
285
+ if (detection.githubCopilot?.detected) {
286
+ console.log(chalk.gray(' • GitHub Copilot instructions generated in .github/'));
287
+ }
264
288
  // Generate workflows if requested
265
289
  if (config.generateWorkflows) {
266
290
  const workflowSpinner = ora('Generating GitHub Actions workflows...').start();
@@ -327,6 +351,12 @@ export async function initCommand(options) {
327
351
  if (result.skillsInstalled.length > 0) {
328
352
  console.log(chalk.gray(` • ${result.skillsInstalled.length} skills installed to .claude/commands/`));
329
353
  }
354
+ if (result.agentTeamsEnabled) {
355
+ console.log(chalk.gray(' • Multi-agent teams enabled in .claude/settings.json'));
356
+ }
357
+ if (result.agentDefinitionsInstalled.length > 0) {
358
+ console.log(chalk.gray(` • ${result.agentDefinitionsInstalled.length} agent definitions installed to .claude/agents/`));
359
+ }
330
360
  }
331
361
  else {
332
362
  claudeSpinner.info('Claude Code not detected (skipped)');
@@ -336,6 +366,49 @@ export async function initCommand(options) {
336
366
  claudeSpinner.info('Claude Code integration skipped');
337
367
  }
338
368
  }
369
+ // Install Ralph shell scripts
370
+ try {
371
+ const { installRalphScripts } = await import('../core/ralph-scripts.js');
372
+ const scripts = await installRalphScripts(cwd);
373
+ if (scripts.length > 0) {
374
+ console.log(chalk.gray(` • ${scripts.length} Ralph scripts installed to .rulebook/scripts/`));
375
+ }
376
+ }
377
+ catch {
378
+ // Skip if Ralph scripts installation fails
379
+ }
380
+ // Create PLANS.md for session continuity
381
+ try {
382
+ const { initPlans } = await import('../core/plans-manager.js');
383
+ const created = await initPlans(cwd);
384
+ if (created) {
385
+ console.log(chalk.gray(' • PLANS.md created for session continuity'));
386
+ }
387
+ }
388
+ catch {
389
+ // Non-blocking
390
+ }
391
+ // Create AGENTS.override.md (never overwrites existing)
392
+ try {
393
+ const { initOverride } = await import('../core/override-manager.js');
394
+ const created = await initOverride(cwd);
395
+ if (created) {
396
+ console.log(chalk.gray(' • AGENTS.override.md created (add project-specific rules here)'));
397
+ }
398
+ }
399
+ catch {
400
+ // Non-blocking
401
+ }
402
+ // --add-sequential-thinking: inject into mcp.json if not already present
403
+ if (options.addSequentialThinking) {
404
+ try {
405
+ await addSequentialThinkingMcp(cwd);
406
+ console.log(chalk.gray(' • sequential-thinking MCP added to mcp.json'));
407
+ }
408
+ catch {
409
+ console.log(chalk.yellow(' ⚠ Could not add sequential-thinking MCP'));
410
+ }
411
+ }
339
412
  if (minimalMode && minimalArtifacts.length > 0) {
340
413
  console.log(chalk.green('\n✅ Essentials created:'));
341
414
  for (const artifact of minimalArtifacts) {
@@ -652,13 +725,12 @@ export async function healthCommand() {
652
725
  try {
653
726
  const cwd = process.cwd();
654
727
  console.log(chalk.bold.blue('\n🏥 Project Health Check\n'));
655
- const { calculateHealthScore, getHealthGrade } = await import('../core/health-scorer.js');
728
+ const { calculateHealthScore } = await import('../core/health-scorer.js');
656
729
  const spinner = ora('Analyzing project health...').start();
657
730
  const health = await calculateHealthScore(cwd);
658
731
  spinner.succeed('Health analysis complete');
659
732
  console.log('');
660
- const grade = getHealthGrade(health.overall);
661
- console.log(chalk.bold(`Overall Health Score: ${health.overall}/100 (${grade})`));
733
+ console.log(chalk.bold(`Overall Health Score: ${health.overall}/100 (${health.grade})`));
662
734
  console.log('');
663
735
  console.log(chalk.bold('Category Scores:\n'));
664
736
  console.log(` 📝 Documentation: ${health.categories.documentation}/100`);
@@ -667,6 +739,9 @@ export async function healthCommand() {
667
739
  console.log(` 🔒 Security: ${health.categories.security}/100`);
668
740
  console.log(` 🔄 CI/CD: ${health.categories.cicd}/100`);
669
741
  console.log(` 📦 Dependencies: ${health.categories.dependencies}/100`);
742
+ console.log(` 🤖 AGENTS.md: ${health.categories.agentsMd}/100`);
743
+ console.log(` 🔁 Ralph: ${health.categories.ralph}/100`);
744
+ console.log(` 🧠 Memory: ${health.categories.memory}/100`);
670
745
  console.log('');
671
746
  if (health.recommendations.length > 0) {
672
747
  console.log(chalk.bold.yellow('Recommendations:\n'));
@@ -1188,6 +1263,7 @@ export async function updateCommand(options) {
1188
1263
  }
1189
1264
  const minimalMode = options.minimal ?? existingMode === 'minimal';
1190
1265
  const lightMode = options.light !== undefined ? options.light : (existingLightMode ?? false);
1266
+ const leanMode = options.lean ?? existingConfig?.agentsMode === 'lean';
1191
1267
  // Build config from detected project
1192
1268
  const config = {
1193
1269
  languages: detection.languages.map((l) => l.language),
@@ -1203,6 +1279,7 @@ export async function updateCommand(options) {
1203
1279
  installGitHooks: installHooksOnUpdate,
1204
1280
  minimal: minimalMode,
1205
1281
  lightMode: lightMode,
1282
+ ...(leanMode ? { agentsMode: 'lean' } : {}),
1206
1283
  };
1207
1284
  if (minimalMode) {
1208
1285
  config.ides = [];
@@ -1221,6 +1298,10 @@ export async function updateCommand(options) {
1221
1298
  const cursorRulesPath = path.join(cwd, '.cursorrules');
1222
1299
  const cursorCommandsDir = path.join(cwd, '.cursor', 'commands');
1223
1300
  const usesCursor = existsSync(cursorRulesPath) || existsSync(cursorCommandsDir);
1301
+ // Deprecated notice: .cursorrules is superseded by .cursor/rules/*.mdc in Cursor v0.45+
1302
+ if (existsSync(cursorRulesPath)) {
1303
+ console.log(chalk.yellow(' ⚠ .cursorrules is deprecated as of Cursor v0.45. Use .cursor/rules/*.mdc instead.'));
1304
+ }
1224
1305
  if (usesCursor) {
1225
1306
  // Check if commands already exist to avoid duplicate generation
1226
1307
  const existingCommandsDir = path.join(cwd, '.cursor', 'commands');
@@ -1314,6 +1395,19 @@ export async function updateCommand(options) {
1314
1395
  const mergedContent = await mergeFullAgents(detection.existingAgents, config, cwd);
1315
1396
  await writeFile(agentsPath, mergedContent);
1316
1397
  mergeSpinner.succeed('AGENTS.md updated');
1398
+ // Show multi-tool config feedback (update command)
1399
+ if (detection.geminiCli?.detected) {
1400
+ console.log(chalk.gray(' • Gemini CLI config updated: GEMINI.md'));
1401
+ }
1402
+ if (detection.continueDev?.detected) {
1403
+ console.log(chalk.gray(' • Continue.dev rules updated in .continue/rules/'));
1404
+ }
1405
+ if (detection.windsurf?.detected) {
1406
+ console.log(chalk.gray(' • Windsurf rules updated: .windsurfrules'));
1407
+ }
1408
+ if (detection.githubCopilot?.detected) {
1409
+ console.log(chalk.gray(' • GitHub Copilot instructions updated in .github/'));
1410
+ }
1317
1411
  if (installHooksOnUpdate) {
1318
1412
  const hookLanguages = detection.languages.length > 0
1319
1413
  ? detection.languages
@@ -1375,6 +1469,11 @@ export async function updateCommand(options) {
1375
1469
  ...(existingConfig.memory ? { memory: existingConfig.memory } : {}),
1376
1470
  ...(existingConfig.ralph ? { ralph: existingConfig.ralph } : {}),
1377
1471
  ...(existingConfig.skills ? { skills: existingConfig.skills } : {}),
1472
+ ...(leanMode
1473
+ ? { agentsMode: 'lean' }
1474
+ : existingConfig.agentsMode
1475
+ ? { agentsMode: existingConfig.agentsMode }
1476
+ : {}),
1378
1477
  };
1379
1478
  await configManager.saveConfig(rulebookConfig);
1380
1479
  configSpinner.succeed('.rulebook configuration updated');
@@ -1391,6 +1490,12 @@ export async function updateCommand(options) {
1391
1490
  if (result.skillsInstalled.length > 0) {
1392
1491
  console.log(chalk.gray(` • ${result.skillsInstalled.length} skills updated in .claude/commands/`));
1393
1492
  }
1493
+ if (result.agentTeamsEnabled) {
1494
+ console.log(chalk.gray(' • Multi-agent teams enabled in .claude/settings.json'));
1495
+ }
1496
+ if (result.agentDefinitionsInstalled.length > 0) {
1497
+ console.log(chalk.gray(` • ${result.agentDefinitionsInstalled.length} agent definitions updated in .claude/agents/`));
1498
+ }
1394
1499
  }
1395
1500
  else {
1396
1501
  claudeSpinner.info('Claude Code not detected (skipped)');
@@ -1399,6 +1504,25 @@ export async function updateCommand(options) {
1399
1504
  catch {
1400
1505
  claudeSpinner.info('Claude Code integration skipped');
1401
1506
  }
1507
+ // Install/update Ralph shell scripts
1508
+ try {
1509
+ const { installRalphScripts } = await import('../core/ralph-scripts.js');
1510
+ const scripts = await installRalphScripts(cwd);
1511
+ if (scripts.length > 0) {
1512
+ console.log(chalk.gray(` • ${scripts.length} Ralph scripts updated in .rulebook/scripts/`));
1513
+ }
1514
+ }
1515
+ catch {
1516
+ // Skip if Ralph scripts installation fails
1517
+ }
1518
+ // Ensure PLANS.md exists (create if missing, never overwrite)
1519
+ try {
1520
+ const { initPlans } = await import('../core/plans-manager.js');
1521
+ await initPlans(cwd);
1522
+ }
1523
+ catch {
1524
+ // Non-blocking
1525
+ }
1402
1526
  // Migrate memory directory if old structure exists
1403
1527
  try {
1404
1528
  await migrateMemoryDirectory();
@@ -1879,6 +2003,58 @@ export async function memoryStatsCommand() {
1879
2003
  process.exit(1);
1880
2004
  }
1881
2005
  }
2006
+ export async function memoryVerifyCommand() {
2007
+ const ora = (await import('ora')).default;
2008
+ const chalk = (await import('chalk')).default;
2009
+ const spinner = ora('Verifying memory system...').start();
2010
+ try {
2011
+ const { createConfigManager } = await import('../core/config-manager.js');
2012
+ const cwd = process.cwd();
2013
+ const configManager = createConfigManager(cwd);
2014
+ const config = await configManager.loadConfig();
2015
+ const memoryEnabled = config.memory?.enabled ?? false;
2016
+ const dbPathRelative = config.memory?.dbPath ?? '.rulebook/memory/memory.db';
2017
+ const dbPathAbsolute = path.join(cwd, dbPathRelative);
2018
+ spinner.succeed('Memory verification');
2019
+ // Check enabled status
2020
+ console.log(`\n ${memoryEnabled ? chalk.green('✓') : chalk.red('✗')} Memory enabled: ${memoryEnabled}`);
2021
+ // Check DB path
2022
+ console.log(` ${chalk.green('✓')} DB path: ${dbPathRelative}`);
2023
+ // Check if file exists on disk
2024
+ const fileExists = existsSync(dbPathAbsolute);
2025
+ if (fileExists) {
2026
+ const { statSync } = await import('fs');
2027
+ const fileStat = statSync(dbPathAbsolute);
2028
+ const sizeKB = (fileStat.size / 1024).toFixed(1);
2029
+ console.log(` ${chalk.green('✓')} File exists: YES (${sizeKB} KB)`);
2030
+ }
2031
+ else {
2032
+ console.log(` ${chalk.red('✗')} File exists: NO`);
2033
+ }
2034
+ // If memory is enabled and file exists, show record count
2035
+ if (memoryEnabled && fileExists) {
2036
+ try {
2037
+ const { createMemoryManager } = await import('../memory/memory-manager.js');
2038
+ const manager = createMemoryManager(cwd, config.memory);
2039
+ const stats = await manager.getStats();
2040
+ console.log(` ${chalk.green('✓')} Record count: ${stats.memoryCount} memories`);
2041
+ await manager.close();
2042
+ }
2043
+ catch (error) {
2044
+ console.log(` ${chalk.yellow('!')} Record count: unable to read (${String(error)})`);
2045
+ }
2046
+ }
2047
+ else if (!memoryEnabled) {
2048
+ console.log(` ${chalk.yellow('!')} Enable memory with: ${chalk.bold('rulebook config --feature memory --enable')}`);
2049
+ }
2050
+ console.log('');
2051
+ }
2052
+ catch (error) {
2053
+ spinner.fail('Memory verification failed');
2054
+ console.error(chalk.red(String(error)));
2055
+ process.exit(1);
2056
+ }
2057
+ }
1882
2058
  export async function memoryCleanupCommand(options) {
1883
2059
  const ora = (await import('ora')).default;
1884
2060
  const chalk = (await import('chalk')).default;
@@ -1993,6 +2169,7 @@ export async function ralphRunCommand(options) {
1993
2169
  const { RalphManager } = await import('../core/ralph-manager.js');
1994
2170
  const { RalphParser } = await import('../agents/ralph-parser.js');
1995
2171
  const { createConfigManager } = await import('../core/config-manager.js');
2172
+ const { IterationTracker } = await import('../core/iteration-tracker.js');
1996
2173
  const childProcess = await import('child_process');
1997
2174
  const logger = new Logger(cwd);
1998
2175
  const configManager = createConfigManager(cwd);
@@ -2000,6 +2177,22 @@ export async function ralphRunCommand(options) {
2000
2177
  const ralphManager = new RalphManager(cwd, logger);
2001
2178
  const maxIterations = options.maxIterations || config.ralph?.maxIterations || 10;
2002
2179
  const tool = options.tool || config.ralph?.tool || 'claude';
2180
+ // Resolve parallel mode — CLI flag takes precedence over config
2181
+ const parallelWorkers = options.parallel ??
2182
+ (config.ralph?.parallel?.enabled ? config.ralph.parallel.maxWorkers : undefined);
2183
+ // Resolve plan checkpoint config — --plan-first CLI flag takes precedence
2184
+ const planCheckpointConfig = {
2185
+ enabled: options.planFirst ?? config.ralph?.planCheckpoint?.enabled ?? false,
2186
+ autoApproveAfterSeconds: config.ralph?.planCheckpoint?.autoApproveAfterSeconds ?? 0,
2187
+ requireApprovalForStories: config.ralph?.planCheckpoint?.requireApprovalForStories ?? 'all',
2188
+ };
2189
+ // Context compression config
2190
+ const compressionConfig = config.ralph?.contextCompression;
2191
+ const compressionEnabled = compressionConfig?.enabled !== false;
2192
+ const compressionRecentCount = compressionConfig?.recentCount ?? 3;
2193
+ const compressionThreshold = compressionConfig?.threshold ?? 5;
2194
+ const iterationTracker = new IterationTracker(cwd, logger);
2195
+ await iterationTracker.initialize();
2003
2196
  await ralphManager.initialize(maxIterations, tool);
2004
2197
  // Create git branch from PRD
2005
2198
  const prd = await ralphManager.loadPRD();
@@ -2016,6 +2209,115 @@ export async function ralphRunCommand(options) {
2016
2209
  process.on('SIGINT', handleInterrupt);
2017
2210
  // Sync task count from PRD (may have been saved after initialize)
2018
2211
  await ralphManager.refreshTaskCount();
2212
+ // ─── Parallel execution mode ───
2213
+ if (parallelWorkers && parallelWorkers > 1) {
2214
+ spinner.text = `Ralph parallel mode (${parallelWorkers} workers)...`;
2215
+ const batches = await ralphManager.getParallelBatches(parallelWorkers);
2216
+ spinner.stop();
2217
+ console.log(chalk.bold.cyan(`\n Parallel mode: ${batches.length} batch(es), max ${parallelWorkers} workers\n`));
2218
+ let iterationCount = 0;
2219
+ for (const batch of batches) {
2220
+ if (interrupted)
2221
+ break;
2222
+ console.log(chalk.bold(` ── Batch: ${batch.map((s) => s.id).join(', ')} (${batch.length} stories) ──`));
2223
+ // Run all stories in the batch concurrently
2224
+ const batchResults = await Promise.allSettled(batch.map(async (task) => {
2225
+ iterationCount++;
2226
+ const localIteration = iterationCount;
2227
+ const startTime = Date.now();
2228
+ // Build context (shared — read-only)
2229
+ let contextHistory = '';
2230
+ if (compressionEnabled) {
2231
+ contextHistory = await iterationTracker.buildCompressedContext(compressionRecentCount, compressionThreshold);
2232
+ }
2233
+ let plansContext = '';
2234
+ try {
2235
+ const { readPlans, plansExists } = await import('../core/plans-manager.js');
2236
+ if (plansExists(cwd)) {
2237
+ const plans = await readPlans(cwd);
2238
+ if (plans?.context && plans.context.trim()) {
2239
+ plansContext = plans.context.trim();
2240
+ }
2241
+ }
2242
+ }
2243
+ catch {
2244
+ // PLANS.md injection is optional
2245
+ }
2246
+ const prompt = ralphBuildPrompt(task, prd, contextHistory, plansContext);
2247
+ let agentOutput = '';
2248
+ try {
2249
+ agentOutput = await ralphExecuteAgent(tool, prompt, cwd, childProcess.spawn);
2250
+ }
2251
+ catch (agentError) {
2252
+ agentOutput = `Error executing agent: ${agentError.message || agentError}`;
2253
+ }
2254
+ const qualityResults = await ralphRunQualityGates(cwd, childProcess.spawn);
2255
+ const executionTime = Date.now() - startTime;
2256
+ const parsed = RalphParser.parseAgentOutput(agentOutput, localIteration, task.id, task.title, tool);
2257
+ const allGatesPass = qualityResults.type_check &&
2258
+ qualityResults.lint &&
2259
+ qualityResults.tests &&
2260
+ qualityResults.coverage_met;
2261
+ const passCount = Object.values(qualityResults).filter(Boolean).length;
2262
+ const status = allGatesPass
2263
+ ? 'success'
2264
+ : passCount >= 2
2265
+ ? 'partial'
2266
+ : 'failed';
2267
+ let gitCommit;
2268
+ if (allGatesPass) {
2269
+ gitCommit = await ralphGitCommit(cwd, task, localIteration, childProcess.spawn);
2270
+ await ralphManager.markStoryComplete(task.id);
2271
+ console.log(chalk.green(` [parallel] Story ${task.id} completed`));
2272
+ }
2273
+ else {
2274
+ console.log(chalk.yellow(` [parallel] Story ${task.id} not completed (quality gates failed)`));
2275
+ }
2276
+ const result = {
2277
+ iteration: localIteration,
2278
+ timestamp: new Date().toISOString(),
2279
+ task_id: task.id,
2280
+ task_title: task.title,
2281
+ status,
2282
+ ai_tool: tool,
2283
+ execution_time_ms: executionTime,
2284
+ quality_checks: qualityResults,
2285
+ output_summary: parsed.output_summary || `Iteration ${localIteration}: ${task.title}`,
2286
+ git_commit: gitCommit,
2287
+ learnings: parsed.learnings,
2288
+ errors: parsed.errors,
2289
+ metadata: {
2290
+ context_loss_count: parsed.metadata.context_loss_count,
2291
+ parsed_completion: parsed.metadata.parsed_completion,
2292
+ },
2293
+ };
2294
+ await ralphManager.recordIteration(result);
2295
+ return result;
2296
+ }));
2297
+ // Log rejected promises
2298
+ for (const [i, result] of batchResults.entries()) {
2299
+ if (result.status === 'rejected') {
2300
+ const story = batch[i];
2301
+ console.log(chalk.red(` [parallel] Story ${story.id} threw: ${result.reason}`));
2302
+ }
2303
+ }
2304
+ // Check for pause
2305
+ const currentStatus = await ralphManager.getStatus();
2306
+ if (currentStatus?.paused)
2307
+ break;
2308
+ }
2309
+ // Cleanup and summary for parallel mode
2310
+ process.removeListener('SIGINT', handleInterrupt);
2311
+ const stats = await ralphManager.getTaskStats();
2312
+ console.log(`\n Parallel run complete: ${stats.completed}/${stats.total} tasks completed`);
2313
+ console.log(` Iterations: ${iterationCount}`);
2314
+ if (interrupted) {
2315
+ console.log(chalk.yellow(` Paused by user. Resume: ${chalk.bold('rulebook ralph resume')}`));
2316
+ }
2317
+ console.log(`\n View history: ${chalk.bold('rulebook ralph history')}\n`);
2318
+ return;
2319
+ }
2320
+ // ─── Sequential execution (default) ───
2019
2321
  spinner.text = 'Ralph loop running (Ctrl+C to pause)...';
2020
2322
  let iterationCount = 0;
2021
2323
  while (ralphManager.canContinue() && !interrupted) {
@@ -2027,8 +2329,38 @@ export async function ralphRunCommand(options) {
2027
2329
  spinner.stop();
2028
2330
  console.log(chalk.bold.cyan(`\n ── Iteration ${iterationCount}: ${task.title} ──\n`));
2029
2331
  const startTime = Date.now();
2332
+ // 0. Plan checkpoint — require approval before implementation
2333
+ if (planCheckpointConfig.enabled) {
2334
+ const checkpoint = await ralphManager.runCheckpoint(task, tool, planCheckpointConfig);
2335
+ if (!checkpoint.proceed) {
2336
+ console.log(chalk.yellow(` Plan rejected for ${task.id}. Skipping implementation.`));
2337
+ if (checkpoint.feedback) {
2338
+ console.log(chalk.gray(` Feedback: ${checkpoint.feedback}`));
2339
+ }
2340
+ spinner.start('Preparing next iteration...');
2341
+ continue;
2342
+ }
2343
+ }
2030
2344
  // 1. Execute AI agent with task context
2031
- const prompt = ralphBuildPrompt(task, prd);
2345
+ let contextHistory = '';
2346
+ if (compressionEnabled) {
2347
+ contextHistory = await iterationTracker.buildCompressedContext(compressionRecentCount, compressionThreshold);
2348
+ }
2349
+ // Read PLANS.md context for session scratchpad injection
2350
+ let plansContext = '';
2351
+ try {
2352
+ const { readPlans, plansExists } = await import('../core/plans-manager.js');
2353
+ if (plansExists(cwd)) {
2354
+ const plans = await readPlans(cwd);
2355
+ if (plans?.context && plans.context.trim()) {
2356
+ plansContext = plans.context.trim();
2357
+ }
2358
+ }
2359
+ }
2360
+ catch {
2361
+ // PLANS.md injection is optional — skip on error
2362
+ }
2363
+ const prompt = ralphBuildPrompt(task, prd, contextHistory, plansContext);
2032
2364
  let agentOutput = '';
2033
2365
  try {
2034
2366
  agentOutput = await ralphExecuteAgent(tool, prompt, cwd, childProcess.spawn);
@@ -2113,11 +2445,15 @@ export async function ralphRunCommand(options) {
2113
2445
  /**
2114
2446
  * Build prompt for AI agent from user story context
2115
2447
  */
2116
- function ralphBuildPrompt(task, prd) {
2448
+ function ralphBuildPrompt(task, prd, contextHistory, plansContext) {
2117
2449
  const criteria = (task.acceptanceCriteria || []).map((c) => `- ${c}`).join('\n');
2118
2450
  return [
2119
2451
  `You are working on project: ${prd?.project || 'unknown'}`,
2120
2452
  ``,
2453
+ plansContext ? `## Session Context (PLANS.md)\n${plansContext}\n` : '',
2454
+ contextHistory && contextHistory !== 'No iteration history available.'
2455
+ ? `## Iteration History\n${contextHistory}\n`
2456
+ : '',
2121
2457
  `## Current Task: ${task.title}`,
2122
2458
  `ID: ${task.id}`,
2123
2459
  ``,
@@ -2218,11 +2554,25 @@ async function ralphRunQualityGates(cwd, spawn) {
2218
2554
  }, 120000);
2219
2555
  });
2220
2556
  };
2557
+ // Detect monorepo to choose the right test command
2558
+ const { detectMonorepo } = await import('../core/detector.js');
2559
+ const monorepo = await detectMonorepo(cwd).catch(() => ({
2560
+ detected: false,
2561
+ tool: null,
2562
+ packages: [],
2563
+ }));
2564
+ let testCmd = ['npm', ['test']];
2565
+ if (monorepo.detected) {
2566
+ if (monorepo.tool === 'turborepo')
2567
+ testCmd = ['turbo', ['run', 'test']];
2568
+ else if (monorepo.tool === 'nx')
2569
+ testCmd = ['nx', ['run-many', '--target=test']];
2570
+ }
2221
2571
  // Run gates in parallel
2222
2572
  const [typeCheck, lint, tests] = await Promise.all([
2223
2573
  runGate('npm', ['run', 'type-check']),
2224
2574
  runGate('npm', ['run', 'lint']),
2225
- runGate('npm', ['test']),
2575
+ runGate(testCmd[0], testCmd[1]),
2226
2576
  ]);
2227
2577
  return {
2228
2578
  type_check: typeCheck,
@@ -2329,12 +2679,18 @@ export async function ralphStatusCommand() {
2329
2679
  return;
2330
2680
  }
2331
2681
  spinner.stop();
2682
+ // Show agentsMode from config
2683
+ const { createConfigManager } = await import('../core/config-manager.js');
2684
+ const configManager = createConfigManager(cwd);
2685
+ const cfg = await configManager.loadConfig();
2686
+ const agentsMode = cfg.agentsMode ?? 'full';
2332
2687
  console.log(`\n ${chalk.bold('Ralph Loop Status')}`);
2333
2688
  console.log(` Iteration: ${status.current_iteration}/${status.max_iterations}`);
2334
2689
  console.log(` Tasks: ${status.completed_tasks}/${status.total_tasks}`);
2335
2690
  console.log(` Status: ${status.paused ? chalk.yellow('PAUSED') : chalk.green('RUNNING')}`);
2336
2691
  console.log(` AI Tool: ${status.tool}`);
2337
2692
  console.log(` Started: ${new Date(status.started_at).toLocaleString()}`);
2693
+ console.log(` Agents Mode: ${agentsMode === 'lean' ? chalk.cyan('lean') : chalk.gray('full')} (rulebook mode set lean|full)`);
2338
2694
  console.log();
2339
2695
  }
2340
2696
  catch (error) {
@@ -2508,6 +2864,302 @@ export async function setupClaudeCodePlugin() {
2508
2864
  process.exit(1);
2509
2865
  }
2510
2866
  }
2867
+ // ─── Plans Commands ────────────────────────────────────────────────────────
2868
+ /**
2869
+ * Add sequential-thinking MCP server entry to mcp.json (or .cursor/mcp.json).
2870
+ * Non-destructive: skips if already present.
2871
+ */
2872
+ async function addSequentialThinkingMcp(cwd) {
2873
+ const { readFileSync, writeFileSync, existsSync } = await import('fs');
2874
+ const { mkdirSync } = await import('fs');
2875
+ const seqEntry = {
2876
+ command: 'npx',
2877
+ args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
2878
+ };
2879
+ const candidates = [
2880
+ path.join(cwd, 'mcp.json'),
2881
+ path.join(cwd, '.cursor', 'mcp.json'),
2882
+ path.join(cwd, '.mcp.json'),
2883
+ ];
2884
+ // Find existing mcp.json or default to mcp.json
2885
+ let mcpPath = candidates.find((p) => existsSync(p)) ?? path.join(cwd, 'mcp.json');
2886
+ let mcpConfig = {};
2887
+ if (existsSync(mcpPath)) {
2888
+ try {
2889
+ mcpConfig = JSON.parse(readFileSync(mcpPath, 'utf8'));
2890
+ }
2891
+ catch {
2892
+ mcpConfig = {};
2893
+ }
2894
+ }
2895
+ mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};
2896
+ // Already configured under any key variant
2897
+ const keys = Object.keys(mcpConfig.mcpServers);
2898
+ const alreadyPresent = keys.some((k) => ['sequential-thinking', 'sequential_thinking', 'sequentialThinking'].includes(k));
2899
+ if (alreadyPresent)
2900
+ return;
2901
+ mcpConfig.mcpServers['sequential-thinking'] = seqEntry;
2902
+ // Ensure directory exists
2903
+ const dir = path.dirname(mcpPath);
2904
+ if (!existsSync(dir))
2905
+ mkdirSync(dir, { recursive: true });
2906
+ writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n');
2907
+ }
2908
+ /**
2909
+ * Show contents of AGENTS.override.md.
2910
+ */
2911
+ export async function overrideShowCommand() {
2912
+ const cwd = process.cwd();
2913
+ const { overrideExists, getOverridePath, readOverrideContent } = await import('../core/override-manager.js');
2914
+ if (!overrideExists(cwd)) {
2915
+ console.log(chalk.yellow('AGENTS.override.md does not exist. Run `rulebook override edit` or `rulebook init` to create it.'));
2916
+ return;
2917
+ }
2918
+ const content = await readOverrideContent(cwd);
2919
+ if (!content) {
2920
+ console.log(chalk.gray('AGENTS.override.md exists but has no custom content yet.'));
2921
+ console.log(chalk.gray(` Path: ${getOverridePath(cwd)}`));
2922
+ return;
2923
+ }
2924
+ console.log(chalk.bold('\n📄 AGENTS.override.md\n'));
2925
+ console.log(content);
2926
+ console.log();
2927
+ }
2928
+ /**
2929
+ * Open AGENTS.override.md in $EDITOR, or print path if no EDITOR.
2930
+ */
2931
+ export async function overrideEditCommand() {
2932
+ const cwd = process.cwd();
2933
+ const { initOverride, getOverridePath } = await import('../core/override-manager.js');
2934
+ await initOverride(cwd); // create if missing
2935
+ const overridePath = getOverridePath(cwd);
2936
+ const editor = process.env.EDITOR || process.env.VISUAL;
2937
+ if (editor) {
2938
+ const { spawn } = await import('child_process');
2939
+ const proc = spawn(editor, [overridePath], { stdio: 'inherit', shell: true });
2940
+ await new Promise((resolve) => proc.on('close', () => resolve()));
2941
+ }
2942
+ else {
2943
+ console.log(chalk.gray(`No $EDITOR set. Edit the file directly:`));
2944
+ console.log(chalk.cyan(` ${overridePath}`));
2945
+ }
2946
+ }
2947
+ /**
2948
+ * Reset AGENTS.override.md to empty template.
2949
+ */
2950
+ export async function overrideClearCommand() {
2951
+ const cwd = process.cwd();
2952
+ const { clearOverride } = await import('../core/override-manager.js');
2953
+ await clearOverride(cwd);
2954
+ console.log(chalk.green('✓ AGENTS.override.md reset to empty template'));
2955
+ }
2956
+ /**
2957
+ * Set the AGENTS.md generation mode (lean or full).
2958
+ */
2959
+ export async function modeSetCommand(mode) {
2960
+ const cwd = process.cwd();
2961
+ const { createConfigManager } = await import('../core/config-manager.js');
2962
+ const configManager = createConfigManager(cwd);
2963
+ const config = await configManager.loadConfig();
2964
+ config.agentsMode = mode;
2965
+ await configManager.saveConfig(config);
2966
+ console.log(chalk.green(`✓ AGENTS.md mode set to: ${chalk.bold(mode)}`));
2967
+ if (mode === 'lean') {
2968
+ console.log(chalk.gray(' Lean mode: AGENTS.md will be a lightweight index (<3KB).\n' +
2969
+ ' Run `rulebook update` to regenerate AGENTS.md.'));
2970
+ }
2971
+ else {
2972
+ console.log(chalk.gray(' Full mode: AGENTS.md will include all rules inline.\n' +
2973
+ ' Run `rulebook update` to regenerate AGENTS.md.'));
2974
+ }
2975
+ }
2976
+ /**
2977
+ * Show current PLANS.md content.
2978
+ */
2979
+ export async function plansShowCommand() {
2980
+ const { readPlans, getPlansPath } = await import('../core/plans-manager.js');
2981
+ const cwd = process.cwd();
2982
+ const plans = await readPlans(cwd);
2983
+ if (!plans) {
2984
+ console.log(chalk.yellow(`No PLANS.md found at ${getPlansPath(cwd)}`));
2985
+ console.log(chalk.gray('Run `rulebook plans init` to create one.'));
2986
+ return;
2987
+ }
2988
+ console.log(chalk.bold.blue('\n📋 PLANS.md — Session Scratchpad\n'));
2989
+ if (plans.context &&
2990
+ plans.context !== '_No active context. Start a session to populate this section._') {
2991
+ console.log(chalk.bold('Active Context:'));
2992
+ console.log(chalk.white(plans.context));
2993
+ }
2994
+ if (plans.currentTask && plans.currentTask !== '_No task in progress._') {
2995
+ console.log(chalk.bold('\nCurrent Task:'));
2996
+ console.log(chalk.cyan(plans.currentTask));
2997
+ }
2998
+ if (plans.history) {
2999
+ console.log(chalk.bold('\nSession History:'));
3000
+ console.log(chalk.gray(plans.history));
3001
+ }
3002
+ console.log('');
3003
+ }
3004
+ /**
3005
+ * Initialize PLANS.md in project root.
3006
+ */
3007
+ export async function plansInitCommand() {
3008
+ const { initPlans, getPlansPath } = await import('../core/plans-manager.js');
3009
+ const cwd = process.cwd();
3010
+ const created = await initPlans(cwd);
3011
+ if (created) {
3012
+ console.log(chalk.green(`✓ Created ${getPlansPath(cwd)}`));
3013
+ console.log(chalk.gray(' AI agents will use this file for session continuity.'));
3014
+ }
3015
+ else {
3016
+ console.log(chalk.yellow(`PLANS.md already exists at ${getPlansPath(cwd)}`));
3017
+ }
3018
+ }
3019
+ /**
3020
+ * Reset PLANS.md to the empty template.
3021
+ */
3022
+ export async function plansClearCommand() {
3023
+ const { clearPlans, getPlansPath } = await import('../core/plans-manager.js');
3024
+ const cwd = process.cwd();
3025
+ await clearPlans(cwd);
3026
+ console.log(chalk.green(`✓ Cleared ${getPlansPath(cwd)}`));
3027
+ console.log(chalk.gray(' Session history and context have been reset.'));
3028
+ }
3029
+ // ─── Continue Command ───────────────────────────────────────────────────────
3030
+ /**
3031
+ * `rulebook continue` — Print orientations to resume work in a new AI session.
3032
+ *
3033
+ * Aggregates context from:
3034
+ * 1. PLANS.md (session scratchpad)
3035
+ * 2. Active rulebook tasks (pending items in tasks.md)
3036
+ * 3. Recent git commits (last 5)
3037
+ * 4. Ralph status (if running)
3038
+ *
3039
+ * Outputs a structured prompt that the AI agent can paste at the start of a session.
3040
+ */
3041
+ export async function continueCommand() {
3042
+ const cwd = process.cwd();
3043
+ const { readPlans } = await import('../core/plans-manager.js');
3044
+ const { exec } = await import('child_process');
3045
+ const { promisify } = await import('util');
3046
+ const execAsync = promisify(exec);
3047
+ const fs = await import('fs/promises');
3048
+ console.log(chalk.bold.blue('\n🔄 Generating session continuity context...\n'));
3049
+ const sections = [];
3050
+ // ── 1. PLANS.md context ──
3051
+ const plans = await readPlans(cwd);
3052
+ if (plans && (plans.context || plans.currentTask)) {
3053
+ const plansParts = ['## Active Plans'];
3054
+ if (plans.context && !plans.context.includes('_No active context')) {
3055
+ plansParts.push(plans.context);
3056
+ }
3057
+ if (plans.currentTask && !plans.currentTask.includes('_No task')) {
3058
+ plansParts.push(`**Current Task:** ${plans.currentTask}`);
3059
+ }
3060
+ sections.push(plansParts.join('\n'));
3061
+ }
3062
+ // ── 2. Active tasks (pending checklist items) ──
3063
+ const tasksDir = path.join(cwd, '.rulebook', 'tasks');
3064
+ if (existsSync(tasksDir)) {
3065
+ const taskSummaries = [];
3066
+ try {
3067
+ const entries = await fs.readdir(tasksDir, { withFileTypes: true });
3068
+ for (const entry of entries) {
3069
+ if (!entry.isDirectory() || entry.name === 'archive')
3070
+ continue;
3071
+ const tasksPath = path.join(tasksDir, entry.name, 'tasks.md');
3072
+ if (!existsSync(tasksPath))
3073
+ continue;
3074
+ const content = await fs.readFile(tasksPath, 'utf-8');
3075
+ const pending = content
3076
+ .split('\n')
3077
+ .filter((l) => l.match(/^- \[ \]/))
3078
+ .map((l) => l.replace(/^- \[ \]\s*/, '').trim())
3079
+ .slice(0, 3);
3080
+ if (pending.length > 0) {
3081
+ taskSummaries.push(`**${entry.name}** (${pending.length}+ pending):`);
3082
+ pending.forEach((p) => taskSummaries.push(` - ${p}`));
3083
+ }
3084
+ }
3085
+ }
3086
+ catch {
3087
+ // ignore
3088
+ }
3089
+ if (taskSummaries.length > 0) {
3090
+ sections.push('## Pending Tasks\n' + taskSummaries.join('\n'));
3091
+ }
3092
+ }
3093
+ // ── 3. Recent git commits ──
3094
+ try {
3095
+ const { stdout } = await execAsync('git log --oneline -8', { cwd });
3096
+ if (stdout.trim()) {
3097
+ sections.push('## Recent Commits\n```\n' + stdout.trim() + '\n```');
3098
+ }
3099
+ }
3100
+ catch {
3101
+ // not a git repo or git not available
3102
+ }
3103
+ // ── 4. Ralph status ──
3104
+ const ralphStatePath = path.join(cwd, '.rulebook', 'ralph', 'state.json');
3105
+ if (existsSync(ralphStatePath)) {
3106
+ try {
3107
+ const state = JSON.parse(await fs.readFile(ralphStatePath, 'utf-8'));
3108
+ if (state.enabled) {
3109
+ const prdPath = path.join(cwd, '.rulebook', 'ralph', 'prd.json');
3110
+ let prdInfo = '';
3111
+ if (existsSync(prdPath)) {
3112
+ const prd = JSON.parse(await fs.readFile(prdPath, 'utf-8'));
3113
+ const pending = (prd.userStories ?? []).filter((s) => !s.passes).length;
3114
+ const total = (prd.userStories ?? []).length;
3115
+ prdInfo = ` | ${total - pending}/${total} stories complete`;
3116
+ }
3117
+ sections.push(`## Ralph Status\n` +
3118
+ `Iteration ${state.current_iteration}/${state.max_iterations}${prdInfo} | Tool: ${state.tool} | Paused: ${state.paused}`);
3119
+ }
3120
+ }
3121
+ catch {
3122
+ // ignore
3123
+ }
3124
+ }
3125
+ // ── 5. Current branch ──
3126
+ try {
3127
+ const { stdout } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd });
3128
+ const branch = stdout.trim();
3129
+ if (branch) {
3130
+ sections.unshift(`## Branch\n\`${branch}\``);
3131
+ }
3132
+ }
3133
+ catch {
3134
+ // ignore
3135
+ }
3136
+ // ── Render ──
3137
+ if (sections.length === 0) {
3138
+ console.log(chalk.yellow('No session context found. Create tasks, a PLANS.md, or make some commits.'));
3139
+ return;
3140
+ }
3141
+ const output = [
3142
+ '─'.repeat(60),
3143
+ chalk.bold('📋 SESSION CONTINUITY CONTEXT'),
3144
+ chalk.gray('Paste this at the start of a new AI session:'),
3145
+ '─'.repeat(60),
3146
+ '',
3147
+ sections.join('\n\n'),
3148
+ '',
3149
+ '─'.repeat(60),
3150
+ ].join('\n');
3151
+ console.log(output);
3152
+ // Also write to PLANS.md if it exists
3153
+ if (plans !== null) {
3154
+ const { appendPlansHistory } = await import('../core/plans-manager.js');
3155
+ try {
3156
+ await appendPlansHistory(cwd, `Session context generated. Branch: current. Pending tasks summarized.`);
3157
+ }
3158
+ catch {
3159
+ // non-critical
3160
+ }
3161
+ }
3162
+ }
2511
3163
  export async function migrateMemoryDirectory() {
2512
3164
  const oraModule = await import('ora');
2513
3165
  const ora = oraModule.default;
@@ -2560,4 +3212,128 @@ export async function migrateMemoryDirectory() {
2560
3212
  process.exit(1);
2561
3213
  }
2562
3214
  }
3215
+ // ─── Review Command ─────────────────────────────────────────────────────────
3216
+ /**
3217
+ * `rulebook review` — Run AI-powered code review on changes vs a base branch.
3218
+ *
3219
+ * Retrieves the git diff, builds a structured prompt, sends it to an AI tool,
3220
+ * parses the result, and outputs in the requested format.
3221
+ */
3222
+ export async function reviewCommand(options) {
3223
+ const cwd = process.cwd();
3224
+ const baseBranch = options.baseBranch ?? 'main';
3225
+ const outputFormat = options.output ?? 'terminal';
3226
+ const tool = (options.tool ?? 'claude');
3227
+ const { getDiffContext, buildReviewPrompt, runAIReview, parseReviewOutput, formatReviewTerminal, postGitHubComment, readAgentsMd, hasFailingIssues, } = await import('../core/review-manager.js');
3228
+ // 1. Get diff
3229
+ const diff = await getDiffContext(cwd, baseBranch);
3230
+ if (!diff) {
3231
+ console.log(chalk.yellow(`No changes detected vs ${baseBranch}`));
3232
+ return;
3233
+ }
3234
+ // 2. Read AGENTS.md (optional context)
3235
+ const agentsMdContent = await readAgentsMd(cwd);
3236
+ // 3. Build prompt
3237
+ const projectName = path.basename(cwd);
3238
+ const prompt = buildReviewPrompt(diff, { agentsMdContent, projectName });
3239
+ // 4. Run AI review
3240
+ const spinner = ora('Running AI review...').start();
3241
+ const rawOutput = await runAIReview(prompt, tool);
3242
+ if (!rawOutput) {
3243
+ spinner.fail('AI review returned no output. Is the AI tool installed and configured?');
3244
+ process.exit(1);
3245
+ }
3246
+ spinner.succeed('AI review complete');
3247
+ // 5. Parse result
3248
+ const result = parseReviewOutput(rawOutput);
3249
+ // 6. Output
3250
+ switch (outputFormat) {
3251
+ case 'terminal':
3252
+ console.log(formatReviewTerminal(result));
3253
+ break;
3254
+ case 'json':
3255
+ console.log(JSON.stringify(result, null, 2));
3256
+ break;
3257
+ case 'github-comment':
3258
+ try {
3259
+ await postGitHubComment(result);
3260
+ console.log(chalk.green('Review posted as PR comment'));
3261
+ }
3262
+ catch (error) {
3263
+ console.error(chalk.red(`Failed to post comment: ${error}`));
3264
+ process.exit(1);
3265
+ }
3266
+ break;
3267
+ }
3268
+ // 7. Exit code
3269
+ if (options.failOn && hasFailingIssues(result.issues, options.failOn)) {
3270
+ console.log(chalk.red(`\nFailing: found issues at or above "${options.failOn}" severity`));
3271
+ process.exit(1);
3272
+ }
3273
+ }
3274
+ // ─── Ralph Import Issues Command ───
3275
+ export async function ralphImportIssuesCommand(options) {
3276
+ const oraModule = await import('ora');
3277
+ const ora = oraModule.default;
3278
+ try {
3279
+ const { checkGhCliAvailable, fetchGithubIssues, convertIssueToStory, mergeStoriesIntoExistingPrd, } = await import('../core/github-issues-importer.js');
3280
+ // 1. Check gh CLI availability
3281
+ const ghAvailable = await checkGhCliAvailable();
3282
+ if (!ghAvailable) {
3283
+ console.error(chalk.red('GitHub CLI (gh) is not installed. Install it from: https://cli.github.com'));
3284
+ return;
3285
+ }
3286
+ // 2. Fetch issues
3287
+ const spinner = ora('Fetching GitHub issues...').start();
3288
+ const issues = await fetchGithubIssues({
3289
+ label: options.label,
3290
+ milestone: options.milestone,
3291
+ limit: options.limit ?? 20,
3292
+ });
3293
+ if (issues.length === 0) {
3294
+ spinner.info('No open issues found matching the given filters.');
3295
+ return;
3296
+ }
3297
+ spinner.text = `Converting ${issues.length} issues to Ralph stories...`;
3298
+ // 3. Load existing PRD (may not exist yet)
3299
+ const cwd = process.cwd();
3300
+ let existingPrd = null;
3301
+ try {
3302
+ const { RalphManager } = await import('../core/ralph-manager.js');
3303
+ const { Logger } = await import('../core/logger.js');
3304
+ const logger = new Logger(cwd);
3305
+ const manager = new RalphManager(cwd, logger);
3306
+ existingPrd = await manager.loadPRD();
3307
+ }
3308
+ catch {
3309
+ // PRD not initialized — will create a new one
3310
+ }
3311
+ // 4. Convert issues to stories
3312
+ const newStories = issues.map((issue) => convertIssueToStory(issue));
3313
+ // 5. Merge into existing PRD
3314
+ const { prd: mergedPrd, result } = mergeStoriesIntoExistingPrd(existingPrd, newStories);
3315
+ // 6. Dry run — preview only
3316
+ if (options.dryRun) {
3317
+ spinner.stop();
3318
+ console.log(chalk.yellow(`Dry run — would import ${result.imported} stories, update ${result.updated}, skip ${result.skipped}`));
3319
+ console.log('');
3320
+ for (const story of mergedPrd.userStories) {
3321
+ const marker = story.passes ? chalk.green('[PASS]') : chalk.gray('[ ]');
3322
+ console.log(` ${marker} ${story.id}: ${story.title}`);
3323
+ }
3324
+ return;
3325
+ }
3326
+ // 7. Save PRD
3327
+ const prdPath = path.join(cwd, '.rulebook', 'ralph', 'prd.json');
3328
+ await ensureDir(path.join(cwd, '.rulebook', 'ralph'));
3329
+ await writeFile(prdPath, JSON.stringify(mergedPrd, null, 2));
3330
+ spinner.succeed(`Imported ${result.imported} new stories, updated ${result.updated} existing, ${result.skipped} skipped`);
3331
+ console.log(`\n PRD saved to: ${prdPath}`);
3332
+ console.log(` Total stories: ${mergedPrd.userStories.length}\n`);
3333
+ }
3334
+ catch (error) {
3335
+ console.error(chalk.red(`Failed to import GitHub issues: ${String(error)}`));
3336
+ process.exit(1);
3337
+ }
3338
+ }
2563
3339
  //# sourceMappingURL=commands.js.map