@hivehub/rulebook 3.2.0 → 3.3.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 (172) hide show
  1. package/.claude/commands/rulebook-task-apply.md +2 -2
  2. package/.claude/commands/rulebook-task-archive.md +3 -3
  3. package/.claude/commands/rulebook-task-create.md +4 -4
  4. package/.claude/commands/rulebook-task-list.md +2 -2
  5. package/.claude/commands/rulebook-task-show.md +2 -2
  6. package/.claude/commands/rulebook-task-validate.md +2 -2
  7. package/.claude-plugin/marketplace.json +1 -1
  8. package/.claude-plugin/plugin.json +1 -1
  9. package/README.md +8 -8
  10. package/dist/cli/commands.d.ts.map +1 -1
  11. package/dist/cli/commands.js +332 -37
  12. package/dist/cli/commands.js.map +1 -1
  13. package/dist/core/config-manager.d.ts +10 -0
  14. package/dist/core/config-manager.d.ts.map +1 -1
  15. package/dist/core/config-manager.js +105 -0
  16. package/dist/core/config-manager.js.map +1 -1
  17. package/dist/core/generator.js +38 -38
  18. package/dist/core/generator.js.map +1 -1
  19. package/dist/core/migrator.d.ts.map +1 -1
  20. package/dist/core/migrator.js +3 -3
  21. package/dist/core/migrator.js.map +1 -1
  22. package/dist/core/openspec-manager.d.ts +2 -2
  23. package/dist/core/openspec-manager.d.ts.map +1 -1
  24. package/dist/core/openspec-manager.js +5 -14
  25. package/dist/core/openspec-manager.js.map +1 -1
  26. package/dist/core/openspec-migrator.d.ts.map +1 -1
  27. package/dist/core/openspec-migrator.js +3 -3
  28. package/dist/core/openspec-migrator.js.map +1 -1
  29. package/dist/core/prd-generator.d.ts.map +1 -1
  30. package/dist/core/prd-generator.js +2 -1
  31. package/dist/core/prd-generator.js.map +1 -1
  32. package/dist/core/ralph-manager.d.ts +9 -1
  33. package/dist/core/ralph-manager.d.ts.map +1 -1
  34. package/dist/core/ralph-manager.js +56 -2
  35. package/dist/core/ralph-manager.js.map +1 -1
  36. package/dist/core/task-manager.d.ts.map +1 -1
  37. package/dist/core/task-manager.js +26 -26
  38. package/dist/core/task-manager.js.map +1 -1
  39. package/dist/core/validator.js +6 -6
  40. package/dist/core/validator.js.map +1 -1
  41. package/dist/mcp/rulebook-server.d.ts +1 -1
  42. package/dist/mcp/rulebook-server.d.ts.map +1 -1
  43. package/dist/mcp/rulebook-server.js +144 -27
  44. package/dist/mcp/rulebook-server.js.map +1 -1
  45. package/dist/types.d.ts +1 -0
  46. package/dist/types.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/templates/commands/rulebook-task-apply.md +2 -2
  49. package/templates/commands/rulebook-task-archive.md +3 -3
  50. package/templates/commands/rulebook-task-create.md +4 -4
  51. package/templates/commands/rulebook-task-list.md +2 -2
  52. package/templates/commands/rulebook-task-show.md +2 -2
  53. package/templates/commands/rulebook-task-validate.md +2 -2
  54. package/templates/core/RALPH.md +2 -2
  55. package/templates/core/RULEBOOK.md +13 -13
  56. package/templates/git/CI_CD_PATTERNS.md +4 -4
  57. package/templates/git/GITHUB_ACTIONS.md +3 -3
  58. package/templates/git/GITLAB_CI.md +4 -4
  59. package/templates/git/SECRETS_MANAGEMENT.md +4 -4
  60. package/templates/hooks/COMMIT_MSG.md +4 -4
  61. package/templates/hooks/POST_CHECKOUT.md +3 -3
  62. package/templates/hooks/PREPARE_COMMIT_MSG.md +3 -3
  63. package/templates/hooks/PRE_COMMIT.md +4 -4
  64. package/templates/hooks/PRE_PUSH.md +4 -4
  65. package/templates/skills/cli/aider/SKILL.md +10 -10
  66. package/templates/skills/cli/amazon-q/SKILL.md +10 -10
  67. package/templates/skills/cli/auggie/SKILL.md +10 -10
  68. package/templates/skills/cli/claude/SKILL.md +10 -10
  69. package/templates/skills/cli/claude-code/SKILL.md +146 -146
  70. package/templates/skills/cli/cline/SKILL.md +10 -10
  71. package/templates/skills/cli/codebuddy/SKILL.md +10 -10
  72. package/templates/skills/cli/codeium/SKILL.md +10 -10
  73. package/templates/skills/cli/codex/SKILL.md +10 -10
  74. package/templates/skills/cli/continue/SKILL.md +10 -10
  75. package/templates/skills/cli/cursor-cli/SKILL.md +10 -10
  76. package/templates/skills/cli/factory/SKILL.md +10 -10
  77. package/templates/skills/cli/gemini/SKILL.md +10 -10
  78. package/templates/skills/cli/kilocode/SKILL.md +10 -10
  79. package/templates/skills/cli/opencode/SKILL.md +10 -10
  80. package/templates/skills/core/agent-automation/SKILL.md +10 -10
  81. package/templates/skills/core/dag/SKILL.md +10 -10
  82. package/templates/skills/core/documentation-rules/SKILL.md +10 -10
  83. package/templates/skills/core/quality-enforcement/SKILL.md +10 -10
  84. package/templates/skills/core/rulebook/SKILL.md +1 -1
  85. package/templates/skills/frameworks/angular/SKILL.md +10 -10
  86. package/templates/skills/frameworks/django/SKILL.md +10 -10
  87. package/templates/skills/frameworks/electron/SKILL.md +10 -10
  88. package/templates/skills/frameworks/flask/SKILL.md +10 -10
  89. package/templates/skills/frameworks/flutter/SKILL.md +10 -10
  90. package/templates/skills/frameworks/jquery/SKILL.md +10 -10
  91. package/templates/skills/frameworks/laravel/SKILL.md +10 -10
  92. package/templates/skills/frameworks/nestjs/SKILL.md +10 -10
  93. package/templates/skills/frameworks/nextjs/SKILL.md +10 -10
  94. package/templates/skills/frameworks/nuxt/SKILL.md +10 -10
  95. package/templates/skills/frameworks/rails/SKILL.md +10 -10
  96. package/templates/skills/frameworks/react/SKILL.md +10 -10
  97. package/templates/skills/frameworks/react-native/SKILL.md +10 -10
  98. package/templates/skills/frameworks/spring/SKILL.md +10 -10
  99. package/templates/skills/frameworks/symfony/SKILL.md +10 -10
  100. package/templates/skills/frameworks/vue/SKILL.md +10 -10
  101. package/templates/skills/frameworks/zend/SKILL.md +10 -10
  102. package/templates/skills/ides/copilot/SKILL.md +10 -10
  103. package/templates/skills/ides/cursor/SKILL.md +10 -10
  104. package/templates/skills/ides/jetbrains-ai/SKILL.md +10 -10
  105. package/templates/skills/ides/replit/SKILL.md +10 -10
  106. package/templates/skills/ides/tabnine/SKILL.md +10 -10
  107. package/templates/skills/ides/vscode/SKILL.md +10 -10
  108. package/templates/skills/ides/windsurf/SKILL.md +10 -10
  109. package/templates/skills/ides/zed/SKILL.md +10 -10
  110. package/templates/skills/languages/ada/SKILL.md +68 -68
  111. package/templates/skills/languages/c/SKILL.md +10 -10
  112. package/templates/skills/languages/cpp/SKILL.md +10 -10
  113. package/templates/skills/languages/csharp/SKILL.md +10 -10
  114. package/templates/skills/languages/dart/SKILL.md +342 -342
  115. package/templates/skills/languages/elixir/SKILL.md +10 -10
  116. package/templates/skills/languages/erlang/SKILL.md +10 -10
  117. package/templates/skills/languages/go/SKILL.md +10 -10
  118. package/templates/skills/languages/haskell/SKILL.md +10 -10
  119. package/templates/skills/languages/java/SKILL.md +10 -10
  120. package/templates/skills/languages/javascript/SKILL.md +10 -10
  121. package/templates/skills/languages/julia/SKILL.md +10 -10
  122. package/templates/skills/languages/kotlin/SKILL.md +10 -10
  123. package/templates/skills/languages/lisp/SKILL.md +10 -10
  124. package/templates/skills/languages/lua/SKILL.md +10 -10
  125. package/templates/skills/languages/objectivec/SKILL.md +10 -10
  126. package/templates/skills/languages/php/SKILL.md +10 -10
  127. package/templates/skills/languages/python/SKILL.md +10 -10
  128. package/templates/skills/languages/r/SKILL.md +360 -360
  129. package/templates/skills/languages/ruby/SKILL.md +10 -10
  130. package/templates/skills/languages/rust/SKILL.md +10 -10
  131. package/templates/skills/languages/sas/SKILL.md +10 -10
  132. package/templates/skills/languages/scala/SKILL.md +10 -10
  133. package/templates/skills/languages/solidity/SKILL.md +10 -10
  134. package/templates/skills/languages/sql/SKILL.md +10 -10
  135. package/templates/skills/languages/swift/SKILL.md +10 -10
  136. package/templates/skills/languages/zig/SKILL.md +10 -10
  137. package/templates/skills/modules/atlassian/SKILL.md +10 -10
  138. package/templates/skills/modules/context7/SKILL.md +10 -10
  139. package/templates/skills/modules/figma/SKILL.md +10 -10
  140. package/templates/skills/modules/github-mcp/SKILL.md +10 -10
  141. package/templates/skills/modules/grafana/SKILL.md +10 -10
  142. package/templates/skills/modules/memory/SKILL.md +10 -10
  143. package/templates/skills/modules/notion/SKILL.md +10 -10
  144. package/templates/skills/modules/playwright/SKILL.md +10 -10
  145. package/templates/skills/modules/rulebook-mcp/SKILL.md +10 -10
  146. package/templates/skills/modules/serena/SKILL.md +10 -10
  147. package/templates/skills/modules/supabase/SKILL.md +10 -10
  148. package/templates/skills/modules/synap/SKILL.md +10 -10
  149. package/templates/skills/modules/vectorizer/SKILL.md +10 -10
  150. package/templates/skills/services/azure-blob/SKILL.md +10 -10
  151. package/templates/skills/services/cassandra/SKILL.md +10 -10
  152. package/templates/skills/services/dynamodb/SKILL.md +10 -10
  153. package/templates/skills/services/elasticsearch/SKILL.md +10 -10
  154. package/templates/skills/services/gcs/SKILL.md +10 -10
  155. package/templates/skills/services/influxdb/SKILL.md +10 -10
  156. package/templates/skills/services/kafka/SKILL.md +10 -10
  157. package/templates/skills/services/mariadb/SKILL.md +10 -10
  158. package/templates/skills/services/memcached/SKILL.md +10 -10
  159. package/templates/skills/services/minio/SKILL.md +10 -10
  160. package/templates/skills/services/mongodb/SKILL.md +10 -10
  161. package/templates/skills/services/mysql/SKILL.md +10 -10
  162. package/templates/skills/services/neo4j/SKILL.md +10 -10
  163. package/templates/skills/services/oracle/SKILL.md +10 -10
  164. package/templates/skills/services/postgresql/SKILL.md +10 -10
  165. package/templates/skills/services/rabbitmq/SKILL.md +10 -10
  166. package/templates/skills/services/redis/SKILL.md +10 -10
  167. package/templates/skills/services/s3/SKILL.md +10 -10
  168. package/templates/skills/services/sqlite/SKILL.md +10 -10
  169. package/templates/skills/services/sqlserver/SKILL.md +10 -10
  170. package/templates/skills/workflows/ralph/SETUP.md +228 -228
  171. package/templates/skills/workflows/ralph/SKILL.md +1 -1
  172. package/templates/skills/workflows/ralph/install.sh +1 -1
@@ -183,6 +183,8 @@ export async function initCommand(options) {
183
183
  const dirMigrationSpinner = ora('Migrating directory structure...').start();
184
184
  await configManager.migrateDirectoryStructure(cwd);
185
185
  dirMigrationSpinner.succeed('Directory structure migrated');
186
+ // Ensure .gitignore has .rulebook entries (keep specs/ and tasks/ tracked)
187
+ await configManager.ensureGitignore();
186
188
  // Auto-detect and enable skills based on project detection (v2.0)
187
189
  let enabledSkills = [];
188
190
  try {
@@ -214,7 +216,7 @@ export async function initCommand(options) {
214
216
  modules: config.modules,
215
217
  services: config.services,
216
218
  modular: config.modular ?? true,
217
- rulebookDir: config.rulebookDir || 'rulebook',
219
+ rulebookDir: config.rulebookDir || '.rulebook',
218
220
  skills: enabledSkills.length > 0 ? { enabled: enabledSkills } : undefined,
219
221
  ralph: existingConfig.ralph,
220
222
  memory: existingConfig.memory,
@@ -226,7 +228,7 @@ export async function initCommand(options) {
226
228
  // Migrate flat layout to specs/ subdirectory if needed
227
229
  {
228
230
  const { hasFlatLayout, migrateFlatToSpecs } = await import('../core/migrator.js');
229
- const rulebookDirForMigration = config.rulebookDir || 'rulebook';
231
+ const rulebookDirForMigration = config.rulebookDir || '.rulebook';
230
232
  if (await hasFlatLayout(cwd, rulebookDirForMigration)) {
231
233
  const { migratedFiles } = await migrateFlatToSpecs(cwd, rulebookDirForMigration);
232
234
  if (migratedFiles.length > 0) {
@@ -831,7 +833,7 @@ export async function taskCreateCommand(taskId) {
831
833
  const { createConfigManager } = await import('../core/config-manager.js');
832
834
  const configManager = createConfigManager(cwd);
833
835
  const config = await configManager.loadConfig();
834
- const rulebookDir = config.rulebookDir || 'rulebook';
836
+ const rulebookDir = config.rulebookDir || '.rulebook';
835
837
  const taskManager = createTaskManager(cwd, rulebookDir);
836
838
  await taskManager.createTask(taskId);
837
839
  console.log(chalk.green(`✅ Task ${taskId} created successfully`));
@@ -855,7 +857,7 @@ export async function taskListCommand(includeArchived = false) {
855
857
  const { createConfigManager } = await import('../core/config-manager.js');
856
858
  const configManager = createConfigManager(cwd);
857
859
  const config = await configManager.loadConfig();
858
- const rulebookDir = config.rulebookDir || 'rulebook';
860
+ const rulebookDir = config.rulebookDir || '.rulebook';
859
861
  const taskManager = createTaskManager(cwd, rulebookDir);
860
862
  const tasks = await taskManager.listTasks(includeArchived);
861
863
  if (tasks.length === 0) {
@@ -899,7 +901,7 @@ export async function taskShowCommand(taskId) {
899
901
  const { createConfigManager } = await import('../core/config-manager.js');
900
902
  const configManager = createConfigManager(cwd);
901
903
  const config = await configManager.loadConfig();
902
- const rulebookDir = config.rulebookDir || 'rulebook';
904
+ const rulebookDir = config.rulebookDir || '.rulebook';
903
905
  const taskManager = createTaskManager(cwd, rulebookDir);
904
906
  const task = await taskManager.showTask(taskId);
905
907
  if (!task) {
@@ -941,7 +943,7 @@ export async function taskValidateCommand(taskId) {
941
943
  const { createConfigManager } = await import('../core/config-manager.js');
942
944
  const configManager = createConfigManager(cwd);
943
945
  const config = await configManager.loadConfig();
944
- const rulebookDir = config.rulebookDir || 'rulebook';
946
+ const rulebookDir = config.rulebookDir || '.rulebook';
945
947
  const taskManager = createTaskManager(cwd, rulebookDir);
946
948
  const validation = await taskManager.validateTask(taskId);
947
949
  if (validation.valid) {
@@ -980,7 +982,7 @@ export async function taskArchiveCommand(taskId, skipValidation = false) {
980
982
  const { createConfigManager } = await import('../core/config-manager.js');
981
983
  const configManager = createConfigManager(cwd);
982
984
  const config = await configManager.loadConfig();
983
- const rulebookDir = config.rulebookDir || 'rulebook';
985
+ const rulebookDir = config.rulebookDir || '.rulebook';
984
986
  const taskManager = createTaskManager(cwd, rulebookDir);
985
987
  await taskManager.archiveTask(taskId, skipValidation);
986
988
  console.log(chalk.green(`✅ Task ${taskId} archived successfully`));
@@ -995,25 +997,33 @@ export async function taskArchiveCommand(taskId, skipValidation = false) {
995
997
  * Adds mcp block to .rulebook and creates/updates .cursor/mcp.json
996
998
  */
997
999
  export async function mcpInitCommand() {
998
- const { findRulebookFile } = await import('../mcp/rulebook-server.js');
999
- const { existsSync, readFileSync, writeFileSync } = await import('fs');
1000
+ const { findRulebookConfig } = await import('../mcp/rulebook-server.js');
1001
+ const { existsSync, readFileSync, writeFileSync, statSync } = await import('fs');
1000
1002
  const { join, dirname } = await import('path');
1001
1003
  const { createConfigManager } = await import('../core/config-manager.js');
1002
1004
  try {
1003
- // Find or create .rulebook file
1005
+ // Find or create .rulebook file/directory
1004
1006
  const cwd = process.cwd();
1005
- let rulebookPath = findRulebookFile(cwd);
1007
+ let rulebookPath = findRulebookConfig(cwd);
1006
1008
  if (!rulebookPath) {
1007
- // Create new .rulebook file
1009
+ // Create new .rulebook directory via ConfigManager
1008
1010
  rulebookPath = join(cwd, '.rulebook');
1009
1011
  const configManager = createConfigManager(cwd);
1010
1012
  await configManager.initializeConfig();
1011
1013
  }
1012
1014
  const projectRoot = dirname(rulebookPath);
1015
+ // Resolve config file path (handle .rulebook as directory or file)
1016
+ let configFilePath = rulebookPath;
1017
+ if (existsSync(rulebookPath)) {
1018
+ const stats = statSync(rulebookPath);
1019
+ if (stats.isDirectory()) {
1020
+ configFilePath = join(rulebookPath, 'rulebook.json');
1021
+ }
1022
+ }
1013
1023
  // Load existing config
1014
1024
  let config = {};
1015
- if (existsSync(rulebookPath)) {
1016
- const raw = readFileSync(rulebookPath, 'utf8');
1025
+ if (existsSync(configFilePath)) {
1026
+ const raw = readFileSync(configFilePath, 'utf8');
1017
1027
  config = JSON.parse(raw);
1018
1028
  }
1019
1029
  // Add/update mcp block
@@ -1021,11 +1031,11 @@ export async function mcpInitCommand() {
1021
1031
  if (config.mcp.enabled === undefined)
1022
1032
  config.mcp.enabled = true;
1023
1033
  if (!config.mcp.tasksDir)
1024
- config.mcp.tasksDir = 'rulebook/tasks';
1034
+ config.mcp.tasksDir = '.rulebook/tasks';
1025
1035
  if (!config.mcp.archiveDir)
1026
- config.mcp.archiveDir = 'rulebook/archive';
1036
+ config.mcp.archiveDir = '.rulebook/archive';
1027
1037
  // Save updated config
1028
- writeFileSync(rulebookPath, JSON.stringify(config, null, 2) + '\n');
1038
+ writeFileSync(configFilePath, JSON.stringify(config, null, 2) + '\n');
1029
1039
  // Create/update .cursor/mcp.json if .cursor directory exists
1030
1040
  const cursorDir = join(projectRoot, '.cursor');
1031
1041
  if (existsSync(cursorDir)) {
@@ -1212,7 +1222,7 @@ export async function updateCommand(options) {
1212
1222
  if (existsSync(openspecChangesPath)) {
1213
1223
  const migrationSpinner = ora('Migrating OpenSpec tasks to Rulebook format...').start();
1214
1224
  const { migrateOpenSpecToRulebook, migrateOpenSpecArchives, removeOpenSpecRulebookFile } = await import('../core/openspec-migrator.js');
1215
- const rulebookDir = config.rulebookDir || 'rulebook';
1225
+ const rulebookDir = config.rulebookDir || '.rulebook';
1216
1226
  const migrationResult = await migrateOpenSpecToRulebook(cwd, rulebookDir);
1217
1227
  const archiveMigrationResult = await migrateOpenSpecArchives(cwd, rulebookDir);
1218
1228
  if (migrationResult.migrated > 0 || archiveMigrationResult.migrated > 0) {
@@ -1238,10 +1248,10 @@ export async function updateCommand(options) {
1238
1248
  console.log(chalk.yellow(` - ${error}`));
1239
1249
  }
1240
1250
  }
1241
- // Remove /rulebook/specs/OPENSPEC.md if exists
1251
+ // Remove /.rulebook/specs/OPENSPEC.md if exists
1242
1252
  const removed = await removeOpenSpecRulebookFile(cwd, rulebookDir);
1243
1253
  if (removed) {
1244
- console.log(chalk.gray(' Removed /rulebook/specs/OPENSPEC.md'));
1254
+ console.log(chalk.gray(` Removed /${rulebookDir}/specs/OPENSPEC.md`));
1245
1255
  }
1246
1256
  // Remove OpenSpec commands from .cursor/commands/
1247
1257
  const { removeOpenSpecCommands } = await import('../core/openspec-migrator.js');
@@ -1386,15 +1396,17 @@ export async function updateCommand(options) {
1386
1396
  modules: config.modules,
1387
1397
  services: config.services,
1388
1398
  modular: config.modular ?? true,
1389
- rulebookDir: config.rulebookDir || 'rulebook',
1399
+ rulebookDir: config.rulebookDir || '.rulebook',
1390
1400
  skills: detectedSkills.length > 0 ? { enabled: detectedSkills } : undefined,
1391
1401
  ralph: existingRalph,
1392
1402
  memory: existingConfig.memory,
1393
1403
  });
1404
+ // Ensure .rulebook is in .gitignore with exceptions for specs/tasks
1405
+ await configManager.ensureGitignore();
1394
1406
  // Migrate flat layout to specs/ subdirectory if needed
1395
1407
  {
1396
1408
  const { hasFlatLayout, migrateFlatToSpecs } = await import('../core/migrator.js');
1397
- const rulebookDirForMigration = config.rulebookDir || 'rulebook';
1409
+ const rulebookDirForMigration = config.rulebookDir || '.rulebook';
1398
1410
  if (await hasFlatLayout(cwd, rulebookDirForMigration)) {
1399
1411
  const migrationSpinner = ora('Migrating rulebook files to specs/ subdirectory...').start();
1400
1412
  const { migratedFiles } = await migrateFlatToSpecs(cwd, rulebookDirForMigration);
@@ -2089,7 +2101,9 @@ export async function ralphRunCommand(options) {
2089
2101
  const cwd = process.cwd();
2090
2102
  const { Logger } = await import('../core/logger.js');
2091
2103
  const { RalphManager } = await import('../core/ralph-manager.js');
2104
+ const { RalphParser } = await import('../agents/ralph-parser.js');
2092
2105
  const { createConfigManager } = await import('../core/config-manager.js');
2106
+ const childProcess = await import('child_process');
2093
2107
  const logger = new Logger(cwd);
2094
2108
  const configManager = createConfigManager(cwd);
2095
2109
  const config = await configManager.loadConfig();
@@ -2097,40 +2111,107 @@ export async function ralphRunCommand(options) {
2097
2111
  const maxIterations = options.maxIterations || config.ralph?.maxIterations || 10;
2098
2112
  const tool = options.tool || config.ralph?.tool || 'claude';
2099
2113
  await ralphManager.initialize(maxIterations, tool);
2114
+ // Create git branch from PRD
2115
+ const prd = await ralphManager.loadPRD();
2116
+ if (prd?.branchName) {
2117
+ await ralphCreateBranch(cwd, prd.branchName);
2118
+ }
2119
+ // Handle Ctrl+C for graceful pause
2120
+ let interrupted = false;
2121
+ const handleInterrupt = async () => {
2122
+ interrupted = true;
2123
+ spinner.warn('Pausing after current iteration...');
2124
+ await ralphManager.pause();
2125
+ };
2126
+ process.on('SIGINT', handleInterrupt);
2127
+ // Sync task count from PRD (may have been saved after initialize)
2128
+ await ralphManager.refreshTaskCount();
2100
2129
  spinner.text = 'Ralph loop running (Ctrl+C to pause)...';
2101
2130
  let iterationCount = 0;
2102
- while (ralphManager.canContinue()) {
2131
+ while (ralphManager.canContinue() && !interrupted) {
2103
2132
  iterationCount++;
2104
2133
  const task = await ralphManager.getNextTask();
2105
2134
  if (!task) {
2106
2135
  break;
2107
2136
  }
2108
- spinner.text = `Iteration ${iterationCount}: ${task.title}...`;
2109
- // In production, would execute agent here
2110
- // For now, just mark story as complete
2111
- await ralphManager.markStoryComplete(task.id);
2112
- // Simulate iteration result (placeholder)
2137
+ spinner.stop();
2138
+ console.log(chalk.bold.cyan(`\n ── Iteration ${iterationCount}: ${task.title} ──\n`));
2139
+ const startTime = Date.now();
2140
+ // 1. Execute AI agent with task context
2141
+ const prompt = ralphBuildPrompt(task, prd);
2142
+ let agentOutput = '';
2143
+ try {
2144
+ agentOutput = await ralphExecuteAgent(tool, prompt, cwd, childProcess.spawn);
2145
+ }
2146
+ catch (agentError) {
2147
+ agentOutput = `Error executing agent: ${agentError.message || agentError}`;
2148
+ console.log(chalk.red(` Agent error: ${agentError.message || agentError}`));
2149
+ }
2150
+ // 2. Run quality gates
2151
+ spinner.start('Running quality gates...');
2152
+ const qualityResults = await ralphRunQualityGates(cwd, childProcess.spawn);
2153
+ spinner.stop();
2154
+ // Print quality gate results
2155
+ const gateIcon = (pass) => (pass ? chalk.green('✓') : chalk.red('✗'));
2156
+ console.log(` ${gateIcon(qualityResults.type_check)} type-check`);
2157
+ console.log(` ${gateIcon(qualityResults.lint)} lint`);
2158
+ console.log(` ${gateIcon(qualityResults.tests)} tests`);
2159
+ console.log(` ${gateIcon(qualityResults.coverage_met)} coverage`);
2160
+ const executionTime = Date.now() - startTime;
2161
+ // 3. Parse agent output for learnings/errors
2162
+ const parsed = RalphParser.parseAgentOutput(agentOutput, iterationCount, task.id, task.title, tool);
2163
+ // 4. Determine status from real quality gates
2164
+ const allGatesPass = qualityResults.type_check &&
2165
+ qualityResults.lint &&
2166
+ qualityResults.tests &&
2167
+ qualityResults.coverage_met;
2168
+ const passCount = Object.values(qualityResults).filter(Boolean).length;
2169
+ const status = allGatesPass
2170
+ ? 'success'
2171
+ : passCount >= 2
2172
+ ? 'partial'
2173
+ : 'failed';
2174
+ // 5. Git commit if successful
2175
+ let gitCommit;
2176
+ if (allGatesPass) {
2177
+ gitCommit = await ralphGitCommit(cwd, task, iterationCount, childProcess.spawn);
2178
+ await ralphManager.markStoryComplete(task.id);
2179
+ console.log(chalk.green(`\n ✅ Story ${task.id} completed`));
2180
+ }
2181
+ else {
2182
+ console.log(chalk.yellow(`\n ⚠ Story ${task.id} not completed (quality gates failed)`));
2183
+ }
2184
+ // 6. Record iteration
2113
2185
  const result = {
2114
2186
  iteration: iterationCount,
2115
2187
  timestamp: new Date().toISOString(),
2116
2188
  task_id: task.id,
2117
2189
  task_title: task.title,
2118
- status: 'success',
2190
+ status,
2119
2191
  ai_tool: tool,
2120
- execution_time_ms: 5000,
2121
- quality_checks: { type_check: true, lint: true, tests: true, coverage_met: true },
2122
- output_summary: `Completed ${task.title}`,
2123
- git_commit: undefined,
2124
- learnings: [],
2125
- errors: [],
2126
- metadata: { context_loss_count: 0, parsed_completion: true },
2192
+ execution_time_ms: executionTime,
2193
+ quality_checks: qualityResults,
2194
+ output_summary: parsed.output_summary || `Iteration ${iterationCount}: ${task.title}`,
2195
+ git_commit: gitCommit,
2196
+ learnings: parsed.learnings,
2197
+ errors: parsed.errors,
2198
+ metadata: {
2199
+ context_loss_count: parsed.metadata.context_loss_count,
2200
+ parsed_completion: parsed.metadata.parsed_completion,
2201
+ },
2127
2202
  };
2128
2203
  await ralphManager.recordIteration(result);
2204
+ spinner.start('Preparing next iteration...');
2129
2205
  }
2206
+ // Cleanup
2207
+ process.removeListener('SIGINT', handleInterrupt);
2130
2208
  const stats = await ralphManager.getTaskStats();
2131
2209
  spinner.succeed(`Ralph loop complete: ${stats.completed}/${stats.total} tasks completed`);
2132
2210
  console.log(`\n ✅ Iterations: ${iterationCount}`);
2133
2211
  console.log(` 📊 Completed: ${stats.completed}/${stats.total}`);
2212
+ if (interrupted) {
2213
+ console.log(chalk.yellow(` ⏸ Paused by user. Resume: ${chalk.bold('rulebook ralph resume')}`));
2214
+ }
2134
2215
  console.log(`\n View history: ${chalk.bold('rulebook ralph history')}\n`);
2135
2216
  }
2136
2217
  catch (error) {
@@ -2139,6 +2220,208 @@ export async function ralphRunCommand(options) {
2139
2220
  process.exit(1);
2140
2221
  }
2141
2222
  }
2223
+ /**
2224
+ * Build prompt for AI agent from user story context
2225
+ */
2226
+ function ralphBuildPrompt(task, prd) {
2227
+ const criteria = (task.acceptanceCriteria || []).map((c) => `- ${c}`).join('\n');
2228
+ return [
2229
+ `You are working on project: ${prd?.project || 'unknown'}`,
2230
+ ``,
2231
+ `## Current Task: ${task.title}`,
2232
+ `ID: ${task.id}`,
2233
+ ``,
2234
+ `## Description`,
2235
+ task.description,
2236
+ ``,
2237
+ `## Acceptance Criteria`,
2238
+ criteria,
2239
+ ``,
2240
+ task.notes ? `## Notes\n${task.notes}\n` : '',
2241
+ `## Instructions`,
2242
+ `1. Implement the changes described above`,
2243
+ `2. Ensure all acceptance criteria are met`,
2244
+ `3. Run quality checks: type-check, lint, tests`,
2245
+ `4. Fix any issues found by quality checks`,
2246
+ `5. When done, summarize what was changed`,
2247
+ ]
2248
+ .filter(Boolean)
2249
+ .join('\n');
2250
+ }
2251
+ /**
2252
+ * Execute AI agent and capture output
2253
+ */
2254
+ async function ralphExecuteAgent(tool, prompt, cwd, spawn) {
2255
+ // Claude: use -p (print mode) with --dangerously-skip-permissions to allow file edits
2256
+ // Prompt is passed via stdin to avoid shell escaping issues and arg length limits
2257
+ const toolCommands = {
2258
+ claude: {
2259
+ cmd: 'claude',
2260
+ args: ['-p', '--dangerously-skip-permissions', '--verbose'],
2261
+ stdinPrompt: true,
2262
+ },
2263
+ amp: { cmd: 'amp', args: ['-p', prompt], stdinPrompt: false },
2264
+ gemini: { cmd: 'gemini', args: ['-p', prompt], stdinPrompt: false },
2265
+ };
2266
+ const config = toolCommands[tool] || toolCommands.claude;
2267
+ return new Promise((resolve, reject) => {
2268
+ let output = '';
2269
+ let errorOutput = '';
2270
+ const proc = spawn(config.cmd, config.args, {
2271
+ cwd,
2272
+ shell: true,
2273
+ stdio: ['pipe', 'pipe', 'pipe'],
2274
+ env: { ...process.env },
2275
+ });
2276
+ // For Claude, write prompt to stdin then close it
2277
+ if (config.stdinPrompt && proc.stdin) {
2278
+ proc.stdin.write(prompt);
2279
+ proc.stdin.end();
2280
+ }
2281
+ proc.stdout?.on('data', (data) => {
2282
+ const text = data.toString();
2283
+ output += text;
2284
+ process.stdout.write(text);
2285
+ });
2286
+ proc.stderr?.on('data', (data) => {
2287
+ errorOutput += data.toString();
2288
+ });
2289
+ proc.on('close', (code) => {
2290
+ if (code === 0 || output.length > 0) {
2291
+ resolve(output || errorOutput);
2292
+ }
2293
+ else {
2294
+ reject(new Error(`Agent ${tool} exited with code ${code}: ${errorOutput.slice(0, 500)}`));
2295
+ }
2296
+ });
2297
+ proc.on('error', (err) => {
2298
+ reject(new Error(`Failed to start ${tool}: ${err.message}`));
2299
+ });
2300
+ // 10 minute timeout per iteration
2301
+ setTimeout(() => {
2302
+ proc.kill('SIGTERM');
2303
+ resolve(output || 'Agent execution timed out after 10 minutes');
2304
+ }, 600000);
2305
+ });
2306
+ }
2307
+ /**
2308
+ * Run quality gates and return results
2309
+ */
2310
+ async function ralphRunQualityGates(cwd, spawn) {
2311
+ const runGate = (cmd, args) => {
2312
+ return new Promise((resolve) => {
2313
+ const proc = spawn(cmd, args, {
2314
+ cwd,
2315
+ shell: true,
2316
+ stdio: ['pipe', 'pipe', 'pipe'],
2317
+ });
2318
+ proc.on('close', (code) => {
2319
+ resolve(code === 0);
2320
+ });
2321
+ proc.on('error', () => {
2322
+ resolve(false);
2323
+ });
2324
+ // 2 minute timeout per gate
2325
+ setTimeout(() => {
2326
+ proc.kill('SIGTERM');
2327
+ resolve(false);
2328
+ }, 120000);
2329
+ });
2330
+ };
2331
+ // Run gates in parallel
2332
+ const [typeCheck, lint, tests] = await Promise.all([
2333
+ runGate('npm', ['run', 'type-check']),
2334
+ runGate('npm', ['run', 'lint']),
2335
+ runGate('npm', ['test']),
2336
+ ]);
2337
+ return {
2338
+ type_check: typeCheck,
2339
+ lint: lint,
2340
+ tests: tests,
2341
+ coverage_met: tests,
2342
+ };
2343
+ }
2344
+ /**
2345
+ * Create git branch from PRD branchName
2346
+ */
2347
+ async function ralphCreateBranch(cwd, branchName) {
2348
+ const { readFileSync } = await import('fs');
2349
+ const { spawn } = await import('child_process');
2350
+ // Check if already on the branch
2351
+ try {
2352
+ const gitHeadPath = path.join(cwd, '.git', 'HEAD');
2353
+ const head = readFileSync(gitHeadPath, 'utf8').trim();
2354
+ const currentBranch = head.replace('ref: refs/heads/', '');
2355
+ if (currentBranch === branchName) {
2356
+ return;
2357
+ }
2358
+ }
2359
+ catch {
2360
+ return;
2361
+ }
2362
+ // Create or checkout branch
2363
+ await new Promise((resolve) => {
2364
+ const proc = spawn('git', ['checkout', '-B', branchName], {
2365
+ cwd,
2366
+ shell: true,
2367
+ stdio: 'pipe',
2368
+ });
2369
+ proc.on('close', () => resolve());
2370
+ proc.on('error', () => resolve());
2371
+ });
2372
+ }
2373
+ /**
2374
+ * Commit changes after successful iteration
2375
+ */
2376
+ async function ralphGitCommit(cwd, task, iteration, spawn) {
2377
+ // Stage all changes
2378
+ await new Promise((resolve) => {
2379
+ const proc = spawn('git', ['add', '-A'], { cwd, shell: true, stdio: 'pipe' });
2380
+ proc.on('close', () => resolve());
2381
+ proc.on('error', () => resolve());
2382
+ });
2383
+ // Check if there are staged changes
2384
+ const hasChanges = await new Promise((resolve) => {
2385
+ let output = '';
2386
+ const proc = spawn('git', ['diff', '--cached', '--stat'], {
2387
+ cwd,
2388
+ shell: true,
2389
+ stdio: ['pipe', 'pipe', 'pipe'],
2390
+ });
2391
+ proc.stdout?.on('data', (data) => {
2392
+ output += data.toString();
2393
+ });
2394
+ proc.on('close', () => resolve(output.trim().length > 0));
2395
+ proc.on('error', () => resolve(false));
2396
+ });
2397
+ if (!hasChanges) {
2398
+ return undefined;
2399
+ }
2400
+ // Commit with Ralph message
2401
+ const commitMsg = `ralph(${task.id}): ${task.title}\n\nIteration ${iteration} - Ralph autonomous loop`;
2402
+ const commitHash = await new Promise((resolve) => {
2403
+ let output = '';
2404
+ const proc = spawn('git', ['commit', '-m', commitMsg], {
2405
+ cwd,
2406
+ shell: true,
2407
+ stdio: ['pipe', 'pipe', 'pipe'],
2408
+ });
2409
+ proc.stdout?.on('data', (data) => {
2410
+ output += data.toString();
2411
+ });
2412
+ proc.on('close', (code) => {
2413
+ if (code === 0) {
2414
+ const hashMatch = output.match(/\[[\w/.-]+ ([a-f0-9]+)\]/);
2415
+ resolve(hashMatch ? hashMatch[1] : undefined);
2416
+ }
2417
+ else {
2418
+ resolve(undefined);
2419
+ }
2420
+ });
2421
+ proc.on('error', () => resolve(undefined));
2422
+ });
2423
+ return commitHash;
2424
+ }
2142
2425
  export async function ralphStatusCommand() {
2143
2426
  const oraModule = await import('ora');
2144
2427
  const ora = oraModule.default;
@@ -2225,6 +2508,12 @@ export async function ralphPauseCommand() {
2225
2508
  const { RalphManager } = await import('../core/ralph-manager.js');
2226
2509
  const logger = new Logger(cwd);
2227
2510
  const ralphManager = new RalphManager(cwd, logger);
2511
+ const status = await ralphManager.getStatus();
2512
+ if (!status) {
2513
+ spinner.fail('Ralph not initialized');
2514
+ console.log(`\n Run: ${chalk.bold('rulebook ralph init')}\n`);
2515
+ return;
2516
+ }
2228
2517
  await ralphManager.pause();
2229
2518
  spinner.succeed('Ralph loop paused');
2230
2519
  console.log(`\n Resume with: ${chalk.bold('rulebook ralph resume')}\n`);
@@ -2245,6 +2534,12 @@ export async function ralphResumeCommand() {
2245
2534
  const { RalphManager } = await import('../core/ralph-manager.js');
2246
2535
  const logger = new Logger(cwd);
2247
2536
  const ralphManager = new RalphManager(cwd, logger);
2537
+ const status = await ralphManager.getStatus();
2538
+ if (!status) {
2539
+ spinner.fail('Ralph not initialized');
2540
+ console.log(`\n Run: ${chalk.bold('rulebook ralph init')}\n`);
2541
+ return;
2542
+ }
2248
2543
  await ralphManager.resume();
2249
2544
  spinner.succeed('Ralph loop resumed');
2250
2545
  console.log(`\n Continue loop: ${chalk.bold('rulebook ralph run')}\n`);
@@ -2286,7 +2581,7 @@ export async function setupClaudeCodePlugin() {
2286
2581
  }
2287
2582
  // Add rulebook plugin
2288
2583
  const pluginKey = `rulebook@hivehub`;
2289
- const version = pluginJson.version || '3.2.0';
2584
+ const version = pluginJson.version || '3.2.1';
2290
2585
  const installPath = path.join(pluginsDir, 'cache', 'hivehub', 'rulebook', version);
2291
2586
  if (!installedPlugins.plugins[pluginKey]) {
2292
2587
  installedPlugins.plugins[pluginKey] = [];