ralphctl 0.4.5 → 0.5.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/dist/{add-YVXM34RP.mjs → add-67UFUI54.mjs} +2 -2
- package/dist/{chunk-ZLWSPLWI.mjs → chunk-62HYDA7L.mjs} +11 -0
- package/dist/{chunk-PYZEQ2VK.mjs → chunk-BT5FKIZX.mjs} +1 -1
- package/dist/{chunk-OFILN7QL.mjs → chunk-D6QZNEYN.mjs} +226 -154
- package/dist/{chunk-XPLYLRIM.mjs → chunk-ZE2BRQA2.mjs} +20 -10
- package/dist/cli.mjs +7 -7
- package/dist/{mount-H2IH3MWE.mjs → mount-NCYR22SN.mjs} +60 -16
- package/dist/{start-2WH4BTDB.mjs → start-T34NI3LF.mjs} +2 -2
- package/package.json +1 -1
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
addCheckScriptToRepository,
|
|
4
4
|
projectAddCommand
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-BT5FKIZX.mjs";
|
|
6
|
+
import "./chunk-62HYDA7L.mjs";
|
|
7
7
|
import "./chunk-CFUVE2BP.mjs";
|
|
8
8
|
import "./chunk-747KW2RW.mjs";
|
|
9
9
|
import "./chunk-BSB4EDGR.mjs";
|
|
@@ -1091,6 +1091,16 @@ ${ctx.existingAgentsMd}
|
|
|
1091
1091
|
FILE_NAME: ctx.fileName
|
|
1092
1092
|
});
|
|
1093
1093
|
}
|
|
1094
|
+
function buildEvaluationResumePrompt(ctx) {
|
|
1095
|
+
const template = loadTemplate("task-evaluation-resume");
|
|
1096
|
+
const commitInstruction = ctx.needsCommit ? "\n - **Then commit the fix** with a descriptive message before signaling completion." : "";
|
|
1097
|
+
return composePrompt(template, {
|
|
1098
|
+
HARNESS_CONTEXT: loadPartial("harness-context"),
|
|
1099
|
+
SIGNALS: loadPartial("signals-task"),
|
|
1100
|
+
CRITIQUE: ctx.critique,
|
|
1101
|
+
COMMIT_INSTRUCTION: commitInstruction
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1094
1104
|
|
|
1095
1105
|
export {
|
|
1096
1106
|
buildInteractivePrompt,
|
|
@@ -1103,6 +1113,7 @@ export {
|
|
|
1103
1113
|
buildSprintFeedbackPrompt,
|
|
1104
1114
|
buildCheckScriptDiscoverPrompt,
|
|
1105
1115
|
buildRepoOnboardPrompt,
|
|
1116
|
+
buildEvaluationResumePrompt,
|
|
1106
1117
|
processLifecycleAdapter,
|
|
1107
1118
|
resolveProvider,
|
|
1108
1119
|
providerDisplayName,
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ProviderAiSessionAdapter,
|
|
4
4
|
SignalParser,
|
|
5
5
|
buildAutoPrompt,
|
|
6
|
+
buildEvaluationResumePrompt,
|
|
6
7
|
buildEvaluatorPrompt,
|
|
7
8
|
buildIdeateAutoPrompt,
|
|
8
9
|
buildIdeatePrompt,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
buildTicketRefinePrompt,
|
|
14
15
|
getActiveProvider,
|
|
15
16
|
spawnInteractive
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-62HYDA7L.mjs";
|
|
17
18
|
import {
|
|
18
19
|
fetchIssueFromUrl,
|
|
19
20
|
formatIssueContext,
|
|
@@ -1626,6 +1627,14 @@ function resolveCheckScriptForRepo(repo) {
|
|
|
1626
1627
|
}
|
|
1627
1628
|
|
|
1628
1629
|
// src/business/pipelines/steps/run-check-scripts.ts
|
|
1630
|
+
var ERROR_OUTPUT_TAIL_LINES = 100;
|
|
1631
|
+
function tailOutput(output, maxLines = ERROR_OUTPUT_TAIL_LINES) {
|
|
1632
|
+
const lines = output.split("\n");
|
|
1633
|
+
if (lines.length <= maxLines) return output;
|
|
1634
|
+
const hidden = lines.length - maxLines;
|
|
1635
|
+
return `[${String(hidden)} earlier line${hidden !== 1 ? "s" : ""} omitted]
|
|
1636
|
+
${lines.slice(-maxLines).join("\n")}`;
|
|
1637
|
+
}
|
|
1629
1638
|
function runCheckScriptsStep(external, persistence, mode, options) {
|
|
1630
1639
|
return step("run-check-scripts", async (ctx) => {
|
|
1631
1640
|
const sprint = ctx.sprint;
|
|
@@ -1646,15 +1655,17 @@ function runCheckScriptsStep(external, persistence, mode, options) {
|
|
|
1646
1655
|
const checkScript = resolveCheckScriptForRepo(resolved?.repo);
|
|
1647
1656
|
if (!resolved || !checkScript) continue;
|
|
1648
1657
|
const { repo } = resolved;
|
|
1649
|
-
const result = external.runCheckScript(repo.path, checkScript, "sprintStart", repo.checkTimeout);
|
|
1658
|
+
const result = await external.runCheckScript(repo.path, checkScript, "sprintStart", repo.checkTimeout);
|
|
1650
1659
|
if (!result.passed) {
|
|
1651
1660
|
checkResults[repoId] = {
|
|
1652
1661
|
projectPath: repo.path,
|
|
1653
1662
|
success: false,
|
|
1654
1663
|
output: result.output
|
|
1655
1664
|
};
|
|
1656
|
-
return Result.error(
|
|
1657
|
-
${
|
|
1665
|
+
return Result.error(
|
|
1666
|
+
new StorageError(`Check failed for ${repo.path}: ${checkScript}
|
|
1667
|
+
${tailOutput(result.output)}`)
|
|
1668
|
+
);
|
|
1658
1669
|
}
|
|
1659
1670
|
const ranAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1660
1671
|
sprint.checkRanAt[repoId] = ranAt;
|
|
@@ -1680,7 +1691,7 @@ ${result.output}`));
|
|
|
1680
1691
|
return Result.ok(partial2);
|
|
1681
1692
|
}
|
|
1682
1693
|
const { repo } = resolved;
|
|
1683
|
-
const result = external.runCheckScript(repo.path, checkScript, "taskComplete", repo.checkTimeout);
|
|
1694
|
+
const result = await external.runCheckScript(repo.path, checkScript, "taskComplete", repo.checkTimeout);
|
|
1684
1695
|
checkResults[targetRepoId] = {
|
|
1685
1696
|
projectPath: repo.path,
|
|
1686
1697
|
success: result.passed,
|
|
@@ -1689,7 +1700,7 @@ ${result.output}`));
|
|
|
1689
1700
|
if (!result.passed) {
|
|
1690
1701
|
return Result.error(
|
|
1691
1702
|
new StorageError(`Post-task check failed for ${repo.path}: ${checkScript}
|
|
1692
|
-
${result.output}`)
|
|
1703
|
+
${tailOutput(result.output)}`)
|
|
1693
1704
|
);
|
|
1694
1705
|
}
|
|
1695
1706
|
}
|
|
@@ -1892,7 +1903,7 @@ ${instructions}`;
|
|
|
1892
1903
|
if (!resolved || !checkScript) return true;
|
|
1893
1904
|
this.logger.info(`Running post-task check: ${checkScript}`);
|
|
1894
1905
|
const { repo } = resolved;
|
|
1895
|
-
const result = this.external.runCheckScript(repo.path, checkScript, "taskComplete", repo.checkTimeout);
|
|
1906
|
+
const result = await this.external.runCheckScript(repo.path, checkScript, "taskComplete", repo.checkTimeout);
|
|
1896
1907
|
if (result.passed) {
|
|
1897
1908
|
this.logger.success("Post-task check: passed");
|
|
1898
1909
|
}
|
|
@@ -2478,10 +2489,9 @@ var EvaluateTaskUseCase = class {
|
|
|
2478
2489
|
options
|
|
2479
2490
|
);
|
|
2480
2491
|
if (!fixSuccess) {
|
|
2481
|
-
|
|
2482
|
-
await this.persistEvaluationStub(sprintId, taskId, i + 2, reason);
|
|
2483
|
-
break;
|
|
2492
|
+
log2.debug(`Fix attempt ${String(i + 1)}: generator did not signal completion \u2014 re-evaluating anyway`);
|
|
2484
2493
|
}
|
|
2494
|
+
if (i === maxIterations - 1) break;
|
|
2485
2495
|
const previousEvalResult = evalResult;
|
|
2486
2496
|
const stopReeval = log2.time("evaluator-re-spawn");
|
|
2487
2497
|
evalResult = await this.runSingleEvaluation(
|
|
@@ -2505,7 +2515,7 @@ var EvaluateTaskUseCase = class {
|
|
|
2505
2515
|
}
|
|
2506
2516
|
const finalStatus = plateaued ? "plateau" : evalResult.status;
|
|
2507
2517
|
await this.updateTaskEvaluation(sprintId, taskId, evalResult, finalStatus);
|
|
2508
|
-
this.reportResult(task.name, evalResult,
|
|
2518
|
+
this.reportResult(task.name, evalResult, totalIterations, plateaued);
|
|
2509
2519
|
return Result.ok({
|
|
2510
2520
|
taskId,
|
|
2511
2521
|
status: finalStatus,
|
|
@@ -2549,47 +2559,47 @@ var EvaluateTaskUseCase = class {
|
|
|
2549
2559
|
}
|
|
2550
2560
|
}
|
|
2551
2561
|
/**
|
|
2552
|
-
* Spawn a single evaluator session and parse the result.
|
|
2553
|
-
*
|
|
2554
|
-
*
|
|
2555
|
-
* pre-resolved — both are stable across fix-loop iterations, so the
|
|
2556
|
-
* caller computes them once and threads them through.
|
|
2562
|
+
* Spawn a single evaluator session and parse the result. Stable inputs
|
|
2563
|
+
* (`checkScriptSection`, `projectToolingSection`) are passed in
|
|
2564
|
+
* pre-resolved so the fix loop doesn't re-compute them per iteration.
|
|
2557
2565
|
*/
|
|
2558
2566
|
async runSingleEvaluation(task, sprint, repoPath, generatorModel, provider, checkScriptSection, projectToolingSection, options) {
|
|
2559
2567
|
const evaluatorModel = getEvaluatorModel(generatorModel, provider);
|
|
2560
|
-
const sprintDir = this.fs.getSprintDir(sprint.id);
|
|
2561
2568
|
const prompt = this.promptBuilder.buildTaskEvaluationPrompt(
|
|
2562
2569
|
task,
|
|
2563
2570
|
repoPath,
|
|
2564
2571
|
checkScriptSection,
|
|
2565
2572
|
projectToolingSection
|
|
2566
2573
|
);
|
|
2567
|
-
const args = ["--add-dir",
|
|
2574
|
+
const args = ["--add-dir", this.fs.getSprintDir(sprint.id)];
|
|
2568
2575
|
if (provider === "claude") {
|
|
2569
|
-
if (evaluatorModel)
|
|
2570
|
-
args.push("--model", evaluatorModel);
|
|
2571
|
-
}
|
|
2576
|
+
if (evaluatorModel) args.push("--model", evaluatorModel);
|
|
2572
2577
|
args.push("--max-turns", String(options?.maxTurns ?? EVALUATOR_MAX_TURNS));
|
|
2573
2578
|
}
|
|
2574
|
-
|
|
2579
|
+
const result = await this.spawnOrNull(prompt, {
|
|
2580
|
+
cwd: repoPath,
|
|
2581
|
+
args,
|
|
2582
|
+
env: this.aiSession.getSpawnEnv(),
|
|
2583
|
+
abortSignal: options?.abortSignal
|
|
2584
|
+
});
|
|
2585
|
+
if (!result.ok) {
|
|
2586
|
+
this.logger.warning(`Evaluator spawn failed for ${task.name}: ${result.message} \u2014 marking malformed`);
|
|
2587
|
+
return { status: "malformed", dimensions: [], rawOutput: `Evaluator spawn failed: ${result.message}` };
|
|
2588
|
+
}
|
|
2589
|
+
return this.parser.parseEvaluation(result.value.output);
|
|
2590
|
+
}
|
|
2591
|
+
/**
|
|
2592
|
+
* Wrap `spawnWithRetry` in a try/catch so callers can handle spawn
|
|
2593
|
+
* failures without nested error handling. Returns a small discriminated
|
|
2594
|
+
* union — ok with the session result, or !ok with the message.
|
|
2595
|
+
*/
|
|
2596
|
+
async spawnOrNull(prompt, opts) {
|
|
2575
2597
|
try {
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
args,
|
|
2579
|
-
env: this.aiSession.getSpawnEnv(),
|
|
2580
|
-
abortSignal: options?.abortSignal
|
|
2581
|
-
});
|
|
2598
|
+
const value = await this.aiSession.spawnWithRetry(prompt, opts);
|
|
2599
|
+
return { ok: true, value };
|
|
2582
2600
|
} catch (err) {
|
|
2583
|
-
|
|
2584
|
-
`Evaluator spawn failed for ${task.name}: ${err instanceof Error ? err.message : String(err)} \u2014 marking malformed`
|
|
2585
|
-
);
|
|
2586
|
-
return {
|
|
2587
|
-
status: "malformed",
|
|
2588
|
-
dimensions: [],
|
|
2589
|
-
rawOutput: `Evaluator spawn failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2590
|
-
};
|
|
2601
|
+
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
2591
2602
|
}
|
|
2592
|
-
return this.parser.parseEvaluation(result.output);
|
|
2593
2603
|
}
|
|
2594
2604
|
/**
|
|
2595
2605
|
* Resolve the repo's `checkScript` and render it as the evaluator's
|
|
@@ -2618,33 +2628,39 @@ var EvaluateTaskUseCase = class {
|
|
|
2618
2628
|
}
|
|
2619
2629
|
/**
|
|
2620
2630
|
* Resume the generator session with the evaluator critique.
|
|
2621
|
-
*
|
|
2631
|
+
*
|
|
2632
|
+
* Two load-bearing properties (covered by `fix-loop fence` tests):
|
|
2633
|
+
* 1. Prompt comes from `buildTaskEvaluationResumePrompt` — full template
|
|
2634
|
+
* with signals / fix-protocol / commit instruction. A regression to
|
|
2635
|
+
* an inline string silently drops signal requirements.
|
|
2636
|
+
* 2. When `options.generatorSessionId` is set, `resumeSessionId` is
|
|
2637
|
+
* threaded so the fix continues the original session (`--resume`).
|
|
2638
|
+
* Absent an ID, spawn fresh and log at debug.
|
|
2639
|
+
*
|
|
2640
|
+
* Returns true iff the generator signaled `<task-complete>` — used as a
|
|
2641
|
+
* diagnostic only; the evaluator settles whether the fix actually worked.
|
|
2622
2642
|
*/
|
|
2623
2643
|
async resumeGeneratorWithCritique(task, sprint, repoPath, critique, options) {
|
|
2624
|
-
const
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
});
|
|
2641
|
-
spinner.succeed(`Fix attempt completed: ${task.name}`);
|
|
2642
|
-
const signals = this.parser.parseExecutionSignals(result.output);
|
|
2643
|
-
return signals.complete;
|
|
2644
|
-
} catch {
|
|
2645
|
-
spinner?.fail(`Fix attempt failed: ${task.name}`);
|
|
2644
|
+
const resumePrompt = this.promptBuilder.buildTaskEvaluationResumePrompt(critique, options?.needsCommit ?? true);
|
|
2645
|
+
const resumeSessionId = options?.generatorSessionId;
|
|
2646
|
+
this.logger.debug(
|
|
2647
|
+
resumeSessionId ? `Resuming generator session ${resumeSessionId} for fix attempt: ${task.name}` : `No generator session ID \u2014 spawning fresh fix attempt: ${task.name}`
|
|
2648
|
+
);
|
|
2649
|
+
const spinner = this.logger.spinner(`Fixing evaluation issues: ${task.name}`);
|
|
2650
|
+
const result = await this.spawnOrNull(resumePrompt, {
|
|
2651
|
+
cwd: repoPath,
|
|
2652
|
+
args: ["--add-dir", this.fs.getSprintDir(sprint.id)],
|
|
2653
|
+
env: this.aiSession.getSpawnEnv(),
|
|
2654
|
+
maxTurns: options?.maxTurns,
|
|
2655
|
+
resumeSessionId,
|
|
2656
|
+
abortSignal: options?.abortSignal
|
|
2657
|
+
});
|
|
2658
|
+
if (!result.ok) {
|
|
2659
|
+
spinner.fail(`Fix attempt failed: ${task.name}`);
|
|
2646
2660
|
return false;
|
|
2647
2661
|
}
|
|
2662
|
+
spinner.succeed(`Fix attempt completed: ${task.name}`);
|
|
2663
|
+
return this.parser.parseExecutionSignals(result.value.output).complete;
|
|
2648
2664
|
}
|
|
2649
2665
|
/**
|
|
2650
2666
|
* Persist a real evaluation entry to the sidecar file.
|
|
@@ -2664,49 +2680,46 @@ var EvaluateTaskUseCase = class {
|
|
|
2664
2680
|
}
|
|
2665
2681
|
}
|
|
2666
2682
|
/**
|
|
2667
|
-
*
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
await this.persistence.writeEvaluation(sprintId, taskId, iteration, "failed", `_(no re-evaluation: ${reason})_`);
|
|
2672
|
-
} catch {
|
|
2673
|
-
this.logger.warning(`Could not persist evaluation stub for task ${taskId}`);
|
|
2674
|
-
}
|
|
2675
|
-
}
|
|
2676
|
-
/**
|
|
2677
|
-
* Update the task record with evaluation fields.
|
|
2678
|
-
*
|
|
2679
|
-
* `statusOverride` is set when plateau detection fires: the critique body
|
|
2680
|
-
* is still saved (truncated) for traceability, but the discriminator in
|
|
2681
|
-
* `tasks.json` records `'plateau'` so consumers can distinguish it from
|
|
2682
|
-
* a plain `'failed'` run.
|
|
2683
|
+
* Update the task record with evaluation fields. `statusOverride` is set
|
|
2684
|
+
* for plateau — the body is still the real critique, but the status
|
|
2685
|
+
* column records `'plateau'` so readers can distinguish it from a plain
|
|
2686
|
+
* failure.
|
|
2683
2687
|
*/
|
|
2684
2688
|
async updateTaskEvaluation(sprintId, taskId, evalResult, statusOverride) {
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
(t) => t.id === taskId ? {
|
|
2689
|
-
...t,
|
|
2689
|
+
await this.persistence.updateTask(
|
|
2690
|
+
taskId,
|
|
2691
|
+
{
|
|
2690
2692
|
evaluated: true,
|
|
2691
|
-
evaluationStatus: status,
|
|
2693
|
+
evaluationStatus: statusOverride ?? evalResult.status,
|
|
2692
2694
|
evaluationOutput: evalResult.rawOutput.slice(0, MAX_EVAL_OUTPUT)
|
|
2693
|
-
}
|
|
2695
|
+
},
|
|
2696
|
+
sprintId
|
|
2694
2697
|
);
|
|
2695
|
-
await this.persistence.saveTasks(updatedTasks, sprintId);
|
|
2696
2698
|
}
|
|
2697
2699
|
/**
|
|
2698
2700
|
* Report the evaluation outcome to the user.
|
|
2701
|
+
*
|
|
2702
|
+
* `totalIterations` is the *actual* number of evaluator spawns (initial +
|
|
2703
|
+
* any re-evaluations after fix attempts), NOT the configured maximum.
|
|
2704
|
+
* When the loop breaks early (plateau), runs out of fix budget, or skips
|
|
2705
|
+
* the final re-eval, these two diverge — and the log line must reflect
|
|
2706
|
+
* reality so "6 fix attempts" never shows up when only 1 actually ran.
|
|
2707
|
+
*
|
|
2708
|
+
* The evaluator is advisory: a failing outcome doesn't stop the task
|
|
2709
|
+
* from being marked done; the sprint proceeds. The critique is persisted
|
|
2710
|
+
* in the sidecar for later review, and the warning log lets the user
|
|
2711
|
+
* see what didn't pass without scrolling the evaluations directory.
|
|
2699
2712
|
*/
|
|
2700
|
-
reportResult(taskName, evalResult,
|
|
2713
|
+
reportResult(taskName, evalResult, totalIterations, plateaued) {
|
|
2701
2714
|
if (plateaued) {
|
|
2702
2715
|
this.logger.warning(
|
|
2703
|
-
`Evaluation plateaued on the same failures
|
|
2716
|
+
`Evaluation plateaued on the same failures after ${String(totalIterations)} iteration(s): ${taskName}`
|
|
2704
2717
|
);
|
|
2705
2718
|
} else if (evalResult.status === "malformed") {
|
|
2706
|
-
this.logger.warning(`Evaluator output was malformed for ${taskName}
|
|
2719
|
+
this.logger.warning(`Evaluator output was malformed for ${taskName}`);
|
|
2707
2720
|
} else if (!isPassed(evalResult)) {
|
|
2708
2721
|
this.logger.warning(
|
|
2709
|
-
`Evaluation did not pass after ${String(
|
|
2722
|
+
`Evaluation did not pass after ${String(totalIterations)} iteration(s) \u2014 marking done: ${taskName}`
|
|
2710
2723
|
);
|
|
2711
2724
|
} else {
|
|
2712
2725
|
this.logger.success(`Evaluation passed: ${taskName}`);
|
|
@@ -2729,40 +2742,24 @@ function loadTaskStep(persistence) {
|
|
|
2729
2742
|
function checkAlreadyEvaluatedStep(options) {
|
|
2730
2743
|
return step("check-already-evaluated", (ctx) => {
|
|
2731
2744
|
const task = ctx.tasks?.[0];
|
|
2732
|
-
if (!
|
|
2733
|
-
const
|
|
2734
|
-
return Result.ok(
|
|
2735
|
-
}
|
|
2736
|
-
if (task.evaluated && !options.force) {
|
|
2737
|
-
const summary = {
|
|
2738
|
-
taskId: task.id,
|
|
2739
|
-
status: "skipped",
|
|
2740
|
-
iterations: 0
|
|
2741
|
-
};
|
|
2742
|
-
const partial = { evaluationSummary: summary };
|
|
2743
|
-
return Result.ok(partial);
|
|
2745
|
+
if (task && task.evaluated && !options.force) {
|
|
2746
|
+
const summary = { taskId: task.id, status: "skipped", iterations: 0 };
|
|
2747
|
+
return Result.ok({ evaluationSummary: summary });
|
|
2744
2748
|
}
|
|
2745
|
-
|
|
2746
|
-
return Result.ok(empty);
|
|
2749
|
+
return Result.ok({});
|
|
2747
2750
|
});
|
|
2748
2751
|
}
|
|
2749
2752
|
function runEvaluatorLoopStep(useCase, options) {
|
|
2750
2753
|
return step("run-evaluator-loop", async (ctx) => {
|
|
2751
2754
|
if (ctx.evaluationSummary?.status === "skipped") {
|
|
2752
|
-
|
|
2753
|
-
return Result.ok(empty);
|
|
2755
|
+
return Result.ok({});
|
|
2754
2756
|
}
|
|
2755
2757
|
const result = await useCase.execute(ctx.sprintId, ctx.taskId, {
|
|
2756
|
-
|
|
2757
|
-
maxTurns: options.maxTurns,
|
|
2758
|
-
fallbackModel: ctx.generatorModel ?? void 0,
|
|
2758
|
+
...options,
|
|
2759
2759
|
abortSignal: ctx.abortSignal ?? options.abortSignal
|
|
2760
2760
|
});
|
|
2761
|
-
if (!result.ok)
|
|
2762
|
-
|
|
2763
|
-
}
|
|
2764
|
-
const partial = { evaluationSummary: result.value };
|
|
2765
|
-
return Result.ok(partial);
|
|
2761
|
+
if (!result.ok) return Result.error(result.error);
|
|
2762
|
+
return Result.ok({ evaluationSummary: result.value });
|
|
2766
2763
|
});
|
|
2767
2764
|
}
|
|
2768
2765
|
function createEvaluatorPipeline(deps, options = {}) {
|
|
@@ -2788,10 +2785,7 @@ function createEvaluatorPipeline(deps, options = {}) {
|
|
|
2788
2785
|
function evaluateTask(deps) {
|
|
2789
2786
|
return step("evaluate-task", async (ctx) => {
|
|
2790
2787
|
const evalCfg = await deps.useCase.getEvaluationConfig(deps.options);
|
|
2791
|
-
if (!evalCfg.enabled) {
|
|
2792
|
-
const empty = {};
|
|
2793
|
-
return Result.ok(empty);
|
|
2794
|
-
}
|
|
2788
|
+
if (!evalCfg.enabled) return Result.ok({});
|
|
2795
2789
|
const innerPipeline = createEvaluatorPipeline(
|
|
2796
2790
|
{
|
|
2797
2791
|
persistence: deps.persistence,
|
|
@@ -2806,38 +2800,52 @@ function evaluateTask(deps) {
|
|
|
2806
2800
|
{
|
|
2807
2801
|
iterations: evalCfg.iterations,
|
|
2808
2802
|
maxTurns: deps.options.maxTurns,
|
|
2803
|
+
// noCommit (ExecutionOptions) inverted — if the generator committed
|
|
2804
|
+
// the initial work, the fix must commit too.
|
|
2805
|
+
needsCommit: !deps.options.noCommit,
|
|
2806
|
+
// Model ladder input: evaluator uses a cheaper model than the
|
|
2807
|
+
// generator's. Null when the generator didn't report one (Copilot,
|
|
2808
|
+
// blocked tasks).
|
|
2809
|
+
fallbackModel: ctx.generatorModel ?? void 0,
|
|
2810
|
+
// --resume <id> so the fix continues the generator's session
|
|
2811
|
+
// rather than cold-starting. Undefined → fresh spawn (rare fallback).
|
|
2812
|
+
generatorSessionId: ctx.executionResult?.sessionId,
|
|
2809
2813
|
abortSignal: ctx.abortSignal
|
|
2810
2814
|
}
|
|
2811
2815
|
);
|
|
2812
2816
|
const innerCtx = {
|
|
2813
2817
|
sprintId: ctx.sprint.id,
|
|
2814
2818
|
taskId: ctx.task.id,
|
|
2815
|
-
generatorModel: ctx.generatorModel ?? null,
|
|
2816
2819
|
abortSignal: ctx.abortSignal
|
|
2817
2820
|
};
|
|
2818
|
-
let stepNames = [];
|
|
2819
2821
|
try {
|
|
2820
|
-
const
|
|
2821
|
-
|
|
2822
|
-
// Even on failure the framework populates stepResults up to and
|
|
2823
|
-
// including the failing step. Extract them opportunistically —
|
|
2824
|
-
// if unavailable, proceed with an empty list.
|
|
2825
|
-
[]
|
|
2826
|
-
);
|
|
2827
|
-
if (!result.ok) {
|
|
2822
|
+
const innerResult = await executePipeline(innerPipeline, innerCtx);
|
|
2823
|
+
if (!innerResult.ok) {
|
|
2828
2824
|
deps.logger.warning(
|
|
2829
|
-
`
|
|
2825
|
+
`Evaluator pipeline errored for ${ctx.task.name}: ${innerResult.error.message} \u2014 proceeding with task completion`
|
|
2830
2826
|
);
|
|
2827
|
+
return Result.ok({ evaluationStepNames: [] });
|
|
2831
2828
|
}
|
|
2829
|
+
logIfNonTerminal(deps.logger, ctx.task.name, innerResult.value.context.evaluationSummary);
|
|
2830
|
+
return Result.ok({
|
|
2831
|
+
evaluationStepNames: innerResult.value.stepResults.map((r) => r.stepName)
|
|
2832
|
+
});
|
|
2832
2833
|
} catch (err) {
|
|
2833
2834
|
deps.logger.warning(
|
|
2834
|
-
`Evaluator threw for ${ctx.task.name}: ${err instanceof Error ? err.message : String(err)}
|
|
2835
|
+
`Evaluator threw for ${ctx.task.name}: ${err instanceof Error ? err.message : String(err)} \u2014 proceeding with task completion`
|
|
2835
2836
|
);
|
|
2837
|
+
return Result.ok({ evaluationStepNames: [] });
|
|
2836
2838
|
}
|
|
2837
|
-
const partial = { evaluationStepNames: stepNames };
|
|
2838
|
-
return Result.ok(partial);
|
|
2839
2839
|
});
|
|
2840
2840
|
}
|
|
2841
|
+
function logIfNonTerminal(logger, taskName, summary) {
|
|
2842
|
+
if (!summary) return;
|
|
2843
|
+
if (summary.status === "failed" || summary.status === "malformed" || summary.status === "plateau") {
|
|
2844
|
+
logger.warning(
|
|
2845
|
+
`Evaluation ${summary.status} for ${taskName} after ${String(summary.iterations)} iteration(s) \u2014 proceeding with task completion`
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2841
2849
|
|
|
2842
2850
|
// src/business/pipelines/execute/steps/recover-dirty-tree.ts
|
|
2843
2851
|
function recoverDirtyTree2(deps) {
|
|
@@ -4023,6 +4031,9 @@ var TextPromptBuilderAdapter = class {
|
|
|
4023
4031
|
extraDimensions: task.extraDimensions ?? []
|
|
4024
4032
|
});
|
|
4025
4033
|
}
|
|
4034
|
+
buildTaskEvaluationResumePrompt(critique, needsCommit) {
|
|
4035
|
+
return buildEvaluationResumePrompt({ critique, needsCommit });
|
|
4036
|
+
}
|
|
4026
4037
|
buildFeedbackPrompt(sprintName, completedTasks, feedback, branch) {
|
|
4027
4038
|
return buildSprintFeedbackPrompt(sprintName, completedTasks, feedback, branch);
|
|
4028
4039
|
}
|
|
@@ -4608,8 +4619,9 @@ function describeMcpHint(name) {
|
|
|
4608
4619
|
}
|
|
4609
4620
|
|
|
4610
4621
|
// src/integration/external/lifecycle.ts
|
|
4611
|
-
import {
|
|
4622
|
+
import { spawn } from "child_process";
|
|
4612
4623
|
var DEFAULT_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4624
|
+
var MAX_OUTPUT_BYTES = 50 * 1024 * 1024;
|
|
4613
4625
|
function getHookTimeoutMs() {
|
|
4614
4626
|
const envVal = process.env["RALPHCTL_SETUP_TIMEOUT_MS"];
|
|
4615
4627
|
if (envVal) {
|
|
@@ -4621,16 +4633,76 @@ function getHookTimeoutMs() {
|
|
|
4621
4633
|
function runLifecycleHook(projectPath, script, event, timeoutOverrideMs) {
|
|
4622
4634
|
assertSafeCwd(projectPath);
|
|
4623
4635
|
const timeoutMs = timeoutOverrideMs ?? getHookTimeoutMs();
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4636
|
+
return new Promise((resolve) => {
|
|
4637
|
+
const child = spawn(script, {
|
|
4638
|
+
cwd: projectPath,
|
|
4639
|
+
shell: true,
|
|
4640
|
+
detached: process.platform !== "win32",
|
|
4641
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4642
|
+
env: { ...process.env, RALPHCTL_LIFECYCLE_EVENT: event }
|
|
4643
|
+
});
|
|
4644
|
+
const killTree = () => {
|
|
4645
|
+
if (process.platform !== "win32" && typeof child.pid === "number") {
|
|
4646
|
+
try {
|
|
4647
|
+
process.kill(-child.pid, "SIGTERM");
|
|
4648
|
+
return;
|
|
4649
|
+
} catch {
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
try {
|
|
4653
|
+
child.kill("SIGTERM");
|
|
4654
|
+
} catch {
|
|
4655
|
+
}
|
|
4656
|
+
};
|
|
4657
|
+
const chunks = [];
|
|
4658
|
+
let totalBytes = 0;
|
|
4659
|
+
let timedOut = false;
|
|
4660
|
+
let capExceeded = false;
|
|
4661
|
+
let settled = false;
|
|
4662
|
+
const appendChunk = (chunk) => {
|
|
4663
|
+
if (capExceeded) return;
|
|
4664
|
+
totalBytes += chunk.length;
|
|
4665
|
+
if (totalBytes > MAX_OUTPUT_BYTES) {
|
|
4666
|
+
capExceeded = true;
|
|
4667
|
+
killTree();
|
|
4668
|
+
return;
|
|
4669
|
+
}
|
|
4670
|
+
chunks.push(chunk);
|
|
4671
|
+
};
|
|
4672
|
+
child.stdout.on("data", (chunk) => {
|
|
4673
|
+
appendChunk(chunk);
|
|
4674
|
+
});
|
|
4675
|
+
child.stderr.on("data", (chunk) => {
|
|
4676
|
+
appendChunk(chunk);
|
|
4677
|
+
});
|
|
4678
|
+
const timer = setTimeout(() => {
|
|
4679
|
+
timedOut = true;
|
|
4680
|
+
killTree();
|
|
4681
|
+
}, timeoutMs);
|
|
4682
|
+
const finish = (passed, suffix) => {
|
|
4683
|
+
if (settled) return;
|
|
4684
|
+
settled = true;
|
|
4685
|
+
clearTimeout(timer);
|
|
4686
|
+
const base = Buffer.concat(chunks).toString("utf-8").trim();
|
|
4687
|
+
const output = suffix ? base ? `${base}
|
|
4688
|
+
${suffix}` : suffix : base;
|
|
4689
|
+
resolve({ passed, output });
|
|
4690
|
+
};
|
|
4691
|
+
child.on("error", (err) => {
|
|
4692
|
+
finish(false, `[spawn error: ${err.message}]`);
|
|
4693
|
+
});
|
|
4694
|
+
child.on("close", (code) => {
|
|
4695
|
+
if (timedOut) {
|
|
4696
|
+
finish(false, `[timeout exceeded after ${String(timeoutMs)}ms]`);
|
|
4697
|
+
return;
|
|
4698
|
+
}
|
|
4699
|
+
if (capExceeded) {
|
|
4700
|
+
finish(false, `[output exceeded ${String(MAX_OUTPUT_BYTES)} byte cap \u2014 truncated]`);
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
finish(code === 0);
|
|
4704
|
+
});
|
|
4631
4705
|
});
|
|
4632
|
-
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
4633
|
-
return { passed: result.status === 0, output };
|
|
4634
4706
|
}
|
|
4635
4707
|
|
|
4636
4708
|
// src/integration/ai/task-context.ts
|
|
@@ -4650,7 +4722,7 @@ function getRecentGitHistory(projectPath, count = 20) {
|
|
|
4650
4722
|
}
|
|
4651
4723
|
|
|
4652
4724
|
// src/integration/external/git.ts
|
|
4653
|
-
import { spawnSync
|
|
4725
|
+
import { spawnSync } from "child_process";
|
|
4654
4726
|
var BRANCH_NAME_RE = /^[a-zA-Z0-9/_.-]+$/;
|
|
4655
4727
|
var BRANCH_NAME_INVALID_PATTERNS = [/\.\./, /\.$/, /\/$/, /\.lock$/, /^-/, /\/\//];
|
|
4656
4728
|
function isValidBranchName(name) {
|
|
@@ -4663,7 +4735,7 @@ function isValidBranchName(name) {
|
|
|
4663
4735
|
}
|
|
4664
4736
|
function getCurrentBranch(cwd) {
|
|
4665
4737
|
assertSafeCwd(cwd);
|
|
4666
|
-
const result =
|
|
4738
|
+
const result = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
4667
4739
|
cwd,
|
|
4668
4740
|
encoding: "utf-8",
|
|
4669
4741
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4678,7 +4750,7 @@ function branchExists(cwd, name) {
|
|
|
4678
4750
|
if (!isValidBranchName(name)) {
|
|
4679
4751
|
throw new Error(`Invalid branch name: ${name}`);
|
|
4680
4752
|
}
|
|
4681
|
-
const result =
|
|
4753
|
+
const result = spawnSync("git", ["show-ref", "--verify", `refs/heads/${name}`], {
|
|
4682
4754
|
cwd,
|
|
4683
4755
|
encoding: "utf-8",
|
|
4684
4756
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4695,7 +4767,7 @@ function createAndCheckoutBranch(cwd, name) {
|
|
|
4695
4767
|
return;
|
|
4696
4768
|
}
|
|
4697
4769
|
if (branchExists(cwd, name)) {
|
|
4698
|
-
const result =
|
|
4770
|
+
const result = spawnSync("git", ["checkout", name], {
|
|
4699
4771
|
cwd,
|
|
4700
4772
|
encoding: "utf-8",
|
|
4701
4773
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4704,7 +4776,7 @@ function createAndCheckoutBranch(cwd, name) {
|
|
|
4704
4776
|
throw new Error(`Failed to checkout branch '${name}' in ${cwd}: ${result.stderr.trim()}`);
|
|
4705
4777
|
}
|
|
4706
4778
|
} else {
|
|
4707
|
-
const result =
|
|
4779
|
+
const result = spawnSync("git", ["checkout", "-b", name], {
|
|
4708
4780
|
cwd,
|
|
4709
4781
|
encoding: "utf-8",
|
|
4710
4782
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4720,7 +4792,7 @@ function verifyCurrentBranch(cwd, expected) {
|
|
|
4720
4792
|
}
|
|
4721
4793
|
function getDefaultBranch(cwd) {
|
|
4722
4794
|
assertSafeCwd(cwd);
|
|
4723
|
-
const result =
|
|
4795
|
+
const result = spawnSync("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
4724
4796
|
cwd,
|
|
4725
4797
|
encoding: "utf-8",
|
|
4726
4798
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4741,7 +4813,7 @@ function getDefaultBranch(cwd) {
|
|
|
4741
4813
|
function getHeadSha(cwd) {
|
|
4742
4814
|
try {
|
|
4743
4815
|
assertSafeCwd(cwd);
|
|
4744
|
-
const result =
|
|
4816
|
+
const result = spawnSync("git", ["rev-parse", "HEAD"], {
|
|
4745
4817
|
cwd,
|
|
4746
4818
|
encoding: "utf-8",
|
|
4747
4819
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4754,7 +4826,7 @@ function getHeadSha(cwd) {
|
|
|
4754
4826
|
}
|
|
4755
4827
|
function hasUncommittedChanges(cwd) {
|
|
4756
4828
|
assertSafeCwd(cwd);
|
|
4757
|
-
const result =
|
|
4829
|
+
const result = spawnSync("git", ["status", "--porcelain"], {
|
|
4758
4830
|
cwd,
|
|
4759
4831
|
encoding: "utf-8",
|
|
4760
4832
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4766,7 +4838,7 @@ function hasUncommittedChanges(cwd) {
|
|
|
4766
4838
|
}
|
|
4767
4839
|
function hardResetWorkingTree(cwd) {
|
|
4768
4840
|
assertSafeCwd(cwd);
|
|
4769
|
-
const reset =
|
|
4841
|
+
const reset = spawnSync("git", ["reset", "--hard", "HEAD"], {
|
|
4770
4842
|
cwd,
|
|
4771
4843
|
encoding: "utf-8",
|
|
4772
4844
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4774,7 +4846,7 @@ function hardResetWorkingTree(cwd) {
|
|
|
4774
4846
|
if (reset.status !== 0) {
|
|
4775
4847
|
throw new StorageError(`Failed to reset working tree in ${cwd}: ${reset.stderr.trim() || reset.stdout.trim()}`);
|
|
4776
4848
|
}
|
|
4777
|
-
const clean =
|
|
4849
|
+
const clean = spawnSync("git", ["clean", "-fd"], {
|
|
4778
4850
|
cwd,
|
|
4779
4851
|
encoding: "utf-8",
|
|
4780
4852
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4785,7 +4857,7 @@ function hardResetWorkingTree(cwd) {
|
|
|
4785
4857
|
}
|
|
4786
4858
|
function autoCommit(cwd, message) {
|
|
4787
4859
|
assertSafeCwd(cwd);
|
|
4788
|
-
const add =
|
|
4860
|
+
const add = spawnSync("git", ["add", "-A"], {
|
|
4789
4861
|
cwd,
|
|
4790
4862
|
encoding: "utf-8",
|
|
4791
4863
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4793,7 +4865,7 @@ function autoCommit(cwd, message) {
|
|
|
4793
4865
|
if (add.status !== 0) {
|
|
4794
4866
|
throw new Error(`Failed to stage changes in ${cwd}: ${add.stderr.trim()}`);
|
|
4795
4867
|
}
|
|
4796
|
-
const commit =
|
|
4868
|
+
const commit = spawnSync("git", ["commit", "-m", message], {
|
|
4797
4869
|
cwd,
|
|
4798
4870
|
encoding: "utf-8",
|
|
4799
4871
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4806,14 +4878,14 @@ function generateBranchName(sprintId) {
|
|
|
4806
4878
|
return `ralphctl/${sprintId}`;
|
|
4807
4879
|
}
|
|
4808
4880
|
function isGhAvailable() {
|
|
4809
|
-
const result =
|
|
4881
|
+
const result = spawnSync("gh", ["--version"], {
|
|
4810
4882
|
encoding: "utf-8",
|
|
4811
4883
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4812
4884
|
});
|
|
4813
4885
|
return result.status === 0;
|
|
4814
4886
|
}
|
|
4815
4887
|
function isGlabAvailable() {
|
|
4816
|
-
const result =
|
|
4888
|
+
const result = spawnSync("glab", ["--version"], {
|
|
4817
4889
|
encoding: "utf-8",
|
|
4818
4890
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4819
4891
|
});
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getAllConfigSchemaEntries,
|
|
7
7
|
getConfigDefaultValue,
|
|
8
8
|
parseConfigValue
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BT5FKIZX.mjs";
|
|
10
10
|
import {
|
|
11
11
|
editorInput
|
|
12
12
|
} from "./chunk-OGEXYSFS.mjs";
|
|
@@ -41,14 +41,14 @@ import {
|
|
|
41
41
|
updateTask,
|
|
42
42
|
updateTaskStatus,
|
|
43
43
|
validateImportTasks
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-D6QZNEYN.mjs";
|
|
45
45
|
import {
|
|
46
46
|
SignalParser,
|
|
47
47
|
buildTicketRefinePrompt,
|
|
48
48
|
processLifecycleAdapter,
|
|
49
49
|
providerDisplayName,
|
|
50
50
|
resolveProvider
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-62HYDA7L.mjs";
|
|
52
52
|
import {
|
|
53
53
|
fetchIssueFromUrl,
|
|
54
54
|
formatIssueContext,
|
|
@@ -176,6 +176,7 @@ import {
|
|
|
176
176
|
ProjectNotFoundError,
|
|
177
177
|
SprintNotFoundError,
|
|
178
178
|
SprintStatusError,
|
|
179
|
+
StepError,
|
|
179
180
|
StorageError,
|
|
180
181
|
TaskNotFoundError,
|
|
181
182
|
TicketNotFoundError
|
|
@@ -184,7 +185,7 @@ import {
|
|
|
184
185
|
// package.json
|
|
185
186
|
var package_default = {
|
|
186
187
|
name: "ralphctl",
|
|
187
|
-
version: "0.
|
|
188
|
+
version: "0.5.0",
|
|
188
189
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
|
|
189
190
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
190
191
|
type: "module",
|
|
@@ -2068,12 +2069,20 @@ var InMemoryExecutionRegistry = class {
|
|
|
2068
2069
|
this.transition(executionId, "completed", summary ?? void 0);
|
|
2069
2070
|
}
|
|
2070
2071
|
} catch (err) {
|
|
2071
|
-
void err;
|
|
2072
2072
|
if (abortSignal.aborted) {
|
|
2073
2073
|
this.transition(executionId, "cancelled");
|
|
2074
|
-
|
|
2075
|
-
this.transition(executionId, "failed");
|
|
2074
|
+
return;
|
|
2076
2075
|
}
|
|
2076
|
+
const errInfo = err instanceof StepError ? { message: err.message, stepName: err.stepName } : { message: err instanceof Error ? err.message : String(err) };
|
|
2077
|
+
const entry = this.entries.get(executionId);
|
|
2078
|
+
entry?.logEventBus.emit({
|
|
2079
|
+
kind: "log",
|
|
2080
|
+
level: "error",
|
|
2081
|
+
message: errInfo.stepName ? `[${errInfo.stepName}] ${errInfo.message}` : errInfo.message,
|
|
2082
|
+
context: {},
|
|
2083
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
2084
|
+
});
|
|
2085
|
+
this.transition(executionId, "failed", void 0, errInfo);
|
|
2077
2086
|
}
|
|
2078
2087
|
}
|
|
2079
2088
|
get(id) {
|
|
@@ -2100,7 +2109,7 @@ var InMemoryExecutionRegistry = class {
|
|
|
2100
2109
|
getLogEventBus(id) {
|
|
2101
2110
|
return this.entries.get(id)?.logEventBus ?? null;
|
|
2102
2111
|
}
|
|
2103
|
-
transition(executionId, status, summary) {
|
|
2112
|
+
transition(executionId, status, summary, error2) {
|
|
2104
2113
|
const entry = this.entries.get(executionId);
|
|
2105
2114
|
if (!entry) return;
|
|
2106
2115
|
if (entry.execution.status === status) return;
|
|
@@ -2108,7 +2117,8 @@ var InMemoryExecutionRegistry = class {
|
|
|
2108
2117
|
...entry.execution,
|
|
2109
2118
|
status,
|
|
2110
2119
|
endedAt: /* @__PURE__ */ new Date(),
|
|
2111
|
-
summary: summary ?? entry.execution.summary
|
|
2120
|
+
summary: summary ?? entry.execution.summary,
|
|
2121
|
+
error: error2 ?? entry.execution.error
|
|
2112
2122
|
};
|
|
2113
2123
|
entry.execution = next;
|
|
2114
2124
|
this.notify(next);
|
|
@@ -2204,7 +2214,7 @@ async function selectProject(message = "Select project:") {
|
|
|
2204
2214
|
default: true
|
|
2205
2215
|
});
|
|
2206
2216
|
if (create) {
|
|
2207
|
-
const { projectAddCommand } = await import("./add-
|
|
2217
|
+
const { projectAddCommand } = await import("./add-67UFUI54.mjs");
|
|
2208
2218
|
await projectAddCommand({ interactive: true });
|
|
2209
2219
|
const updated = await listProjects();
|
|
2210
2220
|
if (updated.length === 0) return null;
|
package/dist/cli.mjs
CHANGED
|
@@ -41,10 +41,10 @@ import {
|
|
|
41
41
|
ticketRefineCommand,
|
|
42
42
|
ticketRemoveCommand,
|
|
43
43
|
ticketShowCommand
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-ZE2BRQA2.mjs";
|
|
45
45
|
import {
|
|
46
46
|
projectAddCommand
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-BT5FKIZX.mjs";
|
|
48
48
|
import {
|
|
49
49
|
sprintCreateCommand
|
|
50
50
|
} from "./chunk-FNAAA32W.mjs";
|
|
@@ -56,8 +56,8 @@ import {
|
|
|
56
56
|
executePipeline,
|
|
57
57
|
getTasks,
|
|
58
58
|
sprintStartCommand
|
|
59
|
-
} from "./chunk-
|
|
60
|
-
import "./chunk-
|
|
59
|
+
} from "./chunk-D6QZNEYN.mjs";
|
|
60
|
+
import "./chunk-62HYDA7L.mjs";
|
|
61
61
|
import {
|
|
62
62
|
truncate
|
|
63
63
|
} from "./chunk-GQ2WFKBN.mjs";
|
|
@@ -756,7 +756,7 @@ async function main() {
|
|
|
756
756
|
const isBare = argv.length <= 2;
|
|
757
757
|
const isInteractive = argv[2] === "interactive";
|
|
758
758
|
if (isBare || isInteractive) {
|
|
759
|
-
const { mountInkApp } = await import("./mount-
|
|
759
|
+
const { mountInkApp } = await import("./mount-NCYR22SN.mjs");
|
|
760
760
|
const { fallback } = await mountInkApp({ initialView: "repl" });
|
|
761
761
|
if (!fallback) return;
|
|
762
762
|
printBanner();
|
|
@@ -767,10 +767,10 @@ async function main() {
|
|
|
767
767
|
return;
|
|
768
768
|
}
|
|
769
769
|
if (argv[2] === "sprint" && argv[3] === "start") {
|
|
770
|
-
const { parseSprintStartArgs } = await import("./start-
|
|
770
|
+
const { parseSprintStartArgs } = await import("./start-T34NI3LF.mjs");
|
|
771
771
|
const parsed = parseSprintStartArgs(argv.slice(4));
|
|
772
772
|
if (parsed.ok) {
|
|
773
|
-
const { mountInkApp } = await import("./mount-
|
|
773
|
+
const { mountInkApp } = await import("./mount-NCYR22SN.mjs");
|
|
774
774
|
const { getSharedDeps: getSharedDeps2 } = await import("./bootstrap-FMHG6DRY.mjs");
|
|
775
775
|
let sprintId;
|
|
776
776
|
try {
|
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
ticketRemoveCommand,
|
|
63
63
|
ticketShowCommand,
|
|
64
64
|
useCurrentPrompt
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-ZE2BRQA2.mjs";
|
|
66
66
|
import {
|
|
67
67
|
PromptCancelledError,
|
|
68
68
|
detectCheckScriptCandidates,
|
|
@@ -73,7 +73,7 @@ import {
|
|
|
73
73
|
projectAddCommand,
|
|
74
74
|
suggestCheckScript,
|
|
75
75
|
validateConfigValue
|
|
76
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-BT5FKIZX.mjs";
|
|
77
77
|
import {
|
|
78
78
|
sprintCreateCommand
|
|
79
79
|
} from "./chunk-FNAAA32W.mjs";
|
|
@@ -99,7 +99,7 @@ import {
|
|
|
99
99
|
reorderTask,
|
|
100
100
|
sprintStartCommand,
|
|
101
101
|
updateTaskStatus
|
|
102
|
-
} from "./chunk-
|
|
102
|
+
} from "./chunk-D6QZNEYN.mjs";
|
|
103
103
|
import {
|
|
104
104
|
ProviderAiSessionAdapter,
|
|
105
105
|
SignalParser,
|
|
@@ -107,7 +107,7 @@ import {
|
|
|
107
107
|
exitAltScreen,
|
|
108
108
|
registerTuiInstance,
|
|
109
109
|
withSuspendedTui
|
|
110
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-62HYDA7L.mjs";
|
|
111
111
|
import {
|
|
112
112
|
addTicket,
|
|
113
113
|
allRequirementsApproved,
|
|
@@ -189,7 +189,7 @@ import { render } from "ink";
|
|
|
189
189
|
|
|
190
190
|
// src/integration/ui/tui/views/app.tsx
|
|
191
191
|
import { useEffect as useEffect37, useState as useState38 } from "react";
|
|
192
|
-
import { Box as Box37, useStdout } from "ink";
|
|
192
|
+
import { Box as Box37, useStdout as useStdout2 } from "ink";
|
|
193
193
|
|
|
194
194
|
// src/integration/ui/tui/views/view-router.tsx
|
|
195
195
|
import { useCallback as useCallback12, useMemo as useMemo34, useState as useState37 } from "react";
|
|
@@ -1606,7 +1606,7 @@ function SettingsView() {
|
|
|
1606
1606
|
|
|
1607
1607
|
// src/integration/ui/tui/views/execute-view.tsx
|
|
1608
1608
|
import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef2, useState as useState8 } from "react";
|
|
1609
|
-
import { Box as Box19, Text as Text18, useInput as useInput6 } from "ink";
|
|
1609
|
+
import { Box as Box19, Text as Text18, useInput as useInput6, useStdout } from "ink";
|
|
1610
1610
|
|
|
1611
1611
|
// src/integration/ui/tui/runtime/hooks.ts
|
|
1612
1612
|
import { useCallback as useCallback4, useEffect as useEffect6, useRef, useState as useState6 } from "react";
|
|
@@ -1908,8 +1908,12 @@ function renderLine(event, index, isActiveSpinner) {
|
|
|
1908
1908
|
}
|
|
1909
1909
|
}
|
|
1910
1910
|
}
|
|
1911
|
-
function LogTail({ events,
|
|
1912
|
-
const
|
|
1911
|
+
function LogTail({ events, visibleLines = 8, scrollOffset = 0 }) {
|
|
1912
|
+
const maxOffset = Math.max(0, events.length - visibleLines);
|
|
1913
|
+
const clampedOffset = Math.min(Math.max(0, scrollOffset), maxOffset);
|
|
1914
|
+
const end = events.length - clampedOffset;
|
|
1915
|
+
const start = Math.max(0, end - visibleLines);
|
|
1916
|
+
const window = events.slice(start, end);
|
|
1913
1917
|
const resolvedIds = useMemo5(() => {
|
|
1914
1918
|
const ids = /* @__PURE__ */ new Set();
|
|
1915
1919
|
for (const ev of events) {
|
|
@@ -1919,9 +1923,29 @@ function LogTail({ events, limit = 8 }) {
|
|
|
1919
1923
|
}
|
|
1920
1924
|
return ids;
|
|
1921
1925
|
}, [events]);
|
|
1926
|
+
const scrolledUp = clampedOffset > 0;
|
|
1927
|
+
const hiddenAbove = start;
|
|
1928
|
+
const hiddenBelow = events.length - end;
|
|
1922
1929
|
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
|
|
1923
|
-
/* @__PURE__ */
|
|
1924
|
-
|
|
1930
|
+
/* @__PURE__ */ jsxs14(Box15, { children: [
|
|
1931
|
+
/* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "\u2500\u2500 Log \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
1932
|
+
scrolledUp ? /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
|
|
1933
|
+
" ",
|
|
1934
|
+
glyphs.arrowRight,
|
|
1935
|
+
" ",
|
|
1936
|
+
hiddenAbove,
|
|
1937
|
+
" line",
|
|
1938
|
+
hiddenAbove !== 1 ? "s" : "",
|
|
1939
|
+
" above",
|
|
1940
|
+
hiddenBelow > 0 ? ` \xB7 ${String(hiddenBelow)} below` : ""
|
|
1941
|
+
] }) : events.length > visibleLines ? /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
|
|
1942
|
+
" ",
|
|
1943
|
+
"(",
|
|
1944
|
+
events.length - visibleLines,
|
|
1945
|
+
" hidden)"
|
|
1946
|
+
] }) : null
|
|
1947
|
+
] }),
|
|
1948
|
+
window.length === 0 ? /* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "(no activity yet)" }) : window.map((event, i) => {
|
|
1925
1949
|
const active = event.kind === "spinner-start" && !resolvedIds.has(event.id);
|
|
1926
1950
|
return renderLine(event, i, active);
|
|
1927
1951
|
})
|
|
@@ -1998,9 +2022,13 @@ function ResultCard({ kind, title, fields, nextSteps, lines }) {
|
|
|
1998
2022
|
}
|
|
1999
2023
|
|
|
2000
2024
|
// src/integration/ui/tui/views/execute-view.tsx
|
|
2001
|
-
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2025
|
+
import { Fragment, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2002
2026
|
var EXECUTE_HINTS_RUNNING = [{ key: "c", action: "cancel" }];
|
|
2003
2027
|
var EXECUTE_HINTS_TERMINAL = [{ key: "Enter", action: "back" }];
|
|
2028
|
+
var LOG_TAIL_FIXED_OVERHEAD = 20;
|
|
2029
|
+
var LOG_TAIL_MIN_LINES = 3;
|
|
2030
|
+
var LOG_TAIL_DEFAULT_LINES = 8;
|
|
2031
|
+
var ERROR_MESSAGE_MAX_LINES = 20;
|
|
2004
2032
|
function initialState() {
|
|
2005
2033
|
return {
|
|
2006
2034
|
sprint: null,
|
|
@@ -2026,6 +2054,7 @@ function ExecuteView({ sprintId, executionId, executionOptions }) {
|
|
|
2026
2054
|
const registryEvents = useRegistryEvents(registry);
|
|
2027
2055
|
const [attach, setAttach] = useState8(initialAttach);
|
|
2028
2056
|
const [state, setState] = useState8(initialState);
|
|
2057
|
+
const { stdout } = useStdout();
|
|
2029
2058
|
const processedCountRef = useRef2(0);
|
|
2030
2059
|
const startedRef = useRef2(false);
|
|
2031
2060
|
const attachedId = attach.execution?.id ?? null;
|
|
@@ -2122,6 +2151,7 @@ function ExecuteView({ sprintId, executionId, executionOptions }) {
|
|
|
2122
2151
|
})();
|
|
2123
2152
|
}
|
|
2124
2153
|
}, [signalEvents, shared, sprintId]);
|
|
2154
|
+
const logVisibleLines = stdout.rows ? Math.max(LOG_TAIL_MIN_LINES, stdout.rows - LOG_TAIL_FIXED_OVERHEAD) : LOG_TAIL_DEFAULT_LINES;
|
|
2125
2155
|
const status = liveExecution?.status ?? "running";
|
|
2126
2156
|
const terminal = status === "completed" || status === "failed" || status === "cancelled";
|
|
2127
2157
|
const [closePromptRun, setClosePromptRun] = useState8(false);
|
|
@@ -2190,6 +2220,7 @@ function ExecuteView({ sprintId, executionId, executionOptions }) {
|
|
|
2190
2220
|
if (attach.kind === "attaching") {
|
|
2191
2221
|
return /* @__PURE__ */ jsx20(ViewShell, { title: "Execute", children: /* @__PURE__ */ jsx20(Spinner, { label: "Attaching to execution\u2026" }) });
|
|
2192
2222
|
}
|
|
2223
|
+
const errorCard = terminal && liveExecution?.status === "failed" && liveExecution.error ? buildErrorCard(liveExecution.error) : null;
|
|
2193
2224
|
return /* @__PURE__ */ jsxs18(ViewShell, { title: "Execute", children: [
|
|
2194
2225
|
/* @__PURE__ */ jsxs18(Box19, { children: [
|
|
2195
2226
|
/* @__PURE__ */ jsx20(Text18, { bold: true, color: inkColors.primary, children: state.sprint?.name ?? "Sprint" }),
|
|
@@ -2216,8 +2247,8 @@ function ExecuteView({ sprintId, executionId, executionOptions }) {
|
|
|
2216
2247
|
const taskName = task?.name ?? taskId.slice(0, 8);
|
|
2217
2248
|
return /* @__PURE__ */ jsx20(Spinner, { label: `${taskName} ${glyphs.emDash} ${label}` }, taskId);
|
|
2218
2249
|
}) }) : null,
|
|
2219
|
-
/* @__PURE__ */ jsx20(Box19, { marginTop: spacing.section, children: /* @__PURE__ */ jsx20(LogTail, { events: logEvents }) }),
|
|
2220
|
-
terminal && liveExecution ? /* @__PURE__ */
|
|
2250
|
+
/* @__PURE__ */ jsx20(Box19, { marginTop: spacing.section, children: /* @__PURE__ */ jsx20(LogTail, { events: logEvents, visibleLines: logVisibleLines, scrollOffset: 0 }) }),
|
|
2251
|
+
terminal && liveExecution ? /* @__PURE__ */ jsx20(Box19, { marginTop: spacing.section, flexDirection: "column", children: errorCard ? /* @__PURE__ */ jsx20(ResultCard, { kind: "error", title: "Execution failed", fields: errorCard.fields, lines: errorCard.lines }) : /* @__PURE__ */ jsxs18(Fragment, { children: [
|
|
2221
2252
|
/* @__PURE__ */ jsxs18(Text18, { color: terminalColor(liveExecution.status), bold: true, children: [
|
|
2222
2253
|
terminalGlyph(liveExecution.status),
|
|
2223
2254
|
" Execution ",
|
|
@@ -2229,8 +2260,8 @@ function ExecuteView({ sprintId, executionId, executionOptions }) {
|
|
|
2229
2260
|
glyphs.inlineDot,
|
|
2230
2261
|
" ",
|
|
2231
2262
|
liveExecution.summary.remaining,
|
|
2232
|
-
" remaining",
|
|
2233
2263
|
" ",
|
|
2264
|
+
"remaining ",
|
|
2234
2265
|
glyphs.inlineDot,
|
|
2235
2266
|
" ",
|
|
2236
2267
|
liveExecution.summary.blocked,
|
|
@@ -2239,7 +2270,7 @@ function ExecuteView({ sprintId, executionId, executionOptions }) {
|
|
|
2239
2270
|
liveExecution.summary.stopReason,
|
|
2240
2271
|
")"
|
|
2241
2272
|
] }) : null
|
|
2242
|
-
] }) : null
|
|
2273
|
+
] }) }) : null
|
|
2243
2274
|
] });
|
|
2244
2275
|
}
|
|
2245
2276
|
function terminalColor(status) {
|
|
@@ -2252,6 +2283,19 @@ function terminalGlyph(status) {
|
|
|
2252
2283
|
if (status === "failed") return glyphs.cross;
|
|
2253
2284
|
return glyphs.warningGlyph;
|
|
2254
2285
|
}
|
|
2286
|
+
function buildErrorCard(error) {
|
|
2287
|
+
const fields = error.stepName ? [["Step", error.stepName]] : void 0;
|
|
2288
|
+
const rawLines = error.message.split("\n");
|
|
2289
|
+
if (rawLines.length <= ERROR_MESSAGE_MAX_LINES) {
|
|
2290
|
+
return { fields, lines: rawLines };
|
|
2291
|
+
}
|
|
2292
|
+
const hidden = rawLines.length - ERROR_MESSAGE_MAX_LINES;
|
|
2293
|
+
const visibleLines = [
|
|
2294
|
+
`(${String(hidden)} earlier line${hidden !== 1 ? "s" : ""} omitted)`,
|
|
2295
|
+
...rawLines.slice(-ERROR_MESSAGE_MAX_LINES)
|
|
2296
|
+
];
|
|
2297
|
+
return { fields, lines: visibleLines };
|
|
2298
|
+
}
|
|
2255
2299
|
function CollisionRedirect({ registry, collisionId, fallbackSprintId }) {
|
|
2256
2300
|
const router = useRouter();
|
|
2257
2301
|
useInput6((_input, key) => {
|
|
@@ -7311,7 +7355,7 @@ function buildInitialStack(initialView, mountOptions) {
|
|
|
7311
7355
|
return [{ id: "home" }];
|
|
7312
7356
|
}
|
|
7313
7357
|
function useTerminalWidth() {
|
|
7314
|
-
const { stdout } =
|
|
7358
|
+
const { stdout } = useStdout2();
|
|
7315
7359
|
const [width, setWidth] = useState38(stdout.columns);
|
|
7316
7360
|
useEffect37(() => {
|
|
7317
7361
|
const onResize = () => {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
parseSprintStartArgs,
|
|
4
4
|
sprintStartCommand
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-D6QZNEYN.mjs";
|
|
6
|
+
import "./chunk-62HYDA7L.mjs";
|
|
7
7
|
import "./chunk-GQ2WFKBN.mjs";
|
|
8
8
|
import "./chunk-CFUVE2BP.mjs";
|
|
9
9
|
import "./chunk-747KW2RW.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ralphctl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Agent harness for long-running AI coding tasks — orchestrates Claude Code & GitHub Copilot across repositories",
|
|
5
5
|
"homepage": "https://github.com/lukas-grigis/ralphctl",
|
|
6
6
|
"type": "module",
|