@hivehub/rulebook 3.2.1 → 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.
- package/.claude/commands/rulebook-task-apply.md +2 -2
- package/.claude/commands/rulebook-task-archive.md +3 -3
- package/.claude/commands/rulebook-task-create.md +4 -4
- package/.claude/commands/rulebook-task-list.md +2 -2
- package/.claude/commands/rulebook-task-show.md +2 -2
- package/.claude/commands/rulebook-task-validate.md +2 -2
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +8 -8
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +328 -33
- package/dist/cli/commands.js.map +1 -1
- package/dist/core/config-manager.d.ts +10 -0
- package/dist/core/config-manager.d.ts.map +1 -1
- package/dist/core/config-manager.js +105 -0
- package/dist/core/config-manager.js.map +1 -1
- package/dist/core/generator.js +38 -38
- package/dist/core/generator.js.map +1 -1
- package/dist/core/migrator.d.ts.map +1 -1
- package/dist/core/migrator.js +3 -3
- package/dist/core/migrator.js.map +1 -1
- package/dist/core/openspec-manager.d.ts +2 -2
- package/dist/core/openspec-manager.d.ts.map +1 -1
- package/dist/core/openspec-manager.js +5 -14
- package/dist/core/openspec-manager.js.map +1 -1
- package/dist/core/openspec-migrator.d.ts.map +1 -1
- package/dist/core/openspec-migrator.js +3 -3
- package/dist/core/openspec-migrator.js.map +1 -1
- package/dist/core/prd-generator.d.ts.map +1 -1
- package/dist/core/prd-generator.js +2 -1
- package/dist/core/prd-generator.js.map +1 -1
- package/dist/core/ralph-manager.d.ts +9 -1
- package/dist/core/ralph-manager.d.ts.map +1 -1
- package/dist/core/ralph-manager.js +56 -2
- package/dist/core/ralph-manager.js.map +1 -1
- package/dist/core/task-manager.d.ts.map +1 -1
- package/dist/core/task-manager.js +26 -26
- package/dist/core/task-manager.js.map +1 -1
- package/dist/core/validator.js +6 -6
- package/dist/core/validator.js.map +1 -1
- package/dist/mcp/rulebook-server.d.ts.map +1 -1
- package/dist/mcp/rulebook-server.js +131 -20
- package/dist/mcp/rulebook-server.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/commands/rulebook-task-apply.md +2 -2
- package/templates/commands/rulebook-task-archive.md +3 -3
- package/templates/commands/rulebook-task-create.md +4 -4
- package/templates/commands/rulebook-task-list.md +2 -2
- package/templates/commands/rulebook-task-show.md +2 -2
- package/templates/commands/rulebook-task-validate.md +2 -2
- package/templates/core/RALPH.md +2 -2
- package/templates/core/RULEBOOK.md +13 -13
- package/templates/git/CI_CD_PATTERNS.md +4 -4
- package/templates/git/GITHUB_ACTIONS.md +3 -3
- package/templates/git/GITLAB_CI.md +4 -4
- package/templates/git/SECRETS_MANAGEMENT.md +4 -4
- package/templates/hooks/COMMIT_MSG.md +4 -4
- package/templates/hooks/POST_CHECKOUT.md +3 -3
- package/templates/hooks/PREPARE_COMMIT_MSG.md +3 -3
- package/templates/hooks/PRE_COMMIT.md +4 -4
- package/templates/hooks/PRE_PUSH.md +4 -4
- package/templates/skills/cli/aider/SKILL.md +10 -10
- package/templates/skills/cli/amazon-q/SKILL.md +10 -10
- package/templates/skills/cli/auggie/SKILL.md +10 -10
- package/templates/skills/cli/claude/SKILL.md +10 -10
- package/templates/skills/cli/claude-code/SKILL.md +146 -146
- package/templates/skills/cli/cline/SKILL.md +10 -10
- package/templates/skills/cli/codebuddy/SKILL.md +10 -10
- package/templates/skills/cli/codeium/SKILL.md +10 -10
- package/templates/skills/cli/codex/SKILL.md +10 -10
- package/templates/skills/cli/continue/SKILL.md +10 -10
- package/templates/skills/cli/cursor-cli/SKILL.md +10 -10
- package/templates/skills/cli/factory/SKILL.md +10 -10
- package/templates/skills/cli/gemini/SKILL.md +10 -10
- package/templates/skills/cli/kilocode/SKILL.md +10 -10
- package/templates/skills/cli/opencode/SKILL.md +10 -10
- package/templates/skills/core/agent-automation/SKILL.md +10 -10
- package/templates/skills/core/dag/SKILL.md +10 -10
- package/templates/skills/core/documentation-rules/SKILL.md +10 -10
- package/templates/skills/core/quality-enforcement/SKILL.md +10 -10
- package/templates/skills/core/rulebook/SKILL.md +1 -1
- package/templates/skills/frameworks/angular/SKILL.md +10 -10
- package/templates/skills/frameworks/django/SKILL.md +10 -10
- package/templates/skills/frameworks/electron/SKILL.md +10 -10
- package/templates/skills/frameworks/flask/SKILL.md +10 -10
- package/templates/skills/frameworks/flutter/SKILL.md +10 -10
- package/templates/skills/frameworks/jquery/SKILL.md +10 -10
- package/templates/skills/frameworks/laravel/SKILL.md +10 -10
- package/templates/skills/frameworks/nestjs/SKILL.md +10 -10
- package/templates/skills/frameworks/nextjs/SKILL.md +10 -10
- package/templates/skills/frameworks/nuxt/SKILL.md +10 -10
- package/templates/skills/frameworks/rails/SKILL.md +10 -10
- package/templates/skills/frameworks/react/SKILL.md +10 -10
- package/templates/skills/frameworks/react-native/SKILL.md +10 -10
- package/templates/skills/frameworks/spring/SKILL.md +10 -10
- package/templates/skills/frameworks/symfony/SKILL.md +10 -10
- package/templates/skills/frameworks/vue/SKILL.md +10 -10
- package/templates/skills/frameworks/zend/SKILL.md +10 -10
- package/templates/skills/ides/copilot/SKILL.md +10 -10
- package/templates/skills/ides/cursor/SKILL.md +10 -10
- package/templates/skills/ides/jetbrains-ai/SKILL.md +10 -10
- package/templates/skills/ides/replit/SKILL.md +10 -10
- package/templates/skills/ides/tabnine/SKILL.md +10 -10
- package/templates/skills/ides/vscode/SKILL.md +10 -10
- package/templates/skills/ides/windsurf/SKILL.md +10 -10
- package/templates/skills/ides/zed/SKILL.md +10 -10
- package/templates/skills/languages/ada/SKILL.md +68 -68
- package/templates/skills/languages/c/SKILL.md +10 -10
- package/templates/skills/languages/cpp/SKILL.md +10 -10
- package/templates/skills/languages/csharp/SKILL.md +10 -10
- package/templates/skills/languages/dart/SKILL.md +342 -342
- package/templates/skills/languages/elixir/SKILL.md +10 -10
- package/templates/skills/languages/erlang/SKILL.md +10 -10
- package/templates/skills/languages/go/SKILL.md +10 -10
- package/templates/skills/languages/haskell/SKILL.md +10 -10
- package/templates/skills/languages/java/SKILL.md +10 -10
- package/templates/skills/languages/javascript/SKILL.md +10 -10
- package/templates/skills/languages/julia/SKILL.md +10 -10
- package/templates/skills/languages/kotlin/SKILL.md +10 -10
- package/templates/skills/languages/lisp/SKILL.md +10 -10
- package/templates/skills/languages/lua/SKILL.md +10 -10
- package/templates/skills/languages/objectivec/SKILL.md +10 -10
- package/templates/skills/languages/php/SKILL.md +10 -10
- package/templates/skills/languages/python/SKILL.md +10 -10
- package/templates/skills/languages/r/SKILL.md +360 -360
- package/templates/skills/languages/ruby/SKILL.md +10 -10
- package/templates/skills/languages/rust/SKILL.md +10 -10
- package/templates/skills/languages/sas/SKILL.md +10 -10
- package/templates/skills/languages/scala/SKILL.md +10 -10
- package/templates/skills/languages/solidity/SKILL.md +10 -10
- package/templates/skills/languages/sql/SKILL.md +10 -10
- package/templates/skills/languages/swift/SKILL.md +10 -10
- package/templates/skills/languages/zig/SKILL.md +10 -10
- package/templates/skills/modules/atlassian/SKILL.md +10 -10
- package/templates/skills/modules/context7/SKILL.md +10 -10
- package/templates/skills/modules/figma/SKILL.md +10 -10
- package/templates/skills/modules/github-mcp/SKILL.md +10 -10
- package/templates/skills/modules/grafana/SKILL.md +10 -10
- package/templates/skills/modules/memory/SKILL.md +10 -10
- package/templates/skills/modules/notion/SKILL.md +10 -10
- package/templates/skills/modules/playwright/SKILL.md +10 -10
- package/templates/skills/modules/rulebook-mcp/SKILL.md +10 -10
- package/templates/skills/modules/serena/SKILL.md +10 -10
- package/templates/skills/modules/supabase/SKILL.md +10 -10
- package/templates/skills/modules/synap/SKILL.md +10 -10
- package/templates/skills/modules/vectorizer/SKILL.md +10 -10
- package/templates/skills/services/azure-blob/SKILL.md +10 -10
- package/templates/skills/services/cassandra/SKILL.md +10 -10
- package/templates/skills/services/dynamodb/SKILL.md +10 -10
- package/templates/skills/services/elasticsearch/SKILL.md +10 -10
- package/templates/skills/services/gcs/SKILL.md +10 -10
- package/templates/skills/services/influxdb/SKILL.md +10 -10
- package/templates/skills/services/kafka/SKILL.md +10 -10
- package/templates/skills/services/mariadb/SKILL.md +10 -10
- package/templates/skills/services/memcached/SKILL.md +10 -10
- package/templates/skills/services/minio/SKILL.md +10 -10
- package/templates/skills/services/mongodb/SKILL.md +10 -10
- package/templates/skills/services/mysql/SKILL.md +10 -10
- package/templates/skills/services/neo4j/SKILL.md +10 -10
- package/templates/skills/services/oracle/SKILL.md +10 -10
- package/templates/skills/services/postgresql/SKILL.md +10 -10
- package/templates/skills/services/rabbitmq/SKILL.md +10 -10
- package/templates/skills/services/redis/SKILL.md +10 -10
- package/templates/skills/services/s3/SKILL.md +10 -10
- package/templates/skills/services/sqlite/SKILL.md +10 -10
- package/templates/skills/services/sqlserver/SKILL.md +10 -10
- package/templates/skills/workflows/ralph/SETUP.md +228 -228
- package/templates/skills/workflows/ralph/SKILL.md +1 -1
- package/templates/skills/workflows/ralph/install.sh +1 -1
package/dist/cli/commands.js
CHANGED
|
@@ -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`));
|
|
@@ -996,7 +998,7 @@ export async function taskArchiveCommand(taskId, skipValidation = false) {
|
|
|
996
998
|
*/
|
|
997
999
|
export async function mcpInitCommand() {
|
|
998
1000
|
const { findRulebookConfig } = await import('../mcp/rulebook-server.js');
|
|
999
|
-
const { existsSync, readFileSync, writeFileSync } = await import('fs');
|
|
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 {
|
|
@@ -1004,16 +1006,24 @@ export async function mcpInitCommand() {
|
|
|
1004
1006
|
const cwd = process.cwd();
|
|
1005
1007
|
let rulebookPath = findRulebookConfig(cwd);
|
|
1006
1008
|
if (!rulebookPath) {
|
|
1007
|
-
// Create new .rulebook
|
|
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(
|
|
1016
|
-
const raw = readFileSync(
|
|
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(
|
|
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
|
|
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(
|
|
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.
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
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
|
|
2190
|
+
status,
|
|
2119
2191
|
ai_tool: tool,
|
|
2120
|
-
execution_time_ms:
|
|
2121
|
-
quality_checks:
|
|
2122
|
-
output_summary: `
|
|
2123
|
-
git_commit:
|
|
2124
|
-
learnings:
|
|
2125
|
-
errors:
|
|
2126
|
-
metadata: {
|
|
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`);
|