@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.
- package/.claude/commands/continue.md +33 -0
- package/.claude-plugin/marketplace.json +28 -29
- package/.claude-plugin/plugin.json +8 -8
- package/README.md +66 -144
- package/dist/agents/ralph-parser.d.ts +41 -1
- package/dist/agents/ralph-parser.d.ts.map +1 -1
- package/dist/agents/ralph-parser.js +202 -14
- package/dist/agents/ralph-parser.js.map +1 -1
- package/dist/cli/commands.d.ts +65 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +837 -61
- package/dist/cli/commands.js.map +1 -1
- package/dist/core/agent-manager.d.ts +7 -0
- package/dist/core/agent-manager.d.ts.map +1 -1
- package/dist/core/agent-manager.js +150 -3
- package/dist/core/agent-manager.js.map +1 -1
- package/dist/core/claude-mcp.d.ts +17 -0
- package/dist/core/claude-mcp.d.ts.map +1 -1
- package/dist/core/claude-mcp.js +90 -6
- package/dist/core/claude-mcp.js.map +1 -1
- package/dist/core/config-manager.d.ts.map +1 -1
- package/dist/core/config-manager.js +40 -0
- package/dist/core/config-manager.js.map +1 -1
- package/dist/core/cursor-mdc-generator.d.ts +30 -0
- package/dist/core/cursor-mdc-generator.d.ts.map +1 -0
- package/dist/core/cursor-mdc-generator.js +98 -0
- package/dist/core/cursor-mdc-generator.js.map +1 -0
- package/dist/core/detector.d.ts +25 -1
- package/dist/core/detector.d.ts.map +1 -1
- package/dist/core/detector.js +321 -1
- package/dist/core/detector.js.map +1 -1
- package/dist/core/generator.d.ts +10 -0
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +177 -3
- package/dist/core/generator.js.map +1 -1
- package/dist/core/github-issues-importer.d.ts +82 -0
- package/dist/core/github-issues-importer.d.ts.map +1 -0
- package/dist/core/github-issues-importer.js +161 -0
- package/dist/core/github-issues-importer.js.map +1 -0
- package/dist/core/health-scorer.d.ts +39 -0
- package/dist/core/health-scorer.d.ts.map +1 -1
- package/dist/core/health-scorer.js +256 -13
- package/dist/core/health-scorer.js.map +1 -1
- package/dist/core/indexer/background-indexer.d.ts +27 -0
- package/dist/core/indexer/background-indexer.d.ts.map +1 -0
- package/dist/core/indexer/background-indexer.js +135 -0
- package/dist/core/indexer/background-indexer.js.map +1 -0
- package/dist/core/indexer/file-parser.d.ts +28 -0
- package/dist/core/indexer/file-parser.d.ts.map +1 -0
- package/dist/core/indexer/file-parser.js +171 -0
- package/dist/core/indexer/file-parser.js.map +1 -0
- package/dist/core/indexer/indexer-types.d.ts +35 -0
- package/dist/core/indexer/indexer-types.d.ts.map +1 -0
- package/dist/core/indexer/indexer-types.js +8 -0
- package/dist/core/indexer/indexer-types.js.map +1 -0
- package/dist/core/iteration-tracker.d.ts +28 -0
- package/dist/core/iteration-tracker.d.ts.map +1 -1
- package/dist/core/iteration-tracker.js +86 -0
- package/dist/core/iteration-tracker.js.map +1 -1
- package/dist/core/multi-tool-generator.d.ts +59 -0
- package/dist/core/multi-tool-generator.d.ts.map +1 -0
- package/dist/core/multi-tool-generator.js +157 -0
- package/dist/core/multi-tool-generator.js.map +1 -0
- package/dist/core/override-manager.d.ts +23 -0
- package/dist/core/override-manager.d.ts.map +1 -0
- package/dist/core/override-manager.js +82 -0
- package/dist/core/override-manager.js.map +1 -0
- package/dist/core/plans-manager.d.ts +46 -0
- package/dist/core/plans-manager.d.ts.map +1 -0
- package/dist/core/plans-manager.js +158 -0
- package/dist/core/plans-manager.js.map +1 -0
- package/dist/core/prd-generator.d.ts +12 -0
- package/dist/core/prd-generator.d.ts.map +1 -1
- package/dist/core/prd-generator.js +91 -2
- package/dist/core/prd-generator.js.map +1 -1
- package/dist/core/ralph-manager.d.ts +47 -1
- package/dist/core/ralph-manager.d.ts.map +1 -1
- package/dist/core/ralph-manager.js +107 -0
- package/dist/core/ralph-manager.js.map +1 -1
- package/dist/core/ralph-parallel.d.ts +55 -0
- package/dist/core/ralph-parallel.d.ts.map +1 -0
- package/dist/core/ralph-parallel.js +201 -0
- package/dist/core/ralph-parallel.js.map +1 -0
- package/dist/core/ralph-plan-checkpoint.d.ts +58 -0
- package/dist/core/ralph-plan-checkpoint.d.ts.map +1 -0
- package/dist/core/ralph-plan-checkpoint.js +154 -0
- package/dist/core/ralph-plan-checkpoint.js.map +1 -0
- package/dist/core/ralph-scripts.d.ts +12 -0
- package/dist/core/ralph-scripts.d.ts.map +1 -0
- package/dist/core/ralph-scripts.js +49 -0
- package/dist/core/ralph-scripts.js.map +1 -0
- package/dist/core/review-manager.d.ts +74 -0
- package/dist/core/review-manager.d.ts.map +1 -0
- package/dist/core/review-manager.js +371 -0
- package/dist/core/review-manager.js.map +1 -0
- package/dist/index.js +94 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/rulebook-server.d.ts.map +1 -1
- package/dist/mcp/rulebook-server.js +117 -8
- package/dist/mcp/rulebook-server.js.map +1 -1
- package/dist/memory/memory-manager.d.ts +4 -1
- package/dist/memory/memory-manager.d.ts.map +1 -1
- package/dist/memory/memory-manager.js +33 -4
- package/dist/memory/memory-manager.js.map +1 -1
- package/dist/memory/memory-search.d.ts +2 -2
- package/dist/memory/memory-search.d.ts.map +1 -1
- package/dist/memory/memory-search.js +19 -0
- package/dist/memory/memory-search.js.map +1 -1
- package/dist/memory/memory-store.d.ts +13 -0
- package/dist/memory/memory-store.d.ts.map +1 -1
- package/dist/memory/memory-store.js +92 -1
- package/dist/memory/memory-store.js.map +1 -1
- package/dist/memory/memory-types.d.ts +15 -0
- package/dist/memory/memory-types.d.ts.map +1 -1
- package/dist/types.d.ts +55 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/agents/implementer.md +35 -0
- package/templates/agents/researcher.md +34 -0
- package/templates/agents/team-lead.md +34 -0
- package/templates/agents/tester.md +42 -0
- package/templates/ci/rulebook-review.yml +26 -0
- package/templates/commands/rulebook-task-archive.md +24 -0
- package/templates/core/AGENTS_LEAN.md +25 -0
- package/templates/core/AGENTS_OVERRIDE.md +16 -0
- package/templates/core/MULTI_AGENT.md +74 -0
- package/templates/core/PLANS.md +28 -0
- package/templates/core/RALPH.md +45 -4
- package/templates/core/RULEBOOK.md +42 -0
- package/templates/ides/CONTINUE_RULES.md +16 -0
- package/templates/ides/COPILOT_INSTRUCTIONS.md +23 -0
- package/templates/ides/GEMINI_RULES.md +17 -0
- package/templates/ides/WINDSURF_RULES.md +14 -0
- package/templates/ides/cursor-mdc/go.mdc +24 -0
- package/templates/ides/cursor-mdc/python.mdc +24 -0
- package/templates/ides/cursor-mdc/quality.mdc +25 -0
- package/templates/ides/cursor-mdc/ralph.mdc +39 -0
- package/templates/ides/cursor-mdc/rulebook.mdc +38 -0
- package/templates/ides/cursor-mdc/rust.mdc +24 -0
- package/templates/ides/cursor-mdc/typescript.mdc +25 -0
- package/templates/modules/sequential-thinking.md +42 -0
- package/templates/ralph/ralph-history.bat +4 -0
- package/templates/ralph/ralph-history.sh +5 -0
- package/templates/ralph/ralph-init.bat +5 -0
- package/templates/ralph/ralph-init.sh +5 -0
- package/templates/ralph/ralph-pause.bat +5 -0
- package/templates/ralph/ralph-pause.sh +5 -0
- package/templates/ralph/ralph-run.bat +5 -0
- package/templates/ralph/ralph-run.sh +5 -0
- package/templates/ralph/ralph-status.bat +4 -0
- package/templates/ralph/ralph-status.sh +5 -0
- package/templates/services/DATADOG.md +26 -0
- package/templates/services/DOCKER.md +124 -0
- package/templates/services/DOCKER_COMPOSE.md +168 -0
- package/templates/services/HELM.md +194 -0
- package/templates/services/KUBERNETES.md +208 -0
- package/templates/services/OPENTELEMETRY.md +25 -0
- package/templates/services/PINO.md +24 -0
- package/templates/services/PROMETHEUS.md +33 -0
- package/templates/services/SENTRY.md +23 -0
- package/templates/services/WINSTON.md +30 -0
- package/dist/core/openspec-manager.d.ts +0 -133
- package/dist/core/openspec-manager.d.ts.map +0 -1
- package/dist/core/openspec-manager.js +0 -596
- package/dist/core/openspec-manager.js.map +0 -1
- package/dist/core/openspec-migrator.d.ts +0 -27
- package/dist/core/openspec-migrator.d.ts.map +0 -1
- package/dist/core/openspec-migrator.js +0 -262
- package/dist/core/openspec-migrator.js.map +0 -1
- package/dist/core/test-task-manager.d.ts +0 -49
- package/dist/core/test-task-manager.d.ts.map +0 -1
- package/dist/core/test-task-manager.js +0 -121
- package/dist/core/test-task-manager.js.map +0 -1
package/dist/cli/commands.js
CHANGED
|
@@ -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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|