ralphctl 0.4.0 → 0.4.1
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/{chunk-JYCGQA2D.mjs → chunk-3HJNVQ7N.mjs} +79 -0
- package/dist/{chunk-MRN3Z2XC.mjs → chunk-SM4GGZSU.mjs} +2 -2
- package/dist/cli.mjs +5 -5
- package/dist/{mount-XMN3S4W6.mjs → mount-2N6H5CWA.mjs} +2 -2
- package/dist/prompts/sprint-feedback.md +6 -1
- package/dist/prompts/task-evaluation.md +7 -1
- package/dist/{start-D35SOXMM.mjs → start-IUDCXIEA.mjs} +1 -1
- package/package.json +1 -1
|
@@ -1681,6 +1681,37 @@ function collectRepoIds(tasks) {
|
|
|
1681
1681
|
|
|
1682
1682
|
// src/business/usecases/execute.ts
|
|
1683
1683
|
import { basename } from "path";
|
|
1684
|
+
|
|
1685
|
+
// src/business/usecases/recover-dirty-tree.ts
|
|
1686
|
+
async function recoverDirtyTree(deps, params) {
|
|
1687
|
+
const { external, logger, signalBus } = deps;
|
|
1688
|
+
const { sprintId, taskId, taskName, repoPath } = params;
|
|
1689
|
+
if (!external.hasUncommittedChanges(repoPath)) return;
|
|
1690
|
+
logger.warn(
|
|
1691
|
+
`Dirty tree after "${taskName}" \u2014 auto-committing on the harness's behalf. The agent should commit its own work; see prompt guidance.`,
|
|
1692
|
+
{ taskId, projectPath: repoPath }
|
|
1693
|
+
);
|
|
1694
|
+
signalBus.emit({
|
|
1695
|
+
type: "signal",
|
|
1696
|
+
signal: {
|
|
1697
|
+
type: "note",
|
|
1698
|
+
text: `harness auto-commit: dirty tree after task "${taskName}" settlement`,
|
|
1699
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1700
|
+
},
|
|
1701
|
+
ctx: { sprintId, taskId, projectPath: repoPath }
|
|
1702
|
+
});
|
|
1703
|
+
const message = `chore(harness): auto-commit leftover changes from "${taskName}"`;
|
|
1704
|
+
try {
|
|
1705
|
+
await external.autoCommit(repoPath, message);
|
|
1706
|
+
} catch (err) {
|
|
1707
|
+
logger.error(`Auto-commit failed in ${repoPath}: ${err instanceof Error ? err.message : String(err)}`, {
|
|
1708
|
+
taskId,
|
|
1709
|
+
projectPath: repoPath
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// src/business/usecases/execute.ts
|
|
1684
1715
|
var ExecuteTasksUseCase = class {
|
|
1685
1716
|
constructor(persistence, aiSession, promptBuilder, parser, ui, logger, external, fs, signalParser2, signalHandler, signalBus) {
|
|
1686
1717
|
this.persistence = persistence;
|
|
@@ -1940,6 +1971,10 @@ ${instructions}`;
|
|
|
1940
1971
|
);
|
|
1941
1972
|
if (finishStatus === "done") finishStatus = "failed";
|
|
1942
1973
|
}
|
|
1974
|
+
await recoverDirtyTree(
|
|
1975
|
+
{ external: this.external, logger: this.logger, signalBus: this.signalBus },
|
|
1976
|
+
{ sprintId: sprint.id, taskId: syntheticTask.id, taskName: syntheticTask.name, repoPath }
|
|
1977
|
+
);
|
|
1943
1978
|
this.signalBus.emit({
|
|
1944
1979
|
type: "task-finished",
|
|
1945
1980
|
sprintId: sprint.id,
|
|
@@ -2760,6 +2795,19 @@ function evaluateTask(deps) {
|
|
|
2760
2795
|
});
|
|
2761
2796
|
}
|
|
2762
2797
|
|
|
2798
|
+
// src/business/pipelines/execute/steps/recover-dirty-tree.ts
|
|
2799
|
+
function recoverDirtyTree2(deps) {
|
|
2800
|
+
return step("recover-dirty-tree", async (ctx) => {
|
|
2801
|
+
const { task, sprint } = ctx;
|
|
2802
|
+
const repoPath = await deps.persistence.resolveRepoPath(task.repoId);
|
|
2803
|
+
await recoverDirtyTree(
|
|
2804
|
+
{ external: deps.external, logger: deps.logger, signalBus: deps.signalBus },
|
|
2805
|
+
{ sprintId: sprint.id, taskId: task.id, taskName: task.name, repoPath }
|
|
2806
|
+
);
|
|
2807
|
+
return Result.ok({});
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2763
2811
|
// src/business/pipelines/execute/steps/mark-done.ts
|
|
2764
2812
|
function markDone(deps) {
|
|
2765
2813
|
return step("mark-done", async (ctx) => {
|
|
@@ -2823,6 +2871,14 @@ function createPerTaskPipeline(deps, useCase, options = {}) {
|
|
|
2823
2871
|
options
|
|
2824
2872
|
})
|
|
2825
2873
|
),
|
|
2874
|
+
trace(
|
|
2875
|
+
recoverDirtyTree2({
|
|
2876
|
+
persistence: deps.persistence,
|
|
2877
|
+
external: deps.external,
|
|
2878
|
+
logger: deps.logger,
|
|
2879
|
+
signalBus: deps.signalBus
|
|
2880
|
+
})
|
|
2881
|
+
),
|
|
2826
2882
|
trace(markDone({ persistence: deps.persistence, logger: deps.logger, signalBus: deps.signalBus }))
|
|
2827
2883
|
]);
|
|
2828
2884
|
}
|
|
@@ -5217,6 +5273,25 @@ function hasUncommittedChanges(cwd) {
|
|
|
5217
5273
|
}
|
|
5218
5274
|
return result.stdout.trim().length > 0;
|
|
5219
5275
|
}
|
|
5276
|
+
function autoCommit(cwd, message) {
|
|
5277
|
+
assertSafeCwd(cwd);
|
|
5278
|
+
const add = spawnSync3("git", ["add", "-A"], {
|
|
5279
|
+
cwd,
|
|
5280
|
+
encoding: "utf-8",
|
|
5281
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5282
|
+
});
|
|
5283
|
+
if (add.status !== 0) {
|
|
5284
|
+
throw new Error(`Failed to stage changes in ${cwd}: ${add.stderr.trim()}`);
|
|
5285
|
+
}
|
|
5286
|
+
const commit = spawnSync3("git", ["commit", "-m", message], {
|
|
5287
|
+
cwd,
|
|
5288
|
+
encoding: "utf-8",
|
|
5289
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5290
|
+
});
|
|
5291
|
+
if (commit.status !== 0) {
|
|
5292
|
+
throw new Error(`Failed to commit in ${cwd}: ${commit.stderr.trim() || commit.stdout.trim()}`);
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5220
5295
|
function generateBranchName(sprintId) {
|
|
5221
5296
|
return `ralphctl/${sprintId}`;
|
|
5222
5297
|
}
|
|
@@ -5276,6 +5351,10 @@ var DefaultExternalAdapter = class {
|
|
|
5276
5351
|
hasUncommittedChanges(projectPath) {
|
|
5277
5352
|
return hasUncommittedChanges(projectPath);
|
|
5278
5353
|
}
|
|
5354
|
+
autoCommit(projectPath, message) {
|
|
5355
|
+
autoCommit(projectPath, message);
|
|
5356
|
+
return Promise.resolve();
|
|
5357
|
+
}
|
|
5279
5358
|
createAndCheckoutBranch(projectPath, branchName) {
|
|
5280
5359
|
createAndCheckoutBranch(projectPath, branchName);
|
|
5281
5360
|
}
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
updateTask,
|
|
53
53
|
updateTaskStatus,
|
|
54
54
|
validateImportTasks
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-3HJNVQ7N.mjs";
|
|
56
56
|
import {
|
|
57
57
|
fetchIssueFromUrl,
|
|
58
58
|
formatIssueContext,
|
|
@@ -177,7 +177,7 @@ import {
|
|
|
177
177
|
// package.json
|
|
178
178
|
var package_default = {
|
|
179
179
|
name: "ralphctl",
|
|
180
|
-
version: "0.4.
|
|
180
|
+
version: "0.4.1",
|
|
181
181
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
|
|
182
182
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
183
183
|
type: "module",
|
package/dist/cli.mjs
CHANGED
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
ticketRefineCommand,
|
|
42
42
|
ticketRemoveCommand,
|
|
43
43
|
ticketShowCommand
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-SM4GGZSU.mjs";
|
|
45
45
|
import {
|
|
46
46
|
projectAddCommand
|
|
47
47
|
} from "./chunk-D2YGPLIV.mjs";
|
|
@@ -55,7 +55,7 @@ import "./chunk-NUYQK5MN.mjs";
|
|
|
55
55
|
import {
|
|
56
56
|
getTasks,
|
|
57
57
|
sprintStartCommand
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-3HJNVQ7N.mjs";
|
|
59
59
|
import {
|
|
60
60
|
truncate
|
|
61
61
|
} from "./chunk-JOQO4HMM.mjs";
|
|
@@ -705,7 +705,7 @@ async function main() {
|
|
|
705
705
|
const isBare = argv.length <= 2;
|
|
706
706
|
const isInteractive = argv[2] === "interactive";
|
|
707
707
|
if (isBare || isInteractive) {
|
|
708
|
-
const { mountInkApp } = await import("./mount-
|
|
708
|
+
const { mountInkApp } = await import("./mount-2N6H5CWA.mjs");
|
|
709
709
|
const { fallback } = await mountInkApp({ initialView: "repl" });
|
|
710
710
|
if (!fallback) return;
|
|
711
711
|
printBanner();
|
|
@@ -716,10 +716,10 @@ async function main() {
|
|
|
716
716
|
return;
|
|
717
717
|
}
|
|
718
718
|
if (argv[2] === "sprint" && argv[3] === "start") {
|
|
719
|
-
const { parseSprintStartArgs } = await import("./start-
|
|
719
|
+
const { parseSprintStartArgs } = await import("./start-IUDCXIEA.mjs");
|
|
720
720
|
const parsed = parseSprintStartArgs(argv.slice(4));
|
|
721
721
|
if (parsed.ok) {
|
|
722
|
-
const { mountInkApp } = await import("./mount-
|
|
722
|
+
const { mountInkApp } = await import("./mount-2N6H5CWA.mjs");
|
|
723
723
|
const { getSharedDeps } = await import("./bootstrap-FMHG6DRY.mjs");
|
|
724
724
|
let sprintId;
|
|
725
725
|
try {
|
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
ticketShowCommand,
|
|
63
63
|
useCurrentPrompt,
|
|
64
64
|
validateConfigValue
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-SM4GGZSU.mjs";
|
|
66
66
|
import {
|
|
67
67
|
PromptCancelledError,
|
|
68
68
|
projectAddCommand
|
|
@@ -109,7 +109,7 @@ import {
|
|
|
109
109
|
sprintStartCommand,
|
|
110
110
|
updateTaskStatus,
|
|
111
111
|
withSuspendedTui
|
|
112
|
-
} from "./chunk-
|
|
112
|
+
} from "./chunk-3HJNVQ7N.mjs";
|
|
113
113
|
import {
|
|
114
114
|
addTicket,
|
|
115
115
|
allRequirementsApproved,
|
|
@@ -31,7 +31,10 @@ something entirely new (create a file, add a feature, tweak a script), do exactl
|
|
|
31
31
|
it passes. If no check script is configured, skip this step.
|
|
32
32
|
4. **Output verification results** — Wrap any verification output in `<task-verified>...</task-verified>`. If you
|
|
33
33
|
skipped step 3, emit `<task-verified>no check script configured; change applied</task-verified>`.
|
|
34
|
-
5. **
|
|
34
|
+
5. **Commit your work** — Stage the modified files and create a git commit with a descriptive message summarising the
|
|
35
|
+
feedback you implemented. The harness refuses to mark the task done with a dirty working tree.
|
|
36
|
+
6. **Signal completion** — Output `<task-complete>` once the change is applied, verification (if any) passed, and the
|
|
37
|
+
commit has landed.
|
|
35
38
|
|
|
36
39
|
Only signal `<task-blocked>reason</task-blocked>` if the feedback is literally impossible to carry out (e.g., asks
|
|
37
40
|
you to edit a file in a repository you don't have access to). Ambiguity is **not** a blocker — make a reasonable
|
|
@@ -42,6 +45,8 @@ interpretation and proceed.
|
|
|
42
45
|
- **The feedback is the authoritative instruction** — implement it even if it seems unrelated to the completed tasks.
|
|
43
46
|
- **Do the smallest change that fully satisfies the feedback** — no speculative refactors, no adjacent cleanup.
|
|
44
47
|
- **Make the edits — don't just describe them** — the harness does not apply edits for you; you must write the files.
|
|
48
|
+
- **Must commit** — Create a git commit before signaling completion. Uncommitted changes leave the sprint branch dirty
|
|
49
|
+
and block sprint close.
|
|
45
50
|
|
|
46
51
|
</constraints>
|
|
47
52
|
|
|
@@ -21,6 +21,11 @@ These verification criteria are the pre-agreed definition of "done" — your pri
|
|
|
21
21
|
|
|
22
22
|
## Review Protocol
|
|
23
23
|
|
|
24
|
+
**You are a reviewer — do not edit files.** If you believe a fix is needed, emit `<evaluation-failed>` with a concrete
|
|
25
|
+
critique; the harness will resume the generator to apply the fix. Do not run `git stash`, do not edit tests, do not
|
|
26
|
+
create commits. Your tools are read-only: `git status`, `git log`, `git diff`, file reads, and running existing check
|
|
27
|
+
scripts. Any write operation is a protocol violation.
|
|
28
|
+
|
|
24
29
|
You are working in this project directory:
|
|
25
30
|
|
|
26
31
|
```
|
|
@@ -37,7 +42,8 @@ Run deterministic checks first — these are cheap, fast, and authoritative.
|
|
|
37
42
|
|
|
38
43
|
1. **Run the check script** (if provided above) — this is the same gate the harness uses post-task. If it fails, the
|
|
39
44
|
implementation fails regardless of how good the code looks. Record the output.
|
|
40
|
-
2. **Run `git status`** —
|
|
45
|
+
2. **Run `git status`** — the tree MUST be clean. Uncommitted changes from the generator are a Completeness failure;
|
|
46
|
+
uncommitted changes from you are a protocol violation.
|
|
41
47
|
3. **Run `git log --oneline -10`** — identify which commits belong to this task
|
|
42
48
|
|
|
43
49
|
Computational results are ground truth. If the check script fails, stop early — the implementation does not pass.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ralphctl",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|