@hivehub/rulebook 3.3.1 → 3.4.2
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-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/agents/ralph-parser.d.ts +8 -9
- package/dist/agents/ralph-parser.d.ts.map +1 -1
- package/dist/agents/ralph-parser.js +37 -33
- package/dist/agents/ralph-parser.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +4 -114
- package/dist/cli/commands.js.map +1 -1
- package/dist/core/agent-manager.d.ts +7 -34
- package/dist/core/agent-manager.d.ts.map +1 -1
- package/dist/core/agent-manager.js +7 -224
- package/dist/core/agent-manager.js.map +1 -1
- package/dist/core/cli-bridge.js +1 -1
- package/dist/core/cli-bridge.js.map +1 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +5 -6
- package/dist/core/generator.js.map +1 -1
- package/dist/core/logger.js +1 -1
- package/dist/core/logger.js.map +1 -1
- package/dist/core/migrator.js +1 -1
- package/dist/core/migrator.js.map +1 -1
- package/dist/core/modern-console.d.ts +1 -2
- package/dist/core/modern-console.d.ts.map +1 -1
- package/dist/core/modern-console.js +6 -18
- package/dist/core/modern-console.js.map +1 -1
- package/dist/core/ralph-manager.d.ts +34 -0
- package/dist/core/ralph-manager.d.ts.map +1 -1
- package/dist/core/ralph-manager.js +107 -4
- package/dist/core/ralph-manager.js.map +1 -1
- package/dist/core/task-manager.d.ts +1 -1
- package/dist/core/task-manager.js +1 -1
- package/dist/core/workflow-generator.js +1 -1
- package/dist/core/workflow-generator.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/rulebook-server.d.ts.map +1 -1
- package/dist/mcp/rulebook-server.js +291 -162
- package/dist/mcp/rulebook-server.js.map +1 -1
- package/dist/types.d.ts +0 -32
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -781,11 +781,13 @@ export async function startRulebookMcpServer() {
|
|
|
781
781
|
const configData = await configManager.loadConfig();
|
|
782
782
|
const maxIterations = configData.ralph?.maxIterations || 10;
|
|
783
783
|
const tool = (configData.ralph?.tool || 'claude');
|
|
784
|
-
|
|
784
|
+
// Generate PRD first, then initialize with correct task count
|
|
785
785
|
const prd = await prdGenerator.generatePRD(basename(config.projectRoot) || 'project');
|
|
786
786
|
const { writeFile } = await import('../utils/file-system.js');
|
|
787
787
|
const prdPath = join(config.projectRoot, '.rulebook', 'ralph', 'prd.json');
|
|
788
788
|
await writeFile(prdPath, JSON.stringify(prd, null, 2));
|
|
789
|
+
// Initialize after PRD is written so task count is correct
|
|
790
|
+
await ralphManager.initialize(maxIterations, tool);
|
|
789
791
|
return {
|
|
790
792
|
content: [
|
|
791
793
|
{
|
|
@@ -826,182 +828,297 @@ export async function startRulebookMcpServer() {
|
|
|
826
828
|
const { RalphManager } = await import('../core/ralph-manager.js');
|
|
827
829
|
const { RalphParser } = await import('../agents/ralph-parser.js');
|
|
828
830
|
const { spawn } = await import('child_process');
|
|
831
|
+
const { execSync } = await import('child_process');
|
|
829
832
|
const logger = new Logger(config.projectRoot);
|
|
830
833
|
const ralphManager = new RalphManager(config.projectRoot, logger);
|
|
831
834
|
const configData = await configManager.loadConfig();
|
|
832
835
|
const maxIterations = args.maxIterations || configData.ralph?.maxIterations || 10;
|
|
833
836
|
const tool = (args.tool || configData.ralph?.tool || 'claude');
|
|
834
|
-
//
|
|
835
|
-
const
|
|
836
|
-
if (!
|
|
837
|
-
await ralphManager.
|
|
837
|
+
// ── Concurrency guard: prevent multiple simultaneous Ralph runs ──
|
|
838
|
+
const lockAcquired = await ralphManager.acquireLock(tool);
|
|
839
|
+
if (!lockAcquired) {
|
|
840
|
+
const lockInfo = await ralphManager.getLockInfo();
|
|
841
|
+
return {
|
|
842
|
+
content: [
|
|
843
|
+
{
|
|
844
|
+
type: 'text',
|
|
845
|
+
text: JSON.stringify({
|
|
846
|
+
success: false,
|
|
847
|
+
error: `Ralph is already running (PID ${lockInfo?.pid}, started ${lockInfo?.startedAt}, task: ${lockInfo?.currentTask || 'starting'}, iteration: ${lockInfo?.iteration || 0}). Wait for it to finish or check ralph_status. Do NOT start another run.`,
|
|
848
|
+
}),
|
|
849
|
+
},
|
|
850
|
+
],
|
|
851
|
+
};
|
|
838
852
|
}
|
|
839
|
-
//
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
let stderr = '';
|
|
843
|
-
const proc = spawn(cmd, cmdArgs, {
|
|
844
|
-
cwd: config.projectRoot,
|
|
845
|
-
shell: true,
|
|
846
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
847
|
-
});
|
|
848
|
-
proc.stdout?.on('data', (d) => {
|
|
849
|
-
stdout += d.toString();
|
|
850
|
-
});
|
|
851
|
-
proc.stderr?.on('data', (d) => {
|
|
852
|
-
stderr += d.toString();
|
|
853
|
-
});
|
|
854
|
-
proc.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
|
855
|
-
proc.on('error', (err) => resolve({ code: 1, stdout, stderr: err.message }));
|
|
856
|
-
});
|
|
857
|
-
// Helper: build prompt for AI agent
|
|
858
|
-
const buildPrompt = (task, prd) => {
|
|
859
|
-
const criteria = (task.acceptanceCriteria || [])
|
|
860
|
-
.map((c) => `- ${c}`)
|
|
861
|
-
.join('\n');
|
|
862
|
-
return [
|
|
863
|
-
`You are working on project: ${prd?.project || 'unknown'}`,
|
|
864
|
-
``,
|
|
865
|
-
`## Current Task: ${task.title}`,
|
|
866
|
-
`ID: ${task.id}`,
|
|
867
|
-
``,
|
|
868
|
-
`## Description`,
|
|
869
|
-
task.description,
|
|
870
|
-
``,
|
|
871
|
-
`## Acceptance Criteria`,
|
|
872
|
-
criteria,
|
|
873
|
-
``,
|
|
874
|
-
task.notes ? `## Notes\n${task.notes}\n` : '',
|
|
875
|
-
`## Instructions`,
|
|
876
|
-
`1. Implement the changes described above`,
|
|
877
|
-
`2. Ensure all acceptance criteria are met`,
|
|
878
|
-
`3. Run quality checks: type-check, lint, tests`,
|
|
879
|
-
`4. Fix any issues found by quality checks`,
|
|
880
|
-
`5. When done, summarize what was changed`,
|
|
881
|
-
]
|
|
882
|
-
.filter(Boolean)
|
|
883
|
-
.join('\n');
|
|
853
|
+
// Ensure lock is released on exit, even on crashes
|
|
854
|
+
const cleanupLock = async () => {
|
|
855
|
+
await ralphManager.releaseLock();
|
|
884
856
|
};
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
},
|
|
894
|
-
amp: { cmd: 'amp', args: ['-p', prompt], stdinPrompt: false },
|
|
895
|
-
gemini: { cmd: 'gemini', args: ['-p', prompt], stdinPrompt: false },
|
|
857
|
+
process.on('SIGTERM', cleanupLock);
|
|
858
|
+
process.on('SIGINT', cleanupLock);
|
|
859
|
+
try {
|
|
860
|
+
// Validate tool is available before starting
|
|
861
|
+
const toolCmdNames = {
|
|
862
|
+
claude: 'claude',
|
|
863
|
+
amp: 'amp',
|
|
864
|
+
gemini: 'gemini',
|
|
896
865
|
};
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
866
|
+
const toolCmd = toolCmdNames[tool] || 'claude';
|
|
867
|
+
try {
|
|
868
|
+
execSync(`${toolCmd} --version`, { stdio: 'pipe', timeout: 10000 });
|
|
869
|
+
}
|
|
870
|
+
catch {
|
|
871
|
+
return {
|
|
872
|
+
content: [
|
|
873
|
+
{
|
|
874
|
+
type: 'text',
|
|
875
|
+
text: JSON.stringify({
|
|
876
|
+
success: false,
|
|
877
|
+
error: `CLI tool "${toolCmd}" not found or not responding. Install it first: https://docs.anthropic.com/claude-code`,
|
|
878
|
+
}),
|
|
879
|
+
},
|
|
880
|
+
],
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
// Resume existing state if available, otherwise initialize fresh
|
|
884
|
+
const existingState = await ralphManager.getStatus();
|
|
885
|
+
if (!existingState) {
|
|
886
|
+
await ralphManager.initialize(maxIterations, tool);
|
|
906
887
|
}
|
|
907
|
-
|
|
908
|
-
|
|
888
|
+
// Helper: run a shell command and return stdout
|
|
889
|
+
const runCmd = (cmd, cmdArgs) => new Promise((resolve) => {
|
|
890
|
+
let stdout = '';
|
|
891
|
+
let stderr = '';
|
|
892
|
+
const proc = spawn(cmd, cmdArgs, {
|
|
893
|
+
cwd: config.projectRoot,
|
|
894
|
+
shell: true,
|
|
895
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
896
|
+
});
|
|
897
|
+
proc.stdout?.on('data', (d) => {
|
|
898
|
+
stdout += d.toString();
|
|
899
|
+
});
|
|
900
|
+
proc.stderr?.on('data', (d) => {
|
|
901
|
+
stderr += d.toString();
|
|
902
|
+
});
|
|
903
|
+
proc.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
|
904
|
+
proc.on('error', (err) => resolve({ code: 1, stdout, stderr: err.message }));
|
|
909
905
|
});
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
906
|
+
// Helper: build prompt for AI agent
|
|
907
|
+
const buildPrompt = (task, projectName) => {
|
|
908
|
+
const criteria = (task.acceptanceCriteria || [])
|
|
909
|
+
.map((c) => `- ${c}`)
|
|
910
|
+
.join('\n');
|
|
911
|
+
return [
|
|
912
|
+
`You are working on project: ${projectName}`,
|
|
913
|
+
``,
|
|
914
|
+
`## Current Task: ${task.title}`,
|
|
915
|
+
`ID: ${task.id}`,
|
|
916
|
+
``,
|
|
917
|
+
`## Description`,
|
|
918
|
+
task.description,
|
|
919
|
+
``,
|
|
920
|
+
`## Acceptance Criteria`,
|
|
921
|
+
criteria,
|
|
922
|
+
``,
|
|
923
|
+
task.notes ? `## Notes\n${task.notes}\n` : '',
|
|
924
|
+
`## Instructions`,
|
|
925
|
+
`1. Implement the changes described above`,
|
|
926
|
+
`2. Ensure all acceptance criteria are met`,
|
|
927
|
+
`3. Run quality checks: type-check, lint, tests`,
|
|
928
|
+
`4. Fix any issues found by quality checks`,
|
|
929
|
+
`5. When done, summarize what was changed`,
|
|
930
|
+
]
|
|
931
|
+
.filter(Boolean)
|
|
932
|
+
.join('\n');
|
|
933
|
+
};
|
|
934
|
+
// Helper: execute AI agent with proper error handling
|
|
935
|
+
const executeAgent = (agentTool, prompt) => new Promise((resolve, reject) => {
|
|
936
|
+
let output = '';
|
|
937
|
+
let stderrOutput = '';
|
|
938
|
+
const toolCmds = {
|
|
939
|
+
claude: {
|
|
940
|
+
cmd: 'claude',
|
|
941
|
+
args: ['-p', '--dangerously-skip-permissions', '--verbose'],
|
|
942
|
+
stdinPrompt: true,
|
|
943
|
+
},
|
|
944
|
+
amp: { cmd: 'amp', args: ['-p', prompt], stdinPrompt: false },
|
|
945
|
+
gemini: { cmd: 'gemini', args: ['-p', prompt], stdinPrompt: false },
|
|
946
|
+
};
|
|
947
|
+
const cfg = toolCmds[agentTool] || toolCmds.claude;
|
|
948
|
+
let settled = false;
|
|
949
|
+
const settle = (fn) => {
|
|
950
|
+
if (!settled) {
|
|
951
|
+
settled = true;
|
|
952
|
+
fn();
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
const proc = spawn(cfg.cmd, cfg.args, {
|
|
956
|
+
cwd: config.projectRoot,
|
|
957
|
+
shell: true,
|
|
958
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
959
|
+
});
|
|
960
|
+
if (cfg.stdinPrompt && proc.stdin) {
|
|
961
|
+
proc.stdin.write(prompt);
|
|
962
|
+
proc.stdin.end();
|
|
963
|
+
}
|
|
964
|
+
proc.stdout?.on('data', (d) => {
|
|
965
|
+
output += d.toString();
|
|
966
|
+
});
|
|
967
|
+
proc.stderr?.on('data', (d) => {
|
|
968
|
+
stderrOutput += d.toString();
|
|
969
|
+
});
|
|
970
|
+
proc.on('close', (code) => {
|
|
971
|
+
settle(() => {
|
|
972
|
+
if (code === 0 || output.length > 0) {
|
|
973
|
+
resolve(output);
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
reject(new Error(`Agent ${agentTool} exited with code ${code}${stderrOutput ? ': ' + stderrOutput.slice(0, 500) : ''}`));
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
});
|
|
980
|
+
proc.on('error', (err) => {
|
|
981
|
+
settle(() => reject(new Error(`Failed to spawn ${agentTool}: ${err.message}`)));
|
|
982
|
+
});
|
|
983
|
+
const timeout = setTimeout(() => {
|
|
984
|
+
proc.kill('SIGTERM');
|
|
985
|
+
settle(() => resolve(output || `Agent ${agentTool} timed out after 10 minutes`));
|
|
986
|
+
}, 600000);
|
|
987
|
+
proc.on('close', () => clearTimeout(timeout));
|
|
916
988
|
});
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
// 1. Execute AI agent
|
|
934
|
-
let agentOutput = '';
|
|
935
|
-
try {
|
|
936
|
-
const prompt = buildPrompt(task, prd);
|
|
937
|
-
agentOutput = await executeAgent(tool, prompt);
|
|
938
|
-
}
|
|
939
|
-
catch (agentErr) {
|
|
940
|
-
agentOutput = `Error: ${agentErr.message || agentErr}`;
|
|
989
|
+
// Sync task count from PRD
|
|
990
|
+
await ralphManager.refreshTaskCount();
|
|
991
|
+
// Load PRD for project name (used in prompts)
|
|
992
|
+
const prd = await ralphManager.loadPRD();
|
|
993
|
+
if (!prd || !prd.userStories || prd.userStories.length === 0) {
|
|
994
|
+
return {
|
|
995
|
+
content: [
|
|
996
|
+
{
|
|
997
|
+
type: 'text',
|
|
998
|
+
text: JSON.stringify({
|
|
999
|
+
success: false,
|
|
1000
|
+
error: 'No PRD found or no user stories. Run rulebook_ralph_init first.',
|
|
1001
|
+
}),
|
|
1002
|
+
},
|
|
1003
|
+
],
|
|
1004
|
+
};
|
|
941
1005
|
}
|
|
942
|
-
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
`
|
|
1006
|
+
const projectName = prd.project || 'unknown';
|
|
1007
|
+
const totalTasks = prd.userStories.filter((s) => !s.passes).length;
|
|
1008
|
+
let iterationCount = 0;
|
|
1009
|
+
const iterationResults = [];
|
|
1010
|
+
// Log to stderr so MCP callers can see progress
|
|
1011
|
+
const logProgress = (msg) => {
|
|
1012
|
+
process.stderr.write(`[Ralph] ${msg}\n`);
|
|
1013
|
+
};
|
|
1014
|
+
logProgress(`Starting Ralph loop: ${totalTasks} pending tasks, max ${maxIterations} iterations, tool=${tool}`);
|
|
1015
|
+
while (ralphManager.canContinue() && iterationCount < maxIterations) {
|
|
1016
|
+
iterationCount++;
|
|
1017
|
+
const task = await ralphManager.getNextTask();
|
|
1018
|
+
if (!task)
|
|
1019
|
+
break;
|
|
1020
|
+
// Update lock with current progress
|
|
1021
|
+
await ralphManager.updateLockProgress(iterationCount, `${task.id}: ${task.title}`);
|
|
1022
|
+
logProgress(`Iteration ${iterationCount}/${maxIterations} — Task: ${task.id} "${task.title}"`);
|
|
1023
|
+
const startTime = Date.now();
|
|
1024
|
+
// 1. Execute AI agent
|
|
1025
|
+
let agentOutput = '';
|
|
1026
|
+
try {
|
|
1027
|
+
logProgress(` Executing ${tool} agent...`);
|
|
1028
|
+
const prompt = buildPrompt(task, projectName);
|
|
1029
|
+
agentOutput = await executeAgent(tool, prompt);
|
|
1030
|
+
logProgress(` Agent finished (${((Date.now() - startTime) / 1000).toFixed(0)}s)`);
|
|
1031
|
+
}
|
|
1032
|
+
catch (agentErr) {
|
|
1033
|
+
agentOutput = `Error: ${agentErr.message || agentErr}`;
|
|
1034
|
+
logProgress(` Agent error: ${agentErr.message || agentErr}`);
|
|
1035
|
+
}
|
|
1036
|
+
// 2. Run quality gates
|
|
1037
|
+
logProgress(` Running quality gates...`);
|
|
1038
|
+
const [typeCheck, lint, tests] = await Promise.all([
|
|
1039
|
+
runCmd('npm', ['run', 'type-check']).then((r) => r.code === 0),
|
|
1040
|
+
runCmd('npm', ['run', 'lint']).then((r) => r.code === 0),
|
|
1041
|
+
runCmd('npm', ['test']).then((r) => r.code === 0),
|
|
964
1042
|
]);
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
1043
|
+
const qualityChecks = { type_check: typeCheck, lint, tests, coverage_met: tests };
|
|
1044
|
+
const allPass = typeCheck && lint && tests;
|
|
1045
|
+
const passCount = Object.values(qualityChecks).filter(Boolean).length;
|
|
1046
|
+
const status = allPass
|
|
1047
|
+
? 'success'
|
|
1048
|
+
: passCount >= 2
|
|
1049
|
+
? 'partial'
|
|
1050
|
+
: 'failed';
|
|
1051
|
+
logProgress(` Quality: type-check=${typeCheck ? 'PASS' : 'FAIL'} lint=${lint ? 'PASS' : 'FAIL'} tests=${tests ? 'PASS' : 'FAIL'} → ${status.toUpperCase()}`);
|
|
1052
|
+
// 3. Git commit if all gates pass
|
|
1053
|
+
let gitCommit;
|
|
1054
|
+
if (allPass) {
|
|
1055
|
+
await runCmd('git', ['add', '-A']);
|
|
1056
|
+
const commitResult = await runCmd('git', [
|
|
1057
|
+
'commit',
|
|
1058
|
+
'-m',
|
|
1059
|
+
`ralph(${task.id}): ${task.title}\n\nIteration ${iterationCount} - Ralph autonomous loop`,
|
|
1060
|
+
]);
|
|
1061
|
+
const hashMatch = commitResult.stdout.match(/\[[\w/.-]+ ([a-f0-9]+)\]/);
|
|
1062
|
+
gitCommit = hashMatch ? hashMatch[1] : undefined;
|
|
1063
|
+
await ralphManager.markStoryComplete(task.id);
|
|
1064
|
+
logProgress(` Committed: ${gitCommit || 'no hash'} — Story ${task.id} COMPLETE`);
|
|
1065
|
+
}
|
|
1066
|
+
const iterDuration = Date.now() - startTime;
|
|
1067
|
+
// 4. Parse output for learnings/errors
|
|
1068
|
+
const parsed = RalphParser.parseAgentOutput(agentOutput, iterationCount, task.id, task.title, tool);
|
|
1069
|
+
// 5. Record iteration and refresh task count for canContinue()
|
|
1070
|
+
await ralphManager.recordIteration({
|
|
1071
|
+
iteration: iterationCount,
|
|
1072
|
+
timestamp: new Date().toISOString(),
|
|
1073
|
+
task_id: task.id,
|
|
1074
|
+
task_title: task.title,
|
|
1075
|
+
status,
|
|
1076
|
+
ai_tool: tool,
|
|
1077
|
+
execution_time_ms: iterDuration,
|
|
1078
|
+
quality_checks: qualityChecks,
|
|
1079
|
+
output_summary: parsed.output_summary || `Iteration ${iterationCount}: ${task.title}`,
|
|
1080
|
+
git_commit: gitCommit,
|
|
1081
|
+
learnings: parsed.learnings,
|
|
1082
|
+
errors: parsed.errors,
|
|
1083
|
+
metadata: {
|
|
1084
|
+
context_loss_count: parsed.metadata.context_loss_count,
|
|
1085
|
+
parsed_completion: parsed.metadata.parsed_completion,
|
|
1086
|
+
},
|
|
1087
|
+
});
|
|
1088
|
+
iterationResults.push({
|
|
1089
|
+
iteration: iterationCount,
|
|
1090
|
+
taskId: task.id,
|
|
1091
|
+
taskTitle: task.title,
|
|
1092
|
+
status,
|
|
1093
|
+
durationMs: iterDuration,
|
|
1094
|
+
});
|
|
1095
|
+
// Refresh task count so canContinue() reflects updated PRD
|
|
1096
|
+
await ralphManager.refreshTaskCount();
|
|
1097
|
+
logProgress(` Iteration ${iterationCount} complete (${(iterDuration / 1000).toFixed(0)}s)\n`);
|
|
968
1098
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1099
|
+
const stats = await ralphManager.getTaskStats();
|
|
1100
|
+
logProgress(`Ralph loop finished: ${iterationCount} iterations, ${stats.completed}/${stats.total} tasks completed`);
|
|
1101
|
+
return {
|
|
1102
|
+
content: [
|
|
1103
|
+
{
|
|
1104
|
+
type: 'text',
|
|
1105
|
+
text: JSON.stringify({
|
|
1106
|
+
success: true,
|
|
1107
|
+
iterations: iterationCount,
|
|
1108
|
+
completed: stats.completed,
|
|
1109
|
+
total: stats.total,
|
|
1110
|
+
results: iterationResults,
|
|
1111
|
+
}),
|
|
1112
|
+
},
|
|
1113
|
+
],
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
finally {
|
|
1117
|
+
// Always release lock, even on error
|
|
1118
|
+
await ralphManager.releaseLock();
|
|
1119
|
+
process.removeListener('SIGTERM', cleanupLock);
|
|
1120
|
+
process.removeListener('SIGINT', cleanupLock);
|
|
990
1121
|
}
|
|
991
|
-
const stats = await ralphManager.getTaskStats();
|
|
992
|
-
return {
|
|
993
|
-
content: [
|
|
994
|
-
{
|
|
995
|
-
type: 'text',
|
|
996
|
-
text: JSON.stringify({
|
|
997
|
-
success: true,
|
|
998
|
-
iterations: iterationCount,
|
|
999
|
-
completed: stats.completed,
|
|
1000
|
-
total: stats.total,
|
|
1001
|
-
}),
|
|
1002
|
-
},
|
|
1003
|
-
],
|
|
1004
|
-
};
|
|
1005
1122
|
}
|
|
1006
1123
|
catch (error) {
|
|
1007
1124
|
return {
|
|
@@ -1037,12 +1154,24 @@ export async function startRulebookMcpServer() {
|
|
|
1037
1154
|
};
|
|
1038
1155
|
}
|
|
1039
1156
|
const stats = await ralphManager.getTaskStats();
|
|
1157
|
+
// Check if Ralph is currently running (lock held by alive process)
|
|
1158
|
+
const running = await ralphManager.isRunning();
|
|
1159
|
+
const lockInfo = running ? await ralphManager.getLockInfo() : null;
|
|
1040
1160
|
return {
|
|
1041
1161
|
content: [
|
|
1042
1162
|
{
|
|
1043
1163
|
type: 'text',
|
|
1044
1164
|
text: JSON.stringify({
|
|
1045
1165
|
success: true,
|
|
1166
|
+
running,
|
|
1167
|
+
...(running && lockInfo
|
|
1168
|
+
? {
|
|
1169
|
+
runningPid: lockInfo.pid,
|
|
1170
|
+
runningTask: lockInfo.currentTask || null,
|
|
1171
|
+
runningIteration: lockInfo.iteration || 0,
|
|
1172
|
+
runningSince: lockInfo.startedAt,
|
|
1173
|
+
}
|
|
1174
|
+
: {}),
|
|
1046
1175
|
iteration: status.current_iteration,
|
|
1047
1176
|
maxIterations: status.max_iterations,
|
|
1048
1177
|
completedTasks: stats.completed,
|