ralphctl 0.6.1 → 0.6.3
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/cli.mjs +482 -416
- package/dist/manifest.json +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
} from "./chunk-HIU74KTO.mjs";
|
|
23
23
|
|
|
24
24
|
// src/application/cli/entrypoint.ts
|
|
25
|
+
import { realpathSync } from "fs";
|
|
26
|
+
import { pathToFileURL } from "url";
|
|
25
27
|
import { Command } from "commander";
|
|
26
28
|
|
|
27
29
|
// src/kernel/algorithms/rate-limit-coordinator.ts
|
|
@@ -210,8 +212,8 @@ async function directoryHasEntries(p) {
|
|
|
210
212
|
try {
|
|
211
213
|
const s = await stat(p);
|
|
212
214
|
if (!s.isDirectory()) return false;
|
|
213
|
-
const { readdir:
|
|
214
|
-
const entries = await
|
|
215
|
+
const { readdir: readdir5 } = await import("fs/promises");
|
|
216
|
+
const entries = await readdir5(p);
|
|
215
217
|
return entries.length > 0;
|
|
216
218
|
} catch {
|
|
217
219
|
return false;
|
|
@@ -408,10 +410,9 @@ function renderEvaluateWorkspaceSection(workspaceDir) {
|
|
|
408
410
|
"",
|
|
409
411
|
"- `task.md` \u2014 the current task being evaluated (description, steps, verification criteria, status)",
|
|
410
412
|
"- `requirements/<ticket-id>.md` \u2014 the refined requirements + raw ticket text that motivated this task",
|
|
411
|
-
"- `tasks.md`
|
|
413
|
+
"- `tasks.md` \u2014 the full task plan, including any sibling tasks' evaluator output rendered inline where present (cross-task consistency + quality bar so far)",
|
|
412
414
|
"- `project-context.md` \u2014 the project's CLAUDE.md / .github/copilot-instructions.md (when present in the target repo)",
|
|
413
|
-
"- `dimensions.md` \u2014 the four floor dimensions plus any extra dimensions the planner emitted on this task"
|
|
414
|
-
"- `evaluations/<task-id>.md` \u2014 prior task evaluations from earlier in this sprint (the quality bar so far)"
|
|
415
|
+
"- `dimensions.md` \u2014 the four floor dimensions plus any extra dimensions the planner emitted on this task"
|
|
415
416
|
].join("\n");
|
|
416
417
|
}
|
|
417
418
|
function renderDoneCriteriaSection(doneCriteriaBullet) {
|
|
@@ -570,8 +571,8 @@ ${input.task.steps.map((s, i) => `${String(i + 1)}. ${s}`).join("\n")}` : "";
|
|
|
570
571
|
${input.task.verificationCriteria.map((c34) => `- ${c34}`).join("\n")}` : "";
|
|
571
572
|
const branchLine = input.sprint.branch !== null ? `**Branch:** \`${input.sprint.branch}\`` : "";
|
|
572
573
|
const checkScriptSection = renderCheckScriptSection(input.checkScript);
|
|
573
|
-
const
|
|
574
|
-
const environmentStatus =
|
|
574
|
+
const setupRanAtForRepo = input.sprint.setupRanAt.get(input.task.projectPath);
|
|
575
|
+
const environmentStatus = setupRanAtForRepo !== void 0 ? `Setup script ran at ${String(setupRanAtForRepo)}.` : "Not run.";
|
|
575
576
|
const rendered = substitute(tpl.value, {
|
|
576
577
|
TASK_NAME: input.task.name,
|
|
577
578
|
TASK_ID: String(input.task.id),
|
|
@@ -913,8 +914,7 @@ function getAdapter(name) {
|
|
|
913
914
|
|
|
914
915
|
// src/integration/persistence/session-md-writer.ts
|
|
915
916
|
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
916
|
-
import { dirname as dirname2
|
|
917
|
-
import { readdir as readdir2 } from "fs/promises";
|
|
917
|
+
import { dirname as dirname2 } from "path";
|
|
918
918
|
var FRONTMATTER_DELIM = "---";
|
|
919
919
|
function renderFrontmatter(fields) {
|
|
920
920
|
const lines = [FRONTMATTER_DELIM];
|
|
@@ -994,7 +994,8 @@ async function writeSessionFinish(args) {
|
|
|
994
994
|
const fm2 = renderFrontmatter({
|
|
995
995
|
finished: args.finished,
|
|
996
996
|
exitCode: args.exitCode,
|
|
997
|
-
sessionId: args.sessionId
|
|
997
|
+
sessionId: args.sessionId,
|
|
998
|
+
model: args.model
|
|
998
999
|
});
|
|
999
1000
|
return writeFileSafe(args.path, `${fm2}
|
|
1000
1001
|
|
|
@@ -1008,6 +1009,7 @@ _(no prompt recorded \u2014 session finish without start)_
|
|
|
1008
1009
|
merged["finished"] = args.finished;
|
|
1009
1010
|
merged["exitCode"] = args.exitCode;
|
|
1010
1011
|
if (args.sessionId !== void 0) merged["sessionId"] = args.sessionId;
|
|
1012
|
+
if (args.model !== void 0) merged["model"] = args.model;
|
|
1011
1013
|
const fm = renderFrontmatter(merged);
|
|
1012
1014
|
const content = `${fm}
|
|
1013
1015
|
|
|
@@ -1054,22 +1056,6 @@ function unquote(s) {
|
|
|
1054
1056
|
}
|
|
1055
1057
|
return s;
|
|
1056
1058
|
}
|
|
1057
|
-
async function nextSessionPath(unitDir) {
|
|
1058
|
-
let entries;
|
|
1059
|
-
try {
|
|
1060
|
-
entries = await readdir2(unitDir);
|
|
1061
|
-
} catch {
|
|
1062
|
-
return join5(unitDir, "session-1.md");
|
|
1063
|
-
}
|
|
1064
|
-
let max = 0;
|
|
1065
|
-
for (const entry of entries) {
|
|
1066
|
-
const m = /^session-(\d+)\.md$/.exec(entry);
|
|
1067
|
-
if (!m) continue;
|
|
1068
|
-
const n = Number(m[1]);
|
|
1069
|
-
if (Number.isFinite(n) && n > max) max = n;
|
|
1070
|
-
}
|
|
1071
|
-
return join5(unitDir, `session-${String(max + 1)}.md`);
|
|
1072
|
-
}
|
|
1073
1059
|
|
|
1074
1060
|
// src/integration/ai/session/session-runner.ts
|
|
1075
1061
|
import { spawn } from "child_process";
|
|
@@ -1365,26 +1351,28 @@ var ProviderAiSessionAdapter = class {
|
|
|
1365
1351
|
* The actual provider exit code isn't surfaced through the runner's
|
|
1366
1352
|
* typed Result — `1` is a faithful "spawn failed" stand-in for audit
|
|
1367
1353
|
* purposes; the underlying error's message is already in the logs.
|
|
1354
|
+
*
|
|
1355
|
+
* `model` is the first audit hint we have for headless spawns where
|
|
1356
|
+
* the runner picks the model — `writeSessionFinish` merges it into
|
|
1357
|
+
* the frontmatter alongside the other finish fields in a single write.
|
|
1368
1358
|
*/
|
|
1369
1359
|
async writeSessionMdFinishHeadless(options, result) {
|
|
1370
1360
|
const path = options.sessionMdPath;
|
|
1371
1361
|
if (path === void 0) return;
|
|
1372
1362
|
const sessionId = result.ok ? result.value.sessionId : void 0;
|
|
1363
|
+
const model = result.ok ? result.value.model : void 0;
|
|
1373
1364
|
const written = await writeSessionFinish({
|
|
1374
1365
|
path: String(path),
|
|
1375
1366
|
finished: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1376
1367
|
exitCode: result.ok ? 0 : 1,
|
|
1377
|
-
...sessionId !== void 0 ? { sessionId } : {}
|
|
1368
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
1369
|
+
...model !== void 0 ? { model } : {}
|
|
1378
1370
|
});
|
|
1379
1371
|
if (!written.ok) {
|
|
1380
1372
|
this.opts.logger?.warn("failed to write session.md (finish) \u2014 spawn already settled", {
|
|
1381
1373
|
path: String(path),
|
|
1382
1374
|
error: written.error.message
|
|
1383
1375
|
});
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
|
-
if (result.ok && result.value.model !== void 0) {
|
|
1387
|
-
await this.patchSessionMdModel(String(path), result.value.model);
|
|
1388
1376
|
}
|
|
1389
1377
|
}
|
|
1390
1378
|
/**
|
|
@@ -1407,33 +1395,6 @@ var ProviderAiSessionAdapter = class {
|
|
|
1407
1395
|
});
|
|
1408
1396
|
}
|
|
1409
1397
|
}
|
|
1410
|
-
/**
|
|
1411
|
-
* Patch the resolved `model` into an existing `session.md`. Reuses
|
|
1412
|
-
* `writeSessionFinish` to round-trip frontmatter without touching the
|
|
1413
|
-
* prompt body. Best-effort — write failures log a warn.
|
|
1414
|
-
*
|
|
1415
|
-
* Implementation note: `writeSessionFinish` was scoped to the
|
|
1416
|
-
* standard finish fields only. The narrow `model` patch here goes
|
|
1417
|
-
* through `writeSessionStart` would clobber the body, so we re-read,
|
|
1418
|
-
* splice the field, and re-emit via the same writer surface. This
|
|
1419
|
-
* keeps the YAML-handling logic in one place (`session-md-writer`)
|
|
1420
|
-
* even at the cost of a second IO round-trip — the audit pack is
|
|
1421
|
-
* cold-path and we'd rather centralise format knowledge.
|
|
1422
|
-
*/
|
|
1423
|
-
async patchSessionMdModel(path, model) {
|
|
1424
|
-
try {
|
|
1425
|
-
const fs = await import("fs/promises");
|
|
1426
|
-
const existing = await fs.readFile(path, "utf-8");
|
|
1427
|
-
const patched = upsertFrontmatterField(existing, "model", model);
|
|
1428
|
-
if (patched === existing) return;
|
|
1429
|
-
await fs.writeFile(path, patched, { encoding: "utf-8", mode: 384 });
|
|
1430
|
-
} catch (err) {
|
|
1431
|
-
this.opts.logger?.warn("failed to patch session.md model field", {
|
|
1432
|
-
path,
|
|
1433
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1434
|
-
});
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
1398
|
/**
|
|
1438
1399
|
* Build the flag list audited into session.md. Headless: the runner
|
|
1439
1400
|
* appends `--resume <id>` when `resumeSessionId` is set, so we mirror
|
|
@@ -1480,28 +1441,6 @@ function stripTrailingPromptSlot(args) {
|
|
|
1480
1441
|
if (end > 0 && args[end - 1] === "--") end -= 1;
|
|
1481
1442
|
return args.slice(0, end);
|
|
1482
1443
|
}
|
|
1483
|
-
function upsertFrontmatterField(content, key, value) {
|
|
1484
|
-
const lines = content.split("\n");
|
|
1485
|
-
if (lines[0]?.trim() !== "---") return content;
|
|
1486
|
-
let closeIdx = -1;
|
|
1487
|
-
for (let i = 1; i < lines.length; i += 1) {
|
|
1488
|
-
if (lines[i]?.trim() === "---") {
|
|
1489
|
-
closeIdx = i;
|
|
1490
|
-
break;
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
if (closeIdx < 0) return content;
|
|
1494
|
-
const keyRegex = new RegExp(`^${key}\\s*:`);
|
|
1495
|
-
for (let i = 1; i < closeIdx; i += 1) {
|
|
1496
|
-
const line = lines[i] ?? "";
|
|
1497
|
-
if (keyRegex.test(line.trim())) {
|
|
1498
|
-
lines[i] = `${key}: ${value}`;
|
|
1499
|
-
return lines.join("\n");
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
lines.splice(closeIdx, 0, `${key}: ${value}`);
|
|
1503
|
-
return lines.join("\n");
|
|
1504
|
-
}
|
|
1505
1444
|
|
|
1506
1445
|
// src/integration/ai/session/process-runner.ts
|
|
1507
1446
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -1603,20 +1542,20 @@ var NodeProcessRunner = class {
|
|
|
1603
1542
|
|
|
1604
1543
|
// src/integration/ai/skills/bundled-skills-copier.ts
|
|
1605
1544
|
import { existsSync as existsSync3 } from "fs";
|
|
1606
|
-
import { readdir as
|
|
1607
|
-
import { dirname as dirname3, join as
|
|
1545
|
+
import { readdir as readdir3, rm, rmdir } from "fs/promises";
|
|
1546
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
1608
1547
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1609
1548
|
|
|
1610
1549
|
// src/integration/ai/skills/copy-tree.ts
|
|
1611
|
-
import { copyFile, mkdir as mkdir2, readdir as
|
|
1612
|
-
import { join as
|
|
1550
|
+
import { copyFile, mkdir as mkdir2, readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
1551
|
+
import { join as join5 } from "path";
|
|
1613
1552
|
async function copyTree(src, dst) {
|
|
1614
1553
|
try {
|
|
1615
1554
|
await mkdir2(dst, { recursive: true });
|
|
1616
|
-
const dirents = await
|
|
1555
|
+
const dirents = await readdir2(src, { withFileTypes: true });
|
|
1617
1556
|
for (const d of dirents) {
|
|
1618
|
-
const s =
|
|
1619
|
-
const t =
|
|
1557
|
+
const s = join5(src, d.name);
|
|
1558
|
+
const t = join5(dst, d.name);
|
|
1620
1559
|
if (d.isDirectory()) {
|
|
1621
1560
|
const r = await copyTree(s, t);
|
|
1622
1561
|
if (!r.ok) return r;
|
|
@@ -1652,12 +1591,12 @@ async function copyTree(src, dst) {
|
|
|
1652
1591
|
// src/integration/ai/skills/skill-git-exclude.ts
|
|
1653
1592
|
import { existsSync as existsSync2 } from "fs";
|
|
1654
1593
|
import { mkdir as mkdir3, readFile as readFile3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
1655
|
-
import { join as
|
|
1594
|
+
import { join as join6 } from "path";
|
|
1656
1595
|
var BEGIN_MARKER = "# >>> ralphctl-managed-skills (do not edit) >>>";
|
|
1657
1596
|
var END_MARKER = "# <<< ralphctl-managed-skills <<<";
|
|
1658
1597
|
var EXCLUDE_PATTERNS = [".claude/skills/"];
|
|
1659
1598
|
async function gitInfoDir(cwd) {
|
|
1660
|
-
const dotGit =
|
|
1599
|
+
const dotGit = join6(cwd, ".git");
|
|
1661
1600
|
if (!existsSync2(dotGit)) return null;
|
|
1662
1601
|
try {
|
|
1663
1602
|
const s = await stat3(dotGit);
|
|
@@ -1665,12 +1604,12 @@ async function gitInfoDir(cwd) {
|
|
|
1665
1604
|
} catch {
|
|
1666
1605
|
return null;
|
|
1667
1606
|
}
|
|
1668
|
-
return
|
|
1607
|
+
return join6(dotGit, "info");
|
|
1669
1608
|
}
|
|
1670
1609
|
async function addRalphctlSkillsExclude(cwd) {
|
|
1671
1610
|
const infoDir = await gitInfoDir(cwd);
|
|
1672
1611
|
if (infoDir === null) return Result.ok();
|
|
1673
|
-
const excludeFile =
|
|
1612
|
+
const excludeFile = join6(infoDir, "exclude");
|
|
1674
1613
|
try {
|
|
1675
1614
|
await mkdir3(infoDir, { recursive: true });
|
|
1676
1615
|
const existing = existsSync2(excludeFile) ? await readFile3(excludeFile, "utf8") : "";
|
|
@@ -1697,7 +1636,7 @@ ${block}`;
|
|
|
1697
1636
|
async function removeRalphctlSkillsExclude(cwd) {
|
|
1698
1637
|
const infoDir = await gitInfoDir(cwd);
|
|
1699
1638
|
if (infoDir === null) return Result.ok();
|
|
1700
|
-
const excludeFile =
|
|
1639
|
+
const excludeFile = join6(infoDir, "exclude");
|
|
1701
1640
|
if (!existsSync2(excludeFile)) return Result.ok();
|
|
1702
1641
|
try {
|
|
1703
1642
|
const body = await readFile3(excludeFile, "utf8");
|
|
@@ -1735,10 +1674,10 @@ function stripMarkerBlock(body) {
|
|
|
1735
1674
|
}
|
|
1736
1675
|
|
|
1737
1676
|
// src/integration/ai/skills/bundled-skills-copier.ts
|
|
1738
|
-
var SKILLS_SUBDIR =
|
|
1677
|
+
var SKILLS_SUBDIR = join7(".claude", "skills");
|
|
1739
1678
|
var HERE2 = dirname3(fileURLToPath2(import.meta.url));
|
|
1740
1679
|
function bundledSkillsRootDir() {
|
|
1741
|
-
const distRoot =
|
|
1680
|
+
const distRoot = join7(HERE2, "skills");
|
|
1742
1681
|
if (existsSync3(distRoot)) return AbsolutePath.trustString(distRoot);
|
|
1743
1682
|
return AbsolutePath.trustString(HERE2);
|
|
1744
1683
|
}
|
|
@@ -1754,12 +1693,12 @@ var FileBundledSkillsCopier = class {
|
|
|
1754
1693
|
this.bundledRootDir = opts.bundledRootDir ?? bundledSkillsRootDir();
|
|
1755
1694
|
}
|
|
1756
1695
|
async install(sessionDir, phase) {
|
|
1757
|
-
const skillsDir =
|
|
1696
|
+
const skillsDir = join7(sessionDir, SKILLS_SUBDIR);
|
|
1758
1697
|
const sources = await this.collectSourceSkills(phase);
|
|
1759
1698
|
if (!sources.ok) return Result.error(sources.error);
|
|
1760
1699
|
const tracked = this.installed.get(String(sessionDir)) ?? /* @__PURE__ */ new Set();
|
|
1761
1700
|
for (const [name, srcDir] of sources.value) {
|
|
1762
|
-
const dst =
|
|
1701
|
+
const dst = join7(skillsDir, name);
|
|
1763
1702
|
if (existsSync3(dst)) {
|
|
1764
1703
|
continue;
|
|
1765
1704
|
}
|
|
@@ -1786,10 +1725,10 @@ var FileBundledSkillsCopier = class {
|
|
|
1786
1725
|
await removeRalphctlSkillsExclude(sessionDir);
|
|
1787
1726
|
return Result.ok();
|
|
1788
1727
|
}
|
|
1789
|
-
const skillsDir =
|
|
1728
|
+
const skillsDir = join7(sessionDir, SKILLS_SUBDIR);
|
|
1790
1729
|
try {
|
|
1791
1730
|
for (const name of tracked) {
|
|
1792
|
-
await rm(
|
|
1731
|
+
await rm(join7(skillsDir, name), { recursive: true, force: true });
|
|
1793
1732
|
}
|
|
1794
1733
|
this.installed.delete(key);
|
|
1795
1734
|
} catch (err) {
|
|
@@ -1803,7 +1742,7 @@ var FileBundledSkillsCopier = class {
|
|
|
1803
1742
|
);
|
|
1804
1743
|
}
|
|
1805
1744
|
await tryRmdirIfEmpty(skillsDir);
|
|
1806
|
-
await tryRmdirIfEmpty(
|
|
1745
|
+
await tryRmdirIfEmpty(join7(sessionDir, ".claude"));
|
|
1807
1746
|
await removeRalphctlSkillsExclude(sessionDir);
|
|
1808
1747
|
return Result.ok();
|
|
1809
1748
|
}
|
|
@@ -1817,7 +1756,7 @@ var FileBundledSkillsCopier = class {
|
|
|
1817
1756
|
async collectSourceSkills(phase) {
|
|
1818
1757
|
const sources = /* @__PURE__ */ new Map();
|
|
1819
1758
|
for (const sub of ["default", phase]) {
|
|
1820
|
-
const dir =
|
|
1759
|
+
const dir = join7(this.bundledRootDir, sub);
|
|
1821
1760
|
const r = await listSkillDirs(dir);
|
|
1822
1761
|
if (!r.ok) return Result.error(r.error);
|
|
1823
1762
|
for (const [name, abs] of r.value) {
|
|
@@ -1830,10 +1769,10 @@ var FileBundledSkillsCopier = class {
|
|
|
1830
1769
|
async function listSkillDirs(dir) {
|
|
1831
1770
|
if (!existsSync3(dir)) return Result.ok([]);
|
|
1832
1771
|
try {
|
|
1833
|
-
const dirents = await
|
|
1772
|
+
const dirents = await readdir3(dir, { withFileTypes: true });
|
|
1834
1773
|
const out = [];
|
|
1835
1774
|
for (const d of dirents) {
|
|
1836
|
-
if (d.isDirectory()) out.push([d.name,
|
|
1775
|
+
if (d.isDirectory()) out.push([d.name, join7(dir, d.name)]);
|
|
1837
1776
|
}
|
|
1838
1777
|
return Result.ok(out);
|
|
1839
1778
|
} catch (err) {
|
|
@@ -1850,7 +1789,7 @@ async function listSkillDirs(dir) {
|
|
|
1850
1789
|
async function tryRmdirIfEmpty(dir) {
|
|
1851
1790
|
if (!existsSync3(dir)) return;
|
|
1852
1791
|
try {
|
|
1853
|
-
const entries = await
|
|
1792
|
+
const entries = await readdir3(dir);
|
|
1854
1793
|
if (entries.length === 0) await rmdir(dir);
|
|
1855
1794
|
} catch {
|
|
1856
1795
|
}
|
|
@@ -2154,7 +2093,12 @@ var DefaultExternalAdapter = class {
|
|
|
2154
2093
|
formatIssueContext(issue) {
|
|
2155
2094
|
return this.issues.format(issue);
|
|
2156
2095
|
}
|
|
2157
|
-
// ---
|
|
2096
|
+
// --- Setup / check script execution ---------------------------------
|
|
2097
|
+
async runSetupScript(projectPath, script, timeout) {
|
|
2098
|
+
const r = await this.checkScripts.run(projectPath, script, "setup", timeout);
|
|
2099
|
+
if (r.ok) return r.value;
|
|
2100
|
+
return { passed: false, output: `[setup-script error: ${r.error.message}]` };
|
|
2101
|
+
}
|
|
2158
2102
|
async runCheckScript(projectPath, script, phase, timeout) {
|
|
2159
2103
|
const r = await this.checkScripts.run(projectPath, script, phase, timeout);
|
|
2160
2104
|
if (r.ok) return r.value;
|
|
@@ -2930,9 +2874,9 @@ var JsonLogger = class _JsonLogger {
|
|
|
2930
2874
|
|
|
2931
2875
|
// src/integration/logging/jsonl-file-writer.ts
|
|
2932
2876
|
import { appendFile, mkdir as mkdir4 } from "fs/promises";
|
|
2933
|
-
import { dirname as dirname4, join as
|
|
2877
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
2934
2878
|
function fileFor(opts) {
|
|
2935
|
-
return AbsolutePath.trustString(
|
|
2879
|
+
return AbsolutePath.trustString(join8(opts.logsDir, `${opts.sessionId}.jsonl`));
|
|
2936
2880
|
}
|
|
2937
2881
|
var JsonlFileWriter = class {
|
|
2938
2882
|
constructor(opts) {
|
|
@@ -3302,7 +3246,7 @@ var NotFoundError = class extends Error {
|
|
|
3302
3246
|
|
|
3303
3247
|
// src/integration/persistence/json-io.ts
|
|
3304
3248
|
import { mkdir as mkdir6, readFile as readFile5, rename, writeFile as writeFile4 } from "fs/promises";
|
|
3305
|
-
import { dirname as dirname6, join as
|
|
3249
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
3306
3250
|
async function readJsonFile(path, schema2) {
|
|
3307
3251
|
let raw;
|
|
3308
3252
|
try {
|
|
@@ -3360,7 +3304,7 @@ ${issues}`,
|
|
|
3360
3304
|
);
|
|
3361
3305
|
}
|
|
3362
3306
|
const dir = dirname6(path);
|
|
3363
|
-
const tmp =
|
|
3307
|
+
const tmp = join9(dir, `.${pathBasename(path)}.${String(process.pid)}.${String(Date.now())}.${randomTail()}.tmp`);
|
|
3364
3308
|
try {
|
|
3365
3309
|
await mkdir6(dir, { recursive: true });
|
|
3366
3310
|
await writeFile4(tmp, JSON.stringify(validated.data, null, 2) + "\n", {
|
|
@@ -4036,7 +3980,7 @@ function errnoCode3(err) {
|
|
|
4036
3980
|
}
|
|
4037
3981
|
|
|
4038
3982
|
// src/integration/persistence/file-sprint-repository.ts
|
|
4039
|
-
import { mkdir as mkdir7, readdir as
|
|
3983
|
+
import { mkdir as mkdir7, readdir as readdir4, rm as rm2 } from "fs/promises";
|
|
4040
3984
|
|
|
4041
3985
|
// src/integration/persistence/schemas/sprint-schema.ts
|
|
4042
3986
|
import { z as z3 } from "zod";
|
|
@@ -4137,7 +4081,12 @@ var Sprint = class _Sprint {
|
|
|
4137
4081
|
activatedAt;
|
|
4138
4082
|
closedAt;
|
|
4139
4083
|
tickets;
|
|
4140
|
-
|
|
4084
|
+
/**
|
|
4085
|
+
* Audit trail of sprint-start setup-script runs, keyed by repo path.
|
|
4086
|
+
* Filled in by the `setup-scripts-sprint-start` chain leaf. Cleared on
|
|
4087
|
+
* sprint close so a re-opened sprint starts a fresh setup record.
|
|
4088
|
+
*/
|
|
4089
|
+
setupRanAt;
|
|
4141
4090
|
branch;
|
|
4142
4091
|
/**
|
|
4143
4092
|
* Pull / merge request URL recorded after `sprint create-pr` runs.
|
|
@@ -4165,7 +4114,7 @@ var Sprint = class _Sprint {
|
|
|
4165
4114
|
this.activatedAt = props.activatedAt;
|
|
4166
4115
|
this.closedAt = props.closedAt;
|
|
4167
4116
|
this.tickets = props.tickets;
|
|
4168
|
-
this.
|
|
4117
|
+
this.setupRanAt = props.setupRanAt;
|
|
4169
4118
|
this.branch = props.branch;
|
|
4170
4119
|
this.pullRequestUrl = props.pullRequestUrl;
|
|
4171
4120
|
this.projectName = props.projectName;
|
|
@@ -4192,7 +4141,7 @@ var Sprint = class _Sprint {
|
|
|
4192
4141
|
activatedAt: null,
|
|
4193
4142
|
closedAt: null,
|
|
4194
4143
|
tickets: [],
|
|
4195
|
-
|
|
4144
|
+
setupRanAt: /* @__PURE__ */ new Map(),
|
|
4196
4145
|
branch: null,
|
|
4197
4146
|
pullRequestUrl: null,
|
|
4198
4147
|
projectName: input.projectName,
|
|
@@ -4229,7 +4178,7 @@ var Sprint = class _Sprint {
|
|
|
4229
4178
|
this.with({
|
|
4230
4179
|
status: "closed",
|
|
4231
4180
|
closedAt: now,
|
|
4232
|
-
|
|
4181
|
+
setupRanAt: /* @__PURE__ */ new Map()
|
|
4233
4182
|
})
|
|
4234
4183
|
);
|
|
4235
4184
|
}
|
|
@@ -4268,7 +4217,7 @@ var Sprint = class _Sprint {
|
|
|
4268
4217
|
activatedAt: this.activatedAt,
|
|
4269
4218
|
closedAt: this.closedAt,
|
|
4270
4219
|
tickets: this.tickets,
|
|
4271
|
-
|
|
4220
|
+
setupRanAt: this.setupRanAt,
|
|
4272
4221
|
branch: this.branch,
|
|
4273
4222
|
pullRequestUrl: this.pullRequestUrl,
|
|
4274
4223
|
projectName: this.projectName,
|
|
@@ -4364,13 +4313,13 @@ var Sprint = class _Sprint {
|
|
|
4364
4313
|
return Result8.ok(this.with({ branch }));
|
|
4365
4314
|
}
|
|
4366
4315
|
/**
|
|
4367
|
-
* Stamp a
|
|
4316
|
+
* Stamp a setup-script run for one repo. Never fails — the harness owns
|
|
4368
4317
|
* this audit trail and the entity should not gate it.
|
|
4369
4318
|
*/
|
|
4370
|
-
|
|
4371
|
-
const next = new Map(this.
|
|
4319
|
+
recordSetupRun(repo, at) {
|
|
4320
|
+
const next = new Map(this.setupRanAt);
|
|
4372
4321
|
next.set(repo, at);
|
|
4373
|
-
return this.with({
|
|
4322
|
+
return this.with({ setupRanAt: next });
|
|
4374
4323
|
}
|
|
4375
4324
|
/**
|
|
4376
4325
|
* Record the pull / merge request URL published for the sprint branch.
|
|
@@ -4456,7 +4405,7 @@ var Sprint = class _Sprint {
|
|
|
4456
4405
|
activatedAt: "activatedAt" in partial ? partial.activatedAt ?? null : this.activatedAt,
|
|
4457
4406
|
closedAt: "closedAt" in partial ? partial.closedAt ?? null : this.closedAt,
|
|
4458
4407
|
tickets: partial.tickets ?? this.tickets,
|
|
4459
|
-
|
|
4408
|
+
setupRanAt: partial.setupRanAt ?? this.setupRanAt,
|
|
4460
4409
|
branch: "branch" in partial ? partial.branch ?? null : this.branch,
|
|
4461
4410
|
pullRequestUrl: "pullRequestUrl" in partial ? partial.pullRequestUrl ?? null : this.pullRequestUrl,
|
|
4462
4411
|
projectName: this.projectName,
|
|
@@ -4640,7 +4589,13 @@ var sprintJsonSchema = z3.object({
|
|
|
4640
4589
|
// to `[]` on a fresh draft sprint, populated post-plan).
|
|
4641
4590
|
affectedRepositories: z3.array(z3.string()),
|
|
4642
4591
|
// `Map<AbsolutePath, IsoTimestamp>` — serialised as a plain object map.
|
|
4643
|
-
|
|
4592
|
+
// Backwards-compat: v0.6.2 wrote this audit map under the legacy key
|
|
4593
|
+
// `checkRanAt` and may not have emitted any key at all on older drafts.
|
|
4594
|
+
// `.optional().default({})` accepts both shapes; Zod silently strips the
|
|
4595
|
+
// legacy `checkRanAt` payload on the next save (a stale audit trail under
|
|
4596
|
+
// the wrong key has no migration value — the worst case is one extra
|
|
4597
|
+
// setup run on first resume).
|
|
4598
|
+
setupRanAt: z3.record(z3.string(), z3.string()).optional().default({}),
|
|
4644
4599
|
tickets: z3.array(ticketJsonSchema)
|
|
4645
4600
|
});
|
|
4646
4601
|
var SYNTHETIC_SLUG_R = Slug.parse("rehydrate");
|
|
@@ -4653,9 +4608,9 @@ function toSprint(parsed) {
|
|
|
4653
4608
|
if (!r.ok) return Result.error(r.error);
|
|
4654
4609
|
tickets.push(r.value);
|
|
4655
4610
|
}
|
|
4656
|
-
const
|
|
4657
|
-
for (const [k, v] of Object.entries(parsed.
|
|
4658
|
-
|
|
4611
|
+
const setupRanAt = /* @__PURE__ */ new Map();
|
|
4612
|
+
for (const [k, v] of Object.entries(parsed.setupRanAt)) {
|
|
4613
|
+
setupRanAt.set(AbsolutePath.trustString(k), IsoTimestamp.trustString(v));
|
|
4659
4614
|
}
|
|
4660
4615
|
const created = Sprint.create({
|
|
4661
4616
|
id: SprintId.trustString(parsed.id),
|
|
@@ -4690,8 +4645,8 @@ function toSprint(parsed) {
|
|
|
4690
4645
|
const r = s.close(IsoTimestamp.trustString(at));
|
|
4691
4646
|
if (r.ok) s = r.value;
|
|
4692
4647
|
}
|
|
4693
|
-
for (const [path, at] of
|
|
4694
|
-
s = s.
|
|
4648
|
+
for (const [path, at] of setupRanAt) {
|
|
4649
|
+
s = s.recordSetupRun(path, at);
|
|
4695
4650
|
}
|
|
4696
4651
|
if (parsed.branch !== null) {
|
|
4697
4652
|
const r = s.setBranch(parsed.branch);
|
|
@@ -4704,9 +4659,9 @@ function toSprint(parsed) {
|
|
|
4704
4659
|
return Result.ok(s);
|
|
4705
4660
|
}
|
|
4706
4661
|
function fromSprint(sprint) {
|
|
4707
|
-
const
|
|
4708
|
-
for (const [k, v] of sprint.
|
|
4709
|
-
|
|
4662
|
+
const setupRanAt = {};
|
|
4663
|
+
for (const [k, v] of sprint.setupRanAt) {
|
|
4664
|
+
setupRanAt[k] = v;
|
|
4710
4665
|
}
|
|
4711
4666
|
return {
|
|
4712
4667
|
id: sprint.id,
|
|
@@ -4719,7 +4674,7 @@ function fromSprint(sprint) {
|
|
|
4719
4674
|
pullRequestUrl: sprint.pullRequestUrl,
|
|
4720
4675
|
projectName: sprint.projectName,
|
|
4721
4676
|
affectedRepositories: [...sprint.affectedRepositories],
|
|
4722
|
-
|
|
4677
|
+
setupRanAt,
|
|
4723
4678
|
tickets: sprint.tickets.map(fromTicket)
|
|
4724
4679
|
};
|
|
4725
4680
|
}
|
|
@@ -4807,7 +4762,7 @@ var FileSprintRepository = class {
|
|
|
4807
4762
|
async list() {
|
|
4808
4763
|
let entries;
|
|
4809
4764
|
try {
|
|
4810
|
-
const dirents = await
|
|
4765
|
+
const dirents = await readdir4(this.paths.sprintsDir, {
|
|
4811
4766
|
withFileTypes: true
|
|
4812
4767
|
});
|
|
4813
4768
|
entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
|
|
@@ -5496,8 +5451,8 @@ var FileWriteContextFileAdapter = class {
|
|
|
5496
5451
|
};
|
|
5497
5452
|
|
|
5498
5453
|
// src/integration/persistence/execution-unit-builder.ts
|
|
5499
|
-
import { readFile as readFile6
|
|
5500
|
-
import { join as
|
|
5454
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
5455
|
+
import { join as join11 } from "path";
|
|
5501
5456
|
|
|
5502
5457
|
// src/integration/persistence/unit-slug.ts
|
|
5503
5458
|
function unitSlug(id, name) {
|
|
@@ -5514,10 +5469,10 @@ function toSlug(name) {
|
|
|
5514
5469
|
|
|
5515
5470
|
// src/integration/persistence/session-folder-helpers.ts
|
|
5516
5471
|
import { cp, mkdir as mkdir10, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
5517
|
-
import { basename as basename2, dirname as dirname8, join as
|
|
5472
|
+
import { basename as basename2, dirname as dirname8, join as join10 } from "path";
|
|
5518
5473
|
function contextFileFor(provider) {
|
|
5519
5474
|
if (provider === "claude") return { path: "CLAUDE.md", needsGithubDir: false };
|
|
5520
|
-
return { path:
|
|
5475
|
+
return { path: join10(".github", "copilot-instructions.md"), needsGithubDir: true };
|
|
5521
5476
|
}
|
|
5522
5477
|
function renderContextFile(args) {
|
|
5523
5478
|
const { sprint, phase, affectedRepos, copilot } = args;
|
|
@@ -5539,7 +5494,7 @@ function renderInputsLine(phase) {
|
|
|
5539
5494
|
return "- Input: `./ticket.md` \u2014 the seed idea. Write the proposed sprint output to `./output.json`.\n";
|
|
5540
5495
|
if (phase === "plan")
|
|
5541
5496
|
return "- Inputs: per-ticket refined requirements live alongside this folder under `../refinement/<unit>/requirements.json`. Write your generated `tasks.json` to `./tasks.json` in this folder.\n";
|
|
5542
|
-
return "- Inputs are in `./task.md` (the task under review), `./tasks.md`
|
|
5497
|
+
return "- Inputs are in `./task.md` (the task under review), `./tasks.md` (full task plan including any sibling evaluator output), `./requirements/` (per-ticket requirements), `./project-context.md` (target repo context), and `./dimensions.md` (grading rubric).\n";
|
|
5543
5498
|
}
|
|
5544
5499
|
function renderRepoLine(phase, affectedRepos, copilot) {
|
|
5545
5500
|
if (phase === "refine" || phase === "ideate")
|
|
@@ -5649,7 +5604,7 @@ async function mirrorRepo(src, dst) {
|
|
|
5649
5604
|
async function writeContextFile(args) {
|
|
5650
5605
|
const { path, needsGithubDir } = contextFileFor(args.provider);
|
|
5651
5606
|
if (needsGithubDir) {
|
|
5652
|
-
const ensure = await ensureDirSafe(
|
|
5607
|
+
const ensure = await ensureDirSafe(join10(args.root, ".github"));
|
|
5653
5608
|
if (!ensure.ok) return Result.error(ensure.error);
|
|
5654
5609
|
}
|
|
5655
5610
|
const body = renderContextFile({
|
|
@@ -5658,7 +5613,7 @@ async function writeContextFile(args) {
|
|
|
5658
5613
|
affectedRepos: args.affectedRepos,
|
|
5659
5614
|
copilot: args.provider === "copilot"
|
|
5660
5615
|
});
|
|
5661
|
-
return writeFileSafe2(
|
|
5616
|
+
return writeFileSafe2(join10(args.root, path), body);
|
|
5662
5617
|
}
|
|
5663
5618
|
|
|
5664
5619
|
// src/integration/persistence/execution-unit-builder.ts
|
|
@@ -5721,7 +5676,7 @@ function renderTaskInput(task) {
|
|
|
5721
5676
|
}
|
|
5722
5677
|
return lines.join("\n");
|
|
5723
5678
|
}
|
|
5724
|
-
function renderTasksList(tasks) {
|
|
5679
|
+
function renderTasksList(tasks, priorEvaluations) {
|
|
5725
5680
|
const ordered = [...tasks].sort((a, b) => a.order - b.order);
|
|
5726
5681
|
const lines = ["# Task plan", ""];
|
|
5727
5682
|
for (const t of ordered) {
|
|
@@ -5745,6 +5700,10 @@ function renderTasksList(tasks) {
|
|
|
5745
5700
|
for (const c34 of t.verificationCriteria) lines.push(`- ${c34}`);
|
|
5746
5701
|
lines.push("");
|
|
5747
5702
|
}
|
|
5703
|
+
const priorEval = priorEvaluations.get(t.id);
|
|
5704
|
+
if (priorEval !== void 0 && priorEval.length > 0) {
|
|
5705
|
+
lines.push("### Evaluator output", "", "```text", priorEval, "```", "");
|
|
5706
|
+
}
|
|
5748
5707
|
}
|
|
5749
5708
|
return lines.join("\n");
|
|
5750
5709
|
}
|
|
@@ -5775,7 +5734,7 @@ function renderDimensions(task) {
|
|
|
5775
5734
|
return lines.join("\n");
|
|
5776
5735
|
}
|
|
5777
5736
|
async function readProjectContext(repoPath, provider) {
|
|
5778
|
-
const target = provider === "claude" ?
|
|
5737
|
+
const target = provider === "claude" ? join11(repoPath, "CLAUDE.md") : join11(repoPath, ".github", "copilot-instructions.md");
|
|
5779
5738
|
try {
|
|
5780
5739
|
const body = await readFile6(target, "utf-8");
|
|
5781
5740
|
return [`<!-- copied from ${target} -->`, "", body].join("\n");
|
|
@@ -5790,59 +5749,20 @@ async function readProjectContext(repoPath, provider) {
|
|
|
5790
5749
|
].join("\n");
|
|
5791
5750
|
}
|
|
5792
5751
|
}
|
|
5793
|
-
function serialiseTasks(tasks) {
|
|
5794
|
-
return [...tasks].sort((a, b) => a.order - b.order).map((t) => ({
|
|
5795
|
-
id: t.id,
|
|
5796
|
-
name: t.name,
|
|
5797
|
-
description: t.description,
|
|
5798
|
-
steps: t.steps,
|
|
5799
|
-
verificationCriteria: t.verificationCriteria,
|
|
5800
|
-
status: t.status,
|
|
5801
|
-
order: t.order,
|
|
5802
|
-
ticketId: t.ticketId,
|
|
5803
|
-
blockedBy: t.blockedBy,
|
|
5804
|
-
projectPath: t.projectPath,
|
|
5805
|
-
extraDimensions: t.extraDimensions
|
|
5806
|
-
}));
|
|
5807
|
-
}
|
|
5808
5752
|
async function writeExecutionVolatile(args) {
|
|
5809
|
-
const taskMd = await writeFileSafe2(
|
|
5753
|
+
const taskMd = await writeFileSafe2(join11(args.root, "task.md"), renderTaskInput(args.task));
|
|
5810
5754
|
if (!taskMd.ok) return Result.error(taskMd.error);
|
|
5811
|
-
const tasksMd = await writeFileSafe2(
|
|
5755
|
+
const tasksMd = await writeFileSafe2(join11(args.root, "tasks.md"), renderTasksList(args.tasks, args.priorEvaluations));
|
|
5812
5756
|
if (!tasksMd.ok) return Result.error(tasksMd.error);
|
|
5813
|
-
const tasksJson = await writeFileSafe2(
|
|
5814
|
-
join12(args.root, "tasks.json"),
|
|
5815
|
-
JSON.stringify(serialiseTasks(args.tasks), null, 2)
|
|
5816
|
-
);
|
|
5817
|
-
if (!tasksJson.ok) return Result.error(tasksJson.error);
|
|
5818
5757
|
const projectContext = await readProjectContext(args.task.projectPath, args.aiProvider);
|
|
5819
|
-
const projectCtxFile = await writeFileSafe2(
|
|
5758
|
+
const projectCtxFile = await writeFileSafe2(join11(args.root, "project-context.md"), projectContext);
|
|
5820
5759
|
if (!projectCtxFile.ok) return Result.error(projectCtxFile.error);
|
|
5821
|
-
const evaluationsDir = join12(args.root, "evaluations");
|
|
5822
|
-
try {
|
|
5823
|
-
await rm4(evaluationsDir, { recursive: true, force: true });
|
|
5824
|
-
} catch (err) {
|
|
5825
|
-
return Result.error(
|
|
5826
|
-
new StorageError({
|
|
5827
|
-
subCode: "io",
|
|
5828
|
-
message: `failed to clear ${evaluationsDir}: ${err instanceof Error ? err.message : String(err)}`,
|
|
5829
|
-
path: evaluationsDir,
|
|
5830
|
-
cause: err
|
|
5831
|
-
})
|
|
5832
|
-
);
|
|
5833
|
-
}
|
|
5834
|
-
const ensureEvals = await ensureDirSafe(evaluationsDir);
|
|
5835
|
-
if (!ensureEvals.ok) return Result.error(ensureEvals.error);
|
|
5836
|
-
for (const [taskId, body] of args.priorEvaluations) {
|
|
5837
|
-
const w = await writeFileSafe2(join12(evaluationsDir, `${taskId}.md`), body);
|
|
5838
|
-
if (!w.ok) return Result.error(w.error);
|
|
5839
|
-
}
|
|
5840
5760
|
return Result.ok();
|
|
5841
5761
|
}
|
|
5842
5762
|
async function buildExecutionUnit(storage2, input) {
|
|
5843
5763
|
const slug = unitSlug(String(input.task.id), input.task.name);
|
|
5844
5764
|
const root = storage2.executionUnitDir(input.sprint.id, slug);
|
|
5845
|
-
const requirementsDir = AbsolutePath.trustString(
|
|
5765
|
+
const requirementsDir = AbsolutePath.trustString(join11(root, "requirements"));
|
|
5846
5766
|
const ensure = await ensureDirSafe(requirementsDir);
|
|
5847
5767
|
if (!ensure.ok) return Result.error(ensure.error);
|
|
5848
5768
|
const ctx = await writeContextFile({
|
|
@@ -5854,14 +5774,14 @@ async function buildExecutionUnit(storage2, input) {
|
|
|
5854
5774
|
});
|
|
5855
5775
|
if (!ctx.ok) return Result.error(ctx.error);
|
|
5856
5776
|
for (const ticket of input.sprint.tickets) {
|
|
5857
|
-
const filePath =
|
|
5777
|
+
const filePath = join11(requirementsDir, `${ticket.id}.md`);
|
|
5858
5778
|
const w = await writeFileSafe2(filePath, renderRequirementsInput(ticket));
|
|
5859
5779
|
if (!w.ok) return Result.error(w.error);
|
|
5860
5780
|
}
|
|
5861
|
-
const dims = await writeFileSafe2(
|
|
5781
|
+
const dims = await writeFileSafe2(join11(root, "dimensions.md"), renderDimensions(input.task));
|
|
5862
5782
|
if (!dims.ok) return Result.error(dims.error);
|
|
5863
5783
|
const sprintCriteriaPath = String(storage2.doneCriteriaFile(input.sprint.id));
|
|
5864
|
-
const unitCriteriaPath =
|
|
5784
|
+
const unitCriteriaPath = join11(root, "done-criteria.md");
|
|
5865
5785
|
const copied = await copyFileSafe(sprintCriteriaPath, unitCriteriaPath);
|
|
5866
5786
|
if (!copied.ok) {
|
|
5867
5787
|
const msg = `build-execution-unit: done-criteria.md not found for sprint ${String(input.sprint.id)} \u2014 evaluator will grade without per-task criteria reference`;
|
|
@@ -5881,7 +5801,7 @@ async function buildExecutionUnit(storage2, input) {
|
|
|
5881
5801
|
let addDirs;
|
|
5882
5802
|
let sessionCwd;
|
|
5883
5803
|
if (input.aiProvider === "copilot") {
|
|
5884
|
-
const repoMirror =
|
|
5804
|
+
const repoMirror = join11(root, "repo");
|
|
5885
5805
|
const m = await mirrorRepo(input.task.projectPath, repoMirror);
|
|
5886
5806
|
if (!m.ok) return Result.error(m.error);
|
|
5887
5807
|
addDirs = [];
|
|
@@ -5893,8 +5813,7 @@ async function buildExecutionUnit(storage2, input) {
|
|
|
5893
5813
|
return Result.ok({
|
|
5894
5814
|
root,
|
|
5895
5815
|
addDirs,
|
|
5896
|
-
sessionCwd
|
|
5897
|
-
evaluationMdPath: AbsolutePath.trustString(join12(root, "evaluation.md"))
|
|
5816
|
+
sessionCwd
|
|
5898
5817
|
});
|
|
5899
5818
|
}
|
|
5900
5819
|
async function refreshExecutionUnit(storage2, input) {
|
|
@@ -5911,7 +5830,7 @@ async function refreshExecutionUnit(storage2, input) {
|
|
|
5911
5830
|
}
|
|
5912
5831
|
|
|
5913
5832
|
// src/integration/persistence/ideate-unit-builder.ts
|
|
5914
|
-
import { join as
|
|
5833
|
+
import { join as join12 } from "path";
|
|
5915
5834
|
async function buildIdeationUnit(storage2, input) {
|
|
5916
5835
|
const slug = unitSlug(String(input.ticket.id), input.ticket.title);
|
|
5917
5836
|
const root = storage2.ideationUnitDir(input.sprint.id, slug);
|
|
@@ -5925,19 +5844,19 @@ async function buildIdeationUnit(storage2, input) {
|
|
|
5925
5844
|
affectedRepos: []
|
|
5926
5845
|
});
|
|
5927
5846
|
if (!ctx.ok) return Result.error(ctx.error);
|
|
5928
|
-
const ticketMdPath = AbsolutePath.trustString(
|
|
5847
|
+
const ticketMdPath = AbsolutePath.trustString(join12(root, "ticket.md"));
|
|
5929
5848
|
const wrote = await writeFileSafe2(ticketMdPath, renderTicketInput(input.ticket));
|
|
5930
5849
|
if (!wrote.ok) return Result.error(wrote.error);
|
|
5931
5850
|
return Result.ok({
|
|
5932
5851
|
root,
|
|
5933
|
-
sessionMdPath: AbsolutePath.trustString(
|
|
5852
|
+
sessionMdPath: AbsolutePath.trustString(join12(root, "session.md")),
|
|
5934
5853
|
ticketMdPath,
|
|
5935
|
-
outputJsonPath: AbsolutePath.trustString(
|
|
5854
|
+
outputJsonPath: AbsolutePath.trustString(join12(root, "output.json"))
|
|
5936
5855
|
});
|
|
5937
5856
|
}
|
|
5938
5857
|
|
|
5939
5858
|
// src/integration/persistence/planning-folder-builder.ts
|
|
5940
|
-
import { basename as basename3, join as
|
|
5859
|
+
import { basename as basename3, join as join13 } from "path";
|
|
5941
5860
|
|
|
5942
5861
|
// src/business/usecases/sprint/sprint-requirements-aggregate.ts
|
|
5943
5862
|
function buildSprintRequirementsAggregate(sprint, now = /* @__PURE__ */ new Date()) {
|
|
@@ -6013,7 +5932,7 @@ async function buildPlanningFolder(storage2, input) {
|
|
|
6013
5932
|
});
|
|
6014
5933
|
if (!ctx.ok) return Result.error(ctx.error);
|
|
6015
5934
|
const reqSrc = String(storage2.requirementsAggregateFile(input.sprint.id));
|
|
6016
|
-
const reqDst =
|
|
5935
|
+
const reqDst = join13(root, "requirements.json");
|
|
6017
5936
|
const copied = await copyFileSafe(reqSrc, reqDst);
|
|
6018
5937
|
if (!copied.ok) {
|
|
6019
5938
|
const inlineBody = serialiseSprintRequirementsAggregate(buildSprintRequirementsAggregate(input.sprint));
|
|
@@ -6022,11 +5941,11 @@ async function buildPlanningFolder(storage2, input) {
|
|
|
6022
5941
|
}
|
|
6023
5942
|
let addDirs;
|
|
6024
5943
|
if (isCopilot) {
|
|
6025
|
-
const reposDir =
|
|
5944
|
+
const reposDir = join13(root, "repos");
|
|
6026
5945
|
const ensureRepos = await ensureDirSafe(reposDir);
|
|
6027
5946
|
if (!ensureRepos.ok) return Result.error(ensureRepos.error);
|
|
6028
5947
|
for (const repoPath of input.sprint.affectedRepositories) {
|
|
6029
|
-
const dst =
|
|
5948
|
+
const dst = join13(reposDir, basename3(repoPath));
|
|
6030
5949
|
const m = await mirrorRepo(repoPath, dst);
|
|
6031
5950
|
if (!m.ok) return Result.error(m.error);
|
|
6032
5951
|
}
|
|
@@ -6036,14 +5955,14 @@ async function buildPlanningFolder(storage2, input) {
|
|
|
6036
5955
|
}
|
|
6037
5956
|
return Result.ok({
|
|
6038
5957
|
root,
|
|
6039
|
-
sessionMdPath: AbsolutePath.trustString(
|
|
6040
|
-
rawTasksJsonPath: AbsolutePath.trustString(
|
|
5958
|
+
sessionMdPath: AbsolutePath.trustString(join13(root, "session.md")),
|
|
5959
|
+
rawTasksJsonPath: AbsolutePath.trustString(join13(root, "tasks.json")),
|
|
6041
5960
|
addDirs
|
|
6042
5961
|
});
|
|
6043
5962
|
}
|
|
6044
5963
|
|
|
6045
5964
|
// src/integration/persistence/refine-unit-builder.ts
|
|
6046
|
-
import { join as
|
|
5965
|
+
import { join as join14 } from "path";
|
|
6047
5966
|
async function buildRefinementUnit(storage2, input) {
|
|
6048
5967
|
const slug = unitSlug(String(input.ticket.id), input.ticket.title);
|
|
6049
5968
|
const root = storage2.refinementUnitDir(input.sprint.id, slug);
|
|
@@ -6057,14 +5976,14 @@ async function buildRefinementUnit(storage2, input) {
|
|
|
6057
5976
|
affectedRepos: []
|
|
6058
5977
|
});
|
|
6059
5978
|
if (!ctx.ok) return Result.error(ctx.error);
|
|
6060
|
-
const ticketMdPath = AbsolutePath.trustString(
|
|
5979
|
+
const ticketMdPath = AbsolutePath.trustString(join14(root, "ticket.md"));
|
|
6061
5980
|
const wrote = await writeFileSafe2(ticketMdPath, renderTicketInput(input.ticket));
|
|
6062
5981
|
if (!wrote.ok) return Result.error(wrote.error);
|
|
6063
5982
|
return Result.ok({
|
|
6064
5983
|
root,
|
|
6065
|
-
sessionMdPath: AbsolutePath.trustString(
|
|
5984
|
+
sessionMdPath: AbsolutePath.trustString(join14(root, "session.md")),
|
|
6066
5985
|
ticketMdPath,
|
|
6067
|
-
requirementsJsonPath: AbsolutePath.trustString(
|
|
5986
|
+
requirementsJsonPath: AbsolutePath.trustString(join14(root, "requirements.json"))
|
|
6068
5987
|
});
|
|
6069
5988
|
}
|
|
6070
5989
|
|
|
@@ -6196,15 +6115,11 @@ var InMemorySignalBus = class {
|
|
|
6196
6115
|
};
|
|
6197
6116
|
|
|
6198
6117
|
// src/integration/signals/file-system-handler.ts
|
|
6199
|
-
import { appendFile as appendFile2, mkdir as mkdir11
|
|
6200
|
-
import { dirname as dirname9
|
|
6118
|
+
import { appendFile as appendFile2, mkdir as mkdir11 } from "fs/promises";
|
|
6119
|
+
import { dirname as dirname9 } from "path";
|
|
6201
6120
|
function progressPath(paths, sprintId) {
|
|
6202
6121
|
return paths.progressFile(sprintId);
|
|
6203
6122
|
}
|
|
6204
|
-
function evaluationPath(paths, sprintId, taskId, taskName) {
|
|
6205
|
-
const slug = unitSlug(taskId, taskName);
|
|
6206
|
-
return AbsolutePath.trustString(join16(paths.executionUnitDir(sprintId, slug), "evaluation.md"));
|
|
6207
|
-
}
|
|
6208
6123
|
async function appendLine(path, line) {
|
|
6209
6124
|
try {
|
|
6210
6125
|
await mkdir11(dirname9(path), { recursive: true });
|
|
@@ -6222,26 +6137,6 @@ async function appendLine(path, line) {
|
|
|
6222
6137
|
);
|
|
6223
6138
|
}
|
|
6224
6139
|
}
|
|
6225
|
-
async function writeText(path, body) {
|
|
6226
|
-
try {
|
|
6227
|
-
await mkdir11(dirname9(path), { recursive: true });
|
|
6228
|
-
await writeFile7(path, body.endsWith("\n") ? body : `${body}
|
|
6229
|
-
`, {
|
|
6230
|
-
encoding: "utf-8",
|
|
6231
|
-
mode: 384
|
|
6232
|
-
});
|
|
6233
|
-
return Result.ok();
|
|
6234
|
-
} catch (err) {
|
|
6235
|
-
return Result.error(
|
|
6236
|
-
new StorageError({
|
|
6237
|
-
subCode: "io",
|
|
6238
|
-
message: `failed to write ${path}: ${err instanceof Error ? err.message : String(err)}`,
|
|
6239
|
-
path,
|
|
6240
|
-
cause: err
|
|
6241
|
-
})
|
|
6242
|
-
);
|
|
6243
|
-
}
|
|
6244
|
-
}
|
|
6245
6140
|
var FileSystemSignalHandler = class {
|
|
6246
6141
|
constructor(paths, fileLocker = new FileLocker()) {
|
|
6247
6142
|
this.paths = paths;
|
|
@@ -6273,14 +6168,10 @@ var FileSystemSignalHandler = class {
|
|
|
6273
6168
|
return Result.error(
|
|
6274
6169
|
new StorageError({
|
|
6275
6170
|
subCode: "io",
|
|
6276
|
-
message: "evaluation signal requires taskName in meta
|
|
6171
|
+
message: "evaluation signal requires taskName in meta"
|
|
6277
6172
|
})
|
|
6278
6173
|
);
|
|
6279
6174
|
}
|
|
6280
|
-
const file = evaluationPath(this.paths, meta.sprintId, String(meta.taskId), meta.taskName);
|
|
6281
|
-
const body = renderEvaluationBody(signal);
|
|
6282
|
-
const w = await writeText(file, body);
|
|
6283
|
-
if (!w.ok) return w;
|
|
6284
6175
|
const scoreSuffix = signal.overallScore !== void 0 ? `, score ${String(signal.overallScore)}/5` : "";
|
|
6285
6176
|
return this.appendProgress(
|
|
6286
6177
|
meta.sprintId,
|
|
@@ -6319,33 +6210,6 @@ function formatProgress(timestamp, message, files) {
|
|
|
6319
6210
|
const filesSuffix = files !== void 0 && files.length > 0 ? ` (files: ${files.join(", ")})` : "";
|
|
6320
6211
|
return `- ${timestamp} \u2014 ${message}${filesSuffix}`;
|
|
6321
6212
|
}
|
|
6322
|
-
function renderEvaluationBody(signal) {
|
|
6323
|
-
const lines = [];
|
|
6324
|
-
lines.push(`# Evaluation \u2014 ${signal.status}`);
|
|
6325
|
-
lines.push("");
|
|
6326
|
-
lines.push(`Recorded: ${signal.timestamp}`);
|
|
6327
|
-
if (signal.overallScore !== void 0) {
|
|
6328
|
-
lines.push(`Overall score: ${String(signal.overallScore)}/5`);
|
|
6329
|
-
}
|
|
6330
|
-
lines.push("");
|
|
6331
|
-
if (signal.dimensions.length > 0) {
|
|
6332
|
-
lines.push("## Dimensions");
|
|
6333
|
-
lines.push("");
|
|
6334
|
-
for (const d of signal.dimensions) {
|
|
6335
|
-
const verdict = d.passed ? "PASS" : "FAIL";
|
|
6336
|
-
const scoreLabel = d.score !== void 0 ? ` (score ${String(d.score)}/5)` : "";
|
|
6337
|
-
lines.push(`- **${d.dimension}**${scoreLabel}: ${verdict} \u2014 ${d.finding}`);
|
|
6338
|
-
}
|
|
6339
|
-
lines.push("");
|
|
6340
|
-
}
|
|
6341
|
-
if (signal.critique !== void 0 && signal.critique.length > 0) {
|
|
6342
|
-
lines.push("## Critique");
|
|
6343
|
-
lines.push("");
|
|
6344
|
-
lines.push(signal.critique);
|
|
6345
|
-
lines.push("");
|
|
6346
|
-
}
|
|
6347
|
-
return lines.join("\n");
|
|
6348
|
-
}
|
|
6349
6213
|
|
|
6350
6214
|
// src/integration/signals/parser.ts
|
|
6351
6215
|
function buildPatterns() {
|
|
@@ -7355,18 +7219,18 @@ async function isFirstLaunch(deps) {
|
|
|
7355
7219
|
// src/application/runtime/legacy-detector.ts
|
|
7356
7220
|
import { access } from "fs/promises";
|
|
7357
7221
|
import { homedir } from "os";
|
|
7358
|
-
import { join as
|
|
7222
|
+
import { join as join15 } from "path";
|
|
7359
7223
|
var NOT_LEGACY = { isLegacy: false, legacyConfigPath: null, hint: "" };
|
|
7360
7224
|
function defaultRoot() {
|
|
7361
7225
|
const fromEnv = process.env["RALPHCTL_ROOT"];
|
|
7362
7226
|
if (fromEnv !== void 0 && fromEnv.length > 0) {
|
|
7363
7227
|
return AbsolutePath.trustString(fromEnv);
|
|
7364
7228
|
}
|
|
7365
|
-
return AbsolutePath.trustString(
|
|
7229
|
+
return AbsolutePath.trustString(join15(homedir(), ".ralphctl"));
|
|
7366
7230
|
}
|
|
7367
7231
|
async function detectLegacyLayout(deps = {}) {
|
|
7368
7232
|
const root = deps.root ?? defaultRoot();
|
|
7369
|
-
const legacyConfigPath = AbsolutePath.trustString(
|
|
7233
|
+
const legacyConfigPath = AbsolutePath.trustString(join15(root, "config.json"));
|
|
7370
7234
|
try {
|
|
7371
7235
|
await access(legacyConfigPath);
|
|
7372
7236
|
} catch {
|
|
@@ -7387,7 +7251,7 @@ Then re-run ralphctl to start fresh, and use 'ralphctl project add' to register
|
|
|
7387
7251
|
// src/integration/ai/dist-asset-manifest.ts
|
|
7388
7252
|
import { existsSync as existsSync4 } from "fs";
|
|
7389
7253
|
import { readFile as readFile7, stat as stat4 } from "fs/promises";
|
|
7390
|
-
import { dirname as dirname10, join as
|
|
7254
|
+
import { dirname as dirname10, join as join16 } from "path";
|
|
7391
7255
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7392
7256
|
var HERE3 = dirname10(fileURLToPath3(import.meta.url));
|
|
7393
7257
|
var cached2 = { state: "unverified" };
|
|
@@ -7395,7 +7259,7 @@ async function verifyDistAssets(distRootOverride) {
|
|
|
7395
7259
|
if (cached2.state === "pass") return Result.ok();
|
|
7396
7260
|
if (cached2.state === "fail") return Result.error(cached2.error);
|
|
7397
7261
|
const distRoot = distRootOverride ?? HERE3;
|
|
7398
|
-
const manifestPath =
|
|
7262
|
+
const manifestPath = join16(distRoot, "manifest.json");
|
|
7399
7263
|
if (!existsSync4(manifestPath)) {
|
|
7400
7264
|
cached2 = { state: "pass" };
|
|
7401
7265
|
return Result.ok();
|
|
@@ -7407,7 +7271,7 @@ async function verifyDistAssets(distRootOverride) {
|
|
|
7407
7271
|
}
|
|
7408
7272
|
const missing = [];
|
|
7409
7273
|
for (const rel of parsed.value.assets) {
|
|
7410
|
-
const abs =
|
|
7274
|
+
const abs = join16(distRoot, rel);
|
|
7411
7275
|
try {
|
|
7412
7276
|
const s = await stat4(abs);
|
|
7413
7277
|
if (!s.isFile()) missing.push(rel);
|
|
@@ -9681,7 +9545,22 @@ function unlinkSkillsLeaf(deps, opts = {}) {
|
|
|
9681
9545
|
}
|
|
9682
9546
|
|
|
9683
9547
|
// src/application/chains/execute/per-task-flow.ts
|
|
9684
|
-
import {
|
|
9548
|
+
import { join as join20 } from "path";
|
|
9549
|
+
|
|
9550
|
+
// src/kernel/algorithms/execution-round-paths.ts
|
|
9551
|
+
import { join as join17 } from "path";
|
|
9552
|
+
function roundDir(unitRoot, round) {
|
|
9553
|
+
return join17(unitRoot, "rounds", String(round));
|
|
9554
|
+
}
|
|
9555
|
+
function generatorRoundDir(unitRoot, round) {
|
|
9556
|
+
return join17(roundDir(unitRoot, round), "generator");
|
|
9557
|
+
}
|
|
9558
|
+
function evaluatorRoundDir(unitRoot, round) {
|
|
9559
|
+
return join17(roundDir(unitRoot, round), "evaluator");
|
|
9560
|
+
}
|
|
9561
|
+
function evaluatorVerdictSprintRelative(slug, round) {
|
|
9562
|
+
return join17("execution", slug, "rounds", String(round), "evaluator", "evaluation.md");
|
|
9563
|
+
}
|
|
9685
9564
|
|
|
9686
9565
|
// src/integration/persistence/done-criteria-reader.ts
|
|
9687
9566
|
import { readFile as readFile8 } from "fs/promises";
|
|
@@ -9735,7 +9614,7 @@ var BranchPreflightUseCase = class {
|
|
|
9735
9614
|
};
|
|
9736
9615
|
|
|
9737
9616
|
// src/business/usecases/evaluate/evaluate-and-fix-loop.ts
|
|
9738
|
-
import { join as
|
|
9617
|
+
import { join as join18 } from "path";
|
|
9739
9618
|
|
|
9740
9619
|
// src/business/usecases/_shared/add-dir-args.ts
|
|
9741
9620
|
function buildAdditionalCwdArgs(paths) {
|
|
@@ -9756,6 +9635,24 @@ function renderFileHandoffWrapper(promptFilePath) {
|
|
|
9756
9635
|
"Follow the protocol in that file exactly."
|
|
9757
9636
|
].join("\n");
|
|
9758
9637
|
}
|
|
9638
|
+
function renderFixHandoffWrapper(promptFilePath, critique) {
|
|
9639
|
+
const safeCritique = critique.replace(/<\/evaluator-critique>/g, "<\\/evaluator-critique>");
|
|
9640
|
+
return [
|
|
9641
|
+
"You are an agent under the ralphctl harness \u2014 resuming on a fix round.",
|
|
9642
|
+
"",
|
|
9643
|
+
"The evaluator from the previous round flagged the work as not yet complete.",
|
|
9644
|
+
"Its critique follows verbatim \u2014 read it FIRST, before doing anything else:",
|
|
9645
|
+
"",
|
|
9646
|
+
"<evaluator-critique>",
|
|
9647
|
+
safeCritique,
|
|
9648
|
+
"</evaluator-critique>",
|
|
9649
|
+
"",
|
|
9650
|
+
`Then re-read the task spec at \`${promptFilePath}\` to refresh the success criteria.`,
|
|
9651
|
+
"",
|
|
9652
|
+
"Address every dimension flagged failed. Do not regress the dimensions that already passed.",
|
|
9653
|
+
"When the work is complete, emit `<task-complete>` per the spec."
|
|
9654
|
+
].join("\n");
|
|
9655
|
+
}
|
|
9759
9656
|
|
|
9760
9657
|
// src/business/usecases/evaluate/evaluate-task.ts
|
|
9761
9658
|
var MAX_MALFORMED_CRITIQUE_CHARS = 500;
|
|
@@ -9895,9 +9792,6 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9895
9792
|
history: []
|
|
9896
9793
|
});
|
|
9897
9794
|
}
|
|
9898
|
-
const evaluatorPromptPath = AbsolutePath.trustString(
|
|
9899
|
-
input.evaluateWorkspaceDir !== void 0 ? join19(input.evaluateWorkspaceDir, "evaluator-prompt.md") : join19(String(input.contextsDir), `evaluate-${String(input.task.id)}.md`)
|
|
9900
|
-
);
|
|
9901
9795
|
const history = [];
|
|
9902
9796
|
let previousSignal;
|
|
9903
9797
|
let previousCritique;
|
|
@@ -9921,18 +9815,21 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9921
9815
|
});
|
|
9922
9816
|
}
|
|
9923
9817
|
}
|
|
9818
|
+
const evaluatorPromptPath = AbsolutePath.trustString(
|
|
9819
|
+
join18(evaluatorRoundDir(input.evaluateWorkspaceDir, round), "prompt.md")
|
|
9820
|
+
);
|
|
9924
9821
|
const evalPromptResult = await this.prompts.buildEvaluatePrompt({
|
|
9925
9822
|
task: input.task,
|
|
9926
9823
|
sprint: input.sprint,
|
|
9824
|
+
evaluateWorkspaceDir: input.evaluateWorkspaceDir,
|
|
9927
9825
|
...previousCritique !== void 0 ? { previousCritique } : {},
|
|
9928
|
-
...input.evaluateWorkspaceDir !== void 0 ? { evaluateWorkspaceDir: input.evaluateWorkspaceDir } : {},
|
|
9929
9826
|
...input.doneCriteriaBullet !== void 0 ? { doneCriteriaBullet: input.doneCriteriaBullet } : {}
|
|
9930
9827
|
});
|
|
9931
9828
|
if (!evalPromptResult.ok) return Result.error(evalPromptResult.error);
|
|
9932
9829
|
const written = await this.writeContextFile.write(evaluatorPromptPath, evalPromptResult.value);
|
|
9933
9830
|
if (!written.ok) return Result.error(written.error);
|
|
9934
9831
|
const evaluatorCwd = input.evaluateSessionCwd ?? input.cwd;
|
|
9935
|
-
const evaluatorSessionMdPath = input.nextSessionMdPath ? await input.nextSessionMdPath("evaluator") : void 0;
|
|
9832
|
+
const evaluatorSessionMdPath = input.nextSessionMdPath ? await input.nextSessionMdPath("evaluator", round) : void 0;
|
|
9936
9833
|
const evalResult = await this.evaluator.execute({
|
|
9937
9834
|
task: input.task,
|
|
9938
9835
|
sprint: input.sprint,
|
|
@@ -9945,6 +9842,16 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9945
9842
|
if (!evalResult.ok) return Result.error(evalResult.error);
|
|
9946
9843
|
const { outcome, signal, fullCritique } = evalResult.value;
|
|
9947
9844
|
history.push({ round, outcome, signal, critique: fullCritique });
|
|
9845
|
+
const verdictPath = AbsolutePath.trustString(
|
|
9846
|
+
join18(evaluatorRoundDir(input.evaluateWorkspaceDir, round), "evaluation.md")
|
|
9847
|
+
);
|
|
9848
|
+
const verdictWritten = await this.writeContextFile.write(verdictPath, fullCritique);
|
|
9849
|
+
if (!verdictWritten.ok) {
|
|
9850
|
+
log.warn("failed to persist per-round evaluator verdict", {
|
|
9851
|
+
round,
|
|
9852
|
+
error: verdictWritten.error.message
|
|
9853
|
+
});
|
|
9854
|
+
}
|
|
9948
9855
|
log.info(`evaluator round complete for task ${String(input.task.id)}`, { round, outcome });
|
|
9949
9856
|
if (outcome === "passed") break;
|
|
9950
9857
|
if (outcome === "malformed") {
|
|
@@ -9970,7 +9877,8 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9970
9877
|
break;
|
|
9971
9878
|
}
|
|
9972
9879
|
log.info("resuming generator with critique", { round });
|
|
9973
|
-
const
|
|
9880
|
+
const fixRound = round + 1;
|
|
9881
|
+
const generatorSessionMdPath = input.nextSessionMdPath ? await input.nextSessionMdPath("generator", fixRound) : void 0;
|
|
9974
9882
|
const fixResult = await this.generator.execute({
|
|
9975
9883
|
task: input.task,
|
|
9976
9884
|
sprint: input.sprint,
|
|
@@ -9978,6 +9886,7 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9978
9886
|
promptFilePath: input.executePromptFilePath,
|
|
9979
9887
|
...resumeSessionId !== void 0 ? { resumeSessionId } : {},
|
|
9980
9888
|
...generatorSessionMdPath !== void 0 ? { sessionMdPath: generatorSessionMdPath } : {},
|
|
9889
|
+
fixContext: { critique: fullCritique },
|
|
9981
9890
|
...input.abortSignal !== void 0 ? { abortSignal: input.abortSignal } : {}
|
|
9982
9891
|
});
|
|
9983
9892
|
if (!fixResult.ok) return Result.error(fixResult.error);
|
|
@@ -9989,8 +9898,8 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9989
9898
|
projectPath: input.cwd,
|
|
9990
9899
|
checkScript: input.checkScript
|
|
9991
9900
|
});
|
|
9992
|
-
if (!checkResult.ok)
|
|
9993
|
-
|
|
9901
|
+
if (!checkResult.ok) {
|
|
9902
|
+
if (checkResult.error.code !== "check-failed") return Result.error(checkResult.error);
|
|
9994
9903
|
log.warn("post-task check failed after fix attempt \u2014 re-evaluating anyway", { round });
|
|
9995
9904
|
}
|
|
9996
9905
|
}
|
|
@@ -10037,7 +9946,7 @@ var ExecuteSingleTaskUseCase = class {
|
|
|
10037
9946
|
taskId: input.task.id,
|
|
10038
9947
|
projectPath: input.cwd
|
|
10039
9948
|
});
|
|
10040
|
-
const wrapper = renderFileHandoffWrapper(input.promptFilePath);
|
|
9949
|
+
const wrapper = input.fixContext !== void 0 ? renderFixHandoffWrapper(input.promptFilePath, input.fixContext.critique) : renderFileHandoffWrapper(input.promptFilePath);
|
|
10041
9950
|
log.info(`executing task ${String(input.task.id)}${formatNameSuffix2(input.task.name)}`);
|
|
10042
9951
|
const sessionOptions = {
|
|
10043
9952
|
cwd: input.cwd,
|
|
@@ -10107,6 +10016,22 @@ function formatNameSuffix2(name) {
|
|
|
10107
10016
|
return ` \u2014 "${slice}"`;
|
|
10108
10017
|
}
|
|
10109
10018
|
|
|
10019
|
+
// src/domain/errors/check-failed-error.ts
|
|
10020
|
+
var CheckFailedError = class extends Error {
|
|
10021
|
+
/** Discriminator. `as const` keeps it narrow at the type level. */
|
|
10022
|
+
code = "check-failed";
|
|
10023
|
+
/** Captured output of the failing script — surfaced to logs / progress. */
|
|
10024
|
+
output;
|
|
10025
|
+
constructor(opts) {
|
|
10026
|
+
super(opts.message ?? "post-task check script failed");
|
|
10027
|
+
this.name = "CheckFailedError";
|
|
10028
|
+
this.output = opts.output;
|
|
10029
|
+
if (opts.cause !== void 0) {
|
|
10030
|
+
this.cause = opts.cause;
|
|
10031
|
+
}
|
|
10032
|
+
}
|
|
10033
|
+
};
|
|
10034
|
+
|
|
10110
10035
|
// src/business/usecases/execute/post-task-check.ts
|
|
10111
10036
|
var PostTaskCheckUseCase = class {
|
|
10112
10037
|
constructor(external, logger) {
|
|
@@ -10129,10 +10054,11 @@ var PostTaskCheckUseCase = class {
|
|
|
10129
10054
|
input.timeoutMs
|
|
10130
10055
|
);
|
|
10131
10056
|
if (!result.passed) {
|
|
10132
|
-
log.warn("post-task check failed");
|
|
10057
|
+
log.warn("post-task check failed", { output: result.output });
|
|
10058
|
+
return Result.error(new CheckFailedError({ output: result.output }));
|
|
10133
10059
|
}
|
|
10134
10060
|
return Result.ok({
|
|
10135
|
-
passed:
|
|
10061
|
+
passed: true,
|
|
10136
10062
|
output: result.output,
|
|
10137
10063
|
skipped: false
|
|
10138
10064
|
});
|
|
@@ -10290,8 +10216,7 @@ function buildExecutionUnitLeaf(deps, opts = {}) {
|
|
|
10290
10216
|
...ctx,
|
|
10291
10217
|
executionUnitRoot: out.root,
|
|
10292
10218
|
executionAddDirs: out.addDirs,
|
|
10293
|
-
executionSessionCwd: out.sessionCwd
|
|
10294
|
-
executionEvaluationMdPath: out.evaluationMdPath
|
|
10219
|
+
executionSessionCwd: out.sessionCwd
|
|
10295
10220
|
})
|
|
10296
10221
|
});
|
|
10297
10222
|
}
|
|
@@ -10305,8 +10230,21 @@ function collectPriorEvaluations(tasks) {
|
|
|
10305
10230
|
return map;
|
|
10306
10231
|
}
|
|
10307
10232
|
|
|
10233
|
+
// src/application/chains/leaves/noop-leaf.ts
|
|
10234
|
+
function noopLeaf(name) {
|
|
10235
|
+
return new Leaf(name, {
|
|
10236
|
+
useCase: {
|
|
10237
|
+
execute(input) {
|
|
10238
|
+
return Promise.resolve(Result.ok(input));
|
|
10239
|
+
}
|
|
10240
|
+
},
|
|
10241
|
+
input: (ctx) => ctx,
|
|
10242
|
+
output: (_, ctx) => ctx
|
|
10243
|
+
});
|
|
10244
|
+
}
|
|
10245
|
+
|
|
10308
10246
|
// src/application/chains/leaves/render-prompt-to-file.ts
|
|
10309
|
-
import { join as
|
|
10247
|
+
import { join as join19 } from "path";
|
|
10310
10248
|
function renderPromptToFileLeaf(deps, opts) {
|
|
10311
10249
|
return new Leaf("render-prompt-to-file", {
|
|
10312
10250
|
useCase: {
|
|
@@ -10336,7 +10274,7 @@ function defaultPromptPath(ctx, opts) {
|
|
|
10336
10274
|
const sprintDir = storagePaths.sprintDir(ctx.sprintId);
|
|
10337
10275
|
const id = opts.identifier(ctx);
|
|
10338
10276
|
const basename4 = id.length > 0 ? `${opts.flowName}-${id}.md` : `${opts.flowName}.md`;
|
|
10339
|
-
return AbsolutePath.trustString(
|
|
10277
|
+
return AbsolutePath.trustString(join19(sprintDir, "contexts", basename4));
|
|
10340
10278
|
}
|
|
10341
10279
|
|
|
10342
10280
|
// src/application/chains/execute/per-task-flow.ts
|
|
@@ -10383,18 +10321,30 @@ function createPerTaskFlow(deps, opts) {
|
|
|
10383
10321
|
catchIf: (err) => err.code === "aborted",
|
|
10384
10322
|
fallback: markCancelledFallbackLeaf(deps)
|
|
10385
10323
|
});
|
|
10386
|
-
const buildEvalWorkspaceStep =
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
new Sequential("evaluate", [buildEvalWorkspaceStep, evaluateLoopLeaf(deps, evaluateLoop)]),
|
|
10324
|
+
const buildEvalWorkspaceStep = new OnError(
|
|
10325
|
+
buildExecutionUnitLeaf({
|
|
10326
|
+
sessionFolderBuilder: deps.sessionFolderBuilder,
|
|
10327
|
+
aiSession: deps.aiSession
|
|
10328
|
+
}),
|
|
10392
10329
|
{
|
|
10393
10330
|
catchIf: (err) => err.code !== "aborted",
|
|
10394
|
-
fallback: noopLeaf("
|
|
10331
|
+
fallback: noopLeaf("build-execution-unit-noop")
|
|
10332
|
+
}
|
|
10333
|
+
);
|
|
10334
|
+
const evaluateLoopStep = new OnError(evaluateLoopLeaf(deps, evaluateLoop), {
|
|
10335
|
+
catchIf: (err) => err.code !== "aborted",
|
|
10336
|
+
fallback: noopLeaf("evaluate-task-noop")
|
|
10337
|
+
});
|
|
10338
|
+
const postTaskStep = new OnError(
|
|
10339
|
+
new OnError(postTaskCheckLeaf(postCheck), {
|
|
10340
|
+
catchIf: (err) => err.code === "check-failed",
|
|
10341
|
+
fallback: postTaskCheckBlockedFallbackLeaf(deps)
|
|
10342
|
+
}),
|
|
10343
|
+
{
|
|
10344
|
+
catchIf: (err) => err.code !== "aborted" && err.code !== "check-failed",
|
|
10345
|
+
fallback: noopLeaf("post-task-check-noop")
|
|
10395
10346
|
}
|
|
10396
10347
|
);
|
|
10397
|
-
const postTaskStep = postTaskCheckLeaf(postCheck);
|
|
10398
10348
|
const renderPromptStep = renderPromptToFileLeaf(
|
|
10399
10349
|
{ writeContextFile: deps.writeContextFile },
|
|
10400
10350
|
{
|
|
@@ -10403,7 +10353,7 @@ function createPerTaskFlow(deps, opts) {
|
|
|
10403
10353
|
path: (ctx) => {
|
|
10404
10354
|
const slug = unitSlug(String(ctx.task.id), ctx.task.name);
|
|
10405
10355
|
const root = resolveStoragePaths().executionUnitDir(ctx.sprintId, slug);
|
|
10406
|
-
return AbsolutePath.trustString(
|
|
10356
|
+
return AbsolutePath.trustString(join20(String(root), "prompt.md"));
|
|
10407
10357
|
},
|
|
10408
10358
|
buildPrompt: (ctx) => deps.prompts.buildExecutePrompt({
|
|
10409
10359
|
task: ctx.task,
|
|
@@ -10416,9 +10366,10 @@ function createPerTaskFlow(deps, opts) {
|
|
|
10416
10366
|
branchPreflightStep,
|
|
10417
10367
|
markInProgressLeaf(deps),
|
|
10418
10368
|
renderPromptStep,
|
|
10369
|
+
buildEvalWorkspaceStep,
|
|
10419
10370
|
executeTaskStep,
|
|
10420
10371
|
postTaskStep,
|
|
10421
|
-
|
|
10372
|
+
evaluateLoopStep,
|
|
10422
10373
|
commitTaskLeaf(deps),
|
|
10423
10374
|
markDoneLeaf(deps)
|
|
10424
10375
|
]);
|
|
@@ -10461,6 +10412,22 @@ function markBlockedFallbackLeaf(deps) {
|
|
|
10461
10412
|
output: (ctx, task) => ({ ...ctx, task, taskBlocked: true })
|
|
10462
10413
|
});
|
|
10463
10414
|
}
|
|
10415
|
+
function postTaskCheckBlockedFallbackLeaf(deps) {
|
|
10416
|
+
return new Leaf("mark-blocked-check", {
|
|
10417
|
+
useCase: {
|
|
10418
|
+
async execute(input) {
|
|
10419
|
+
const transitioned = input.task.markBlocked("post-task check failed");
|
|
10420
|
+
if (!transitioned.ok) return Result.error(transitioned.error);
|
|
10421
|
+
const saved = await deps.taskRepo.update(input.sprintId, transitioned.value);
|
|
10422
|
+
if (!saved.ok) return Result.error(saved.error);
|
|
10423
|
+
deps.signalBus.emit({ type: "task-finished", taskId: transitioned.value.id, status: "blocked" });
|
|
10424
|
+
return Result.ok(transitioned.value);
|
|
10425
|
+
}
|
|
10426
|
+
},
|
|
10427
|
+
input: (ctx) => ({ sprintId: ctx.sprintId, task: ctx.task }),
|
|
10428
|
+
output: (ctx, task) => ({ ...ctx, task, taskBlocked: true })
|
|
10429
|
+
});
|
|
10430
|
+
}
|
|
10464
10431
|
function markCancelledFallbackLeaf(deps) {
|
|
10465
10432
|
return new Leaf("mark-cancelled", {
|
|
10466
10433
|
useCase: {
|
|
@@ -10496,6 +10463,7 @@ function markInProgressLeaf(deps) {
|
|
|
10496
10463
|
});
|
|
10497
10464
|
}
|
|
10498
10465
|
function executeTaskLeaf(useCase) {
|
|
10466
|
+
let attempt = 0;
|
|
10499
10467
|
return new Leaf("execute-task", {
|
|
10500
10468
|
useCase: {
|
|
10501
10469
|
async execute(input) {
|
|
@@ -10508,7 +10476,9 @@ function executeTaskLeaf(useCase) {
|
|
|
10508
10476
|
message: "execute-task: promptFilePath is missing \u2014 render-prompt-to-file must run first"
|
|
10509
10477
|
});
|
|
10510
10478
|
}
|
|
10511
|
-
|
|
10479
|
+
attempt += 1;
|
|
10480
|
+
const filename = attempt === 1 ? "session.md" : `session-attempt-${String(attempt)}.md`;
|
|
10481
|
+
const sessionMdPath = input.executionUnitRoot !== void 0 ? AbsolutePath.trustString(join20(generatorRoundDir(String(input.executionUnitRoot), 1), filename)) : void 0;
|
|
10512
10482
|
const result = await useCase.execute({
|
|
10513
10483
|
sprint: input.sprint,
|
|
10514
10484
|
task: input.task,
|
|
@@ -10616,19 +10586,21 @@ function evaluateLoopLeaf(deps, loop) {
|
|
|
10616
10586
|
if (input.taskBlocked) {
|
|
10617
10587
|
return Result.ok({ task: input.task });
|
|
10618
10588
|
}
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
) : "";
|
|
10589
|
+
if (input.executionUnitRoot === void 0) {
|
|
10590
|
+
return Result.ok({ task: input.task });
|
|
10591
|
+
}
|
|
10623
10592
|
if (input.promptFilePath === void 0) {
|
|
10624
10593
|
return Result.error({
|
|
10625
10594
|
code: "invalid-state",
|
|
10626
10595
|
message: "evaluate-task: promptFilePath is missing \u2014 render-prompt-to-file must run first"
|
|
10627
10596
|
});
|
|
10628
10597
|
}
|
|
10629
|
-
const
|
|
10630
|
-
const
|
|
10631
|
-
|
|
10598
|
+
const unitRoot = input.executionUnitRoot;
|
|
10599
|
+
const doneCriteriaBullet = await readDoneCriteriaBullet(
|
|
10600
|
+
join20(String(unitRoot), "done-criteria.md"),
|
|
10601
|
+
String(input.task.id)
|
|
10602
|
+
);
|
|
10603
|
+
const refreshWorkspace = async () => {
|
|
10632
10604
|
await deps.aiSession.ensureReady();
|
|
10633
10605
|
const aiProvider = deps.aiSession.getProviderName();
|
|
10634
10606
|
const priorEvaluations = collectPriorEvaluations2(input.tasks);
|
|
@@ -10639,30 +10611,34 @@ function evaluateLoopLeaf(deps, loop) {
|
|
|
10639
10611
|
aiProvider,
|
|
10640
10612
|
priorEvaluations
|
|
10641
10613
|
});
|
|
10642
|
-
}
|
|
10643
|
-
const
|
|
10644
|
-
|
|
10614
|
+
};
|
|
10615
|
+
const nextSessionMdPath = (kind, round) => {
|
|
10616
|
+
const dir = kind === "generator" ? generatorRoundDir : evaluatorRoundDir;
|
|
10617
|
+
return Promise.resolve(AbsolutePath.trustString(join20(dir(String(unitRoot), round), "session.md")));
|
|
10618
|
+
};
|
|
10645
10619
|
const result = await loop.execute({
|
|
10646
10620
|
task: input.task,
|
|
10647
10621
|
sprint: input.sprint,
|
|
10648
10622
|
cwd: input.cwd,
|
|
10649
10623
|
executePromptFilePath: String(input.promptFilePath),
|
|
10650
|
-
|
|
10624
|
+
evaluateWorkspaceDir: String(unitRoot),
|
|
10625
|
+
refreshWorkspace,
|
|
10626
|
+
nextSessionMdPath,
|
|
10651
10627
|
...input.checkScript !== void 0 ? { checkScript: input.checkScript } : {},
|
|
10652
10628
|
...input.resumeSessionId !== void 0 ? { resumeSessionId: input.resumeSessionId } : {},
|
|
10653
10629
|
...input.executionAddDirs !== void 0 ? { addDirs: input.executionAddDirs } : {},
|
|
10654
10630
|
...input.executionSessionCwd !== void 0 ? { evaluateSessionCwd: input.executionSessionCwd } : {},
|
|
10655
|
-
...input.executionUnitRoot !== void 0 ? { evaluateWorkspaceDir: String(input.executionUnitRoot) } : {},
|
|
10656
|
-
...refreshWorkspace !== void 0 ? { refreshWorkspace } : {},
|
|
10657
|
-
...nextSessionMdPath !== void 0 ? { nextSessionMdPath } : {},
|
|
10658
10631
|
...doneCriteriaBullet.length > 0 ? { doneCriteriaBullet } : {}
|
|
10659
10632
|
});
|
|
10660
10633
|
if (!result.ok) return Result.error(result.error);
|
|
10661
10634
|
if (result.value.rounds > 0 && result.value.finalSignal !== null) {
|
|
10635
|
+
const finalRound = result.value.rounds;
|
|
10636
|
+
const slug = unitSlug(String(input.task.id), input.task.name);
|
|
10637
|
+
const file = evaluatorVerdictSprintRelative(slug, finalRound);
|
|
10662
10638
|
const recorded = input.task.recordEvaluation({
|
|
10663
10639
|
status: result.value.finalSignal.status,
|
|
10664
10640
|
output: result.value.finalCritique.slice(0, MAX_PREVIEW_CHARS),
|
|
10665
|
-
file
|
|
10641
|
+
file
|
|
10666
10642
|
});
|
|
10667
10643
|
const saved = await deps.taskRepo.update(input.sprintId, recorded);
|
|
10668
10644
|
if (!saved.ok) return Result.error(saved.error);
|
|
@@ -10683,8 +10659,7 @@ function evaluateLoopLeaf(deps, loop) {
|
|
|
10683
10659
|
taskBlocked: ctx.taskBlocked === true,
|
|
10684
10660
|
...ctx.executionUnitRoot !== void 0 ? { executionUnitRoot: ctx.executionUnitRoot } : {},
|
|
10685
10661
|
...ctx.executionAddDirs !== void 0 ? { executionAddDirs: ctx.executionAddDirs } : {},
|
|
10686
|
-
...ctx.executionSessionCwd !== void 0 ? { executionSessionCwd: ctx.executionSessionCwd } : {}
|
|
10687
|
-
...ctx.executionEvaluationMdPath !== void 0 ? { executionEvaluationMdPath: ctx.executionEvaluationMdPath } : {}
|
|
10662
|
+
...ctx.executionSessionCwd !== void 0 ? { executionSessionCwd: ctx.executionSessionCwd } : {}
|
|
10688
10663
|
}),
|
|
10689
10664
|
// Push the recorded task back onto the context so the downstream
|
|
10690
10665
|
// `mark-done` leaf carries the evaluation forward when it flips
|
|
@@ -10749,17 +10724,6 @@ function commitTaskLeaf(deps) {
|
|
|
10749
10724
|
output: (ctx, task) => ({ ...ctx, task })
|
|
10750
10725
|
});
|
|
10751
10726
|
}
|
|
10752
|
-
function noopLeaf(name) {
|
|
10753
|
-
return new Leaf(name, {
|
|
10754
|
-
useCase: {
|
|
10755
|
-
execute(input) {
|
|
10756
|
-
return Promise.resolve(Result.ok(input));
|
|
10757
|
-
}
|
|
10758
|
-
},
|
|
10759
|
-
input: (ctx) => ctx,
|
|
10760
|
-
output: (_, ctx) => ctx
|
|
10761
|
-
});
|
|
10762
|
-
}
|
|
10763
10727
|
|
|
10764
10728
|
// src/application/chains/execute/execute-flow.ts
|
|
10765
10729
|
function createExecuteFlow(deps, opts) {
|
|
@@ -10782,7 +10746,8 @@ function createExecuteFlow(deps, opts) {
|
|
|
10782
10746
|
const initializeStep = new Sequential("initialize", [
|
|
10783
10747
|
resolveBranchLeaf(deps),
|
|
10784
10748
|
dirtyTreePreflightLeaf(deps, opts),
|
|
10785
|
-
|
|
10749
|
+
resolveCheckScriptsLeaf(deps),
|
|
10750
|
+
setupScriptsSprintStartLeaf(deps)
|
|
10786
10751
|
]);
|
|
10787
10752
|
return new Sequential("execute", [
|
|
10788
10753
|
loadSprintLeaf({ sprintRepo: deps.sprintRepo }),
|
|
@@ -10972,14 +10937,16 @@ function bridgePerTaskChain(task, inner, opts, taskRepo) {
|
|
|
10972
10937
|
async execute(input) {
|
|
10973
10938
|
const fresh = await taskRepo.findBySprintId(input.sprintId);
|
|
10974
10939
|
const liveTasks = fresh.ok ? fresh.value : input.tasks ?? opts.tasks;
|
|
10940
|
+
const liveSprint = input.sprint ?? opts.sprint;
|
|
10941
|
+
const resolvedCheckScript = input.checkScript ?? input.checkScripts?.get(task.projectPath);
|
|
10975
10942
|
const innerCtx = {
|
|
10976
10943
|
sprintId: input.sprintId,
|
|
10977
|
-
sprint:
|
|
10944
|
+
sprint: liveSprint,
|
|
10978
10945
|
task,
|
|
10979
10946
|
tasks: liveTasks,
|
|
10980
10947
|
cwd: task.projectPath,
|
|
10981
10948
|
expectedBranch: input.expectedBranch,
|
|
10982
|
-
...
|
|
10949
|
+
...resolvedCheckScript !== void 0 ? { checkScript: resolvedCheckScript } : {},
|
|
10983
10950
|
...input.noCommit === true ? { noCommit: true } : {}
|
|
10984
10951
|
};
|
|
10985
10952
|
const innerResult = await inner.execute(innerCtx);
|
|
@@ -11081,33 +11048,118 @@ function assertTasksAcyclicLeaf(sortResult) {
|
|
|
11081
11048
|
output: (ctx) => ctx
|
|
11082
11049
|
});
|
|
11083
11050
|
}
|
|
11084
|
-
function
|
|
11051
|
+
function resolveCheckScriptsLeaf(deps) {
|
|
11052
|
+
return new Leaf("resolve-check-scripts", {
|
|
11053
|
+
useCase: {
|
|
11054
|
+
async execute(input) {
|
|
11055
|
+
const map = /* @__PURE__ */ new Map();
|
|
11056
|
+
const repoPaths = input.sprint.affectedRepositories;
|
|
11057
|
+
if (repoPaths.length === 0) {
|
|
11058
|
+
return Result.ok(map);
|
|
11059
|
+
}
|
|
11060
|
+
const project = await deps.projectRepo.findByName(input.sprint.projectName);
|
|
11061
|
+
if (!project.ok) {
|
|
11062
|
+
deps.logger.warn(
|
|
11063
|
+
`resolve-check-scripts: project ${String(input.sprint.projectName)} not found \u2014 per-task gate will skip`,
|
|
11064
|
+
{ error: project.error.message }
|
|
11065
|
+
);
|
|
11066
|
+
return Result.ok(map);
|
|
11067
|
+
}
|
|
11068
|
+
for (const repoPath of repoPaths) {
|
|
11069
|
+
const repo = project.value.repositories.find((r) => r.path === repoPath);
|
|
11070
|
+
if (repo === void 0) continue;
|
|
11071
|
+
const script = repo.checkScript;
|
|
11072
|
+
if (script === void 0 || script.length === 0) continue;
|
|
11073
|
+
map.set(repoPath, script);
|
|
11074
|
+
}
|
|
11075
|
+
return Result.ok(map);
|
|
11076
|
+
}
|
|
11077
|
+
},
|
|
11078
|
+
input: (ctx) => {
|
|
11079
|
+
if (!ctx.sprint) throw new Error("resolve-check-scripts: ctx.sprint must be loaded first");
|
|
11080
|
+
return { sprint: ctx.sprint };
|
|
11081
|
+
},
|
|
11082
|
+
output: (ctx, checkScripts) => ({ ...ctx, checkScripts })
|
|
11083
|
+
});
|
|
11084
|
+
}
|
|
11085
|
+
function setupScriptsSprintStartLeaf(deps) {
|
|
11085
11086
|
return new Leaf(
|
|
11086
|
-
"
|
|
11087
|
+
"setup-scripts-sprint-start",
|
|
11087
11088
|
{
|
|
11088
11089
|
useCase: {
|
|
11089
11090
|
async execute(input) {
|
|
11090
|
-
|
|
11091
|
-
|
|
11091
|
+
const repoPaths = input.sprint.affectedRepositories;
|
|
11092
|
+
if (repoPaths.length === 0) {
|
|
11093
|
+
return Result.ok(void 0);
|
|
11092
11094
|
}
|
|
11093
|
-
const
|
|
11094
|
-
if (!
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
|
|
11098
|
-
currentState: "check-failed",
|
|
11099
|
-
attemptedAction: "execute",
|
|
11100
|
-
message: "sprint-start check script failed"
|
|
11101
|
-
})
|
|
11095
|
+
const project = await deps.projectRepo.findByName(input.sprint.projectName);
|
|
11096
|
+
if (!project.ok) {
|
|
11097
|
+
deps.logger.warn(
|
|
11098
|
+
`setup-scripts-sprint-start: project ${String(input.sprint.projectName)} not found \u2014 skipping setup`,
|
|
11099
|
+
{ error: project.error.message }
|
|
11102
11100
|
);
|
|
11101
|
+
return Result.ok(void 0);
|
|
11102
|
+
}
|
|
11103
|
+
let sprint = input.sprint;
|
|
11104
|
+
const now = IsoTimestamp.trustString((/* @__PURE__ */ new Date()).toISOString());
|
|
11105
|
+
for (const repoPath of repoPaths) {
|
|
11106
|
+
if (input.sprint.setupRanAt.has(repoPath)) {
|
|
11107
|
+
const stampedAt = input.sprint.setupRanAt.get(repoPath);
|
|
11108
|
+
deps.logger.debug(
|
|
11109
|
+
`setup-scripts-sprint-start: skipping ${String(repoPath)} \u2014 already stamped at ${String(stampedAt)}`
|
|
11110
|
+
);
|
|
11111
|
+
continue;
|
|
11112
|
+
}
|
|
11113
|
+
const repo = project.value.repositories.find((r) => r.path === repoPath);
|
|
11114
|
+
if (repo === void 0) {
|
|
11115
|
+
continue;
|
|
11116
|
+
}
|
|
11117
|
+
const script = repo.setupScript;
|
|
11118
|
+
if (script === void 0 || script.length === 0) {
|
|
11119
|
+
continue;
|
|
11120
|
+
}
|
|
11121
|
+
let outcome;
|
|
11122
|
+
try {
|
|
11123
|
+
outcome = await deps.external.runSetupScript(repoPath, script, repo.checkTimeout);
|
|
11124
|
+
} catch (err) {
|
|
11125
|
+
return Result.error(
|
|
11126
|
+
new InvalidStateError({
|
|
11127
|
+
entity: "sprint",
|
|
11128
|
+
currentState: "setup-failed",
|
|
11129
|
+
attemptedAction: "execute",
|
|
11130
|
+
message: `sprint-start setup script failed in ${String(repoPath)}: ${err instanceof Error ? err.message : String(err)}`,
|
|
11131
|
+
hint: "Fix the setup script (configured via `project onboard` / `project repo add`) and retry. Setup runs once at sprint start; the per-task `checkScript` is a separate gate."
|
|
11132
|
+
})
|
|
11133
|
+
);
|
|
11134
|
+
}
|
|
11135
|
+
if (!outcome.passed) {
|
|
11136
|
+
return Result.error(
|
|
11137
|
+
new InvalidStateError({
|
|
11138
|
+
entity: "sprint",
|
|
11139
|
+
currentState: "setup-failed",
|
|
11140
|
+
attemptedAction: "execute",
|
|
11141
|
+
message: `sprint-start setup script failed in ${String(repoPath)}`,
|
|
11142
|
+
hint: "Fix the setup script (configured via `project onboard` / `project repo add`) and retry. Setup runs once at sprint start; the per-task `checkScript` is a separate gate."
|
|
11143
|
+
})
|
|
11144
|
+
);
|
|
11145
|
+
}
|
|
11146
|
+
sprint = sprint.recordSetupRun(repoPath, now);
|
|
11147
|
+
}
|
|
11148
|
+
if (sprint !== input.sprint) {
|
|
11149
|
+
const saved = await deps.sprintRepo.save(sprint);
|
|
11150
|
+
if (!saved.ok) {
|
|
11151
|
+
deps.logger.warn("setup-scripts-sprint-start: failed to persist setup audit stamps", {
|
|
11152
|
+
error: saved.error.message
|
|
11153
|
+
});
|
|
11154
|
+
}
|
|
11103
11155
|
}
|
|
11104
11156
|
return Result.ok(void 0);
|
|
11105
11157
|
}
|
|
11106
11158
|
},
|
|
11107
|
-
input: (ctx) =>
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
}
|
|
11159
|
+
input: (ctx) => {
|
|
11160
|
+
if (!ctx.sprint) throw new Error("setup-scripts-sprint-start: ctx.sprint must be loaded first");
|
|
11161
|
+
return { sprintId: ctx.sprintId, sprint: ctx.sprint };
|
|
11162
|
+
},
|
|
11111
11163
|
output: (ctx) => ctx
|
|
11112
11164
|
}
|
|
11113
11165
|
);
|
|
@@ -11204,10 +11256,10 @@ function applyFeedbackLeaf(useCase) {
|
|
|
11204
11256
|
});
|
|
11205
11257
|
}
|
|
11206
11258
|
const { resolveStoragePaths: resolveStoragePaths2 } = await import("./storage-paths-IPNZZM5D.mjs");
|
|
11207
|
-
const { join:
|
|
11259
|
+
const { join: join34 } = await import("path");
|
|
11208
11260
|
const sprintDir = resolveStoragePaths2().sprintDir(input.sprintId);
|
|
11209
11261
|
const { AbsolutePath: APV } = await import("./absolute-path-WUTZQ37D.mjs");
|
|
11210
|
-
const sessionMdPath = APV.trustString(
|
|
11262
|
+
const sessionMdPath = APV.trustString(join34(sprintDir, "feedback", `session-${String(input.iteration)}.md`));
|
|
11211
11263
|
const result = await useCase.execute({
|
|
11212
11264
|
sprint: input.sprint,
|
|
11213
11265
|
promptFilePath: String(input.promptFilePath),
|
|
@@ -11244,7 +11296,7 @@ function recordFeedbackIterationLeaf(logger) {
|
|
|
11244
11296
|
try {
|
|
11245
11297
|
const { resolveStoragePaths: resolveStoragePaths2 } = await import("./storage-paths-IPNZZM5D.mjs");
|
|
11246
11298
|
const { mkdir: mkdir16, appendFile: appendFile4 } = await import("fs/promises");
|
|
11247
|
-
const { dirname:
|
|
11299
|
+
const { dirname: dirname16 } = await import("path");
|
|
11248
11300
|
const path = resolveStoragePaths2().feedbackFile(input.sprintId);
|
|
11249
11301
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11250
11302
|
const block = `
|
|
@@ -11254,7 +11306,7 @@ function recordFeedbackIterationLeaf(logger) {
|
|
|
11254
11306
|
|
|
11255
11307
|
${input.feedbackText.trim()}
|
|
11256
11308
|
`;
|
|
11257
|
-
await mkdir16(
|
|
11309
|
+
await mkdir16(dirname16(path), { recursive: true });
|
|
11258
11310
|
await appendFile4(path, block, "utf-8");
|
|
11259
11311
|
} catch (err) {
|
|
11260
11312
|
logger.warn("feedback: failed to append to feedback.md", {
|
|
@@ -11716,8 +11768,8 @@ function saveSprintLeaf(deps, name = "save-sprint") {
|
|
|
11716
11768
|
}
|
|
11717
11769
|
|
|
11718
11770
|
// src/application/chains/leaves/save-tasks.ts
|
|
11719
|
-
import { writeFile as
|
|
11720
|
-
import { dirname as
|
|
11771
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
11772
|
+
import { dirname as dirname11 } from "path";
|
|
11721
11773
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
11722
11774
|
var FALLBACK_CRITERION = "(no explicit criteria \u2014 use task description as proxy)";
|
|
11723
11775
|
function renderDoneCriteria(tasks) {
|
|
@@ -11743,9 +11795,9 @@ function saveTasksLeaf(deps, name = "save-tasks") {
|
|
|
11743
11795
|
const storage2 = resolveStoragePaths();
|
|
11744
11796
|
const criteriaPath = String(storage2.doneCriteriaFile(input.sprintId));
|
|
11745
11797
|
try {
|
|
11746
|
-
await mkdir12(
|
|
11798
|
+
await mkdir12(dirname11(criteriaPath), { recursive: true });
|
|
11747
11799
|
const body = renderDoneCriteria(input.tasks);
|
|
11748
|
-
await
|
|
11800
|
+
await writeFile7(criteriaPath, body, { encoding: "utf-8", mode: 384 });
|
|
11749
11801
|
} catch (err) {
|
|
11750
11802
|
return Result.error(
|
|
11751
11803
|
new StorageError({
|
|
@@ -11848,11 +11900,11 @@ function ideateAndPlanLeaf(useCase) {
|
|
|
11848
11900
|
|
|
11849
11901
|
// src/application/chains/onboard/onboard-load-leaves.ts
|
|
11850
11902
|
import { readFile as readFile9 } from "fs/promises";
|
|
11851
|
-
import { join as
|
|
11903
|
+
import { join as join22 } from "path";
|
|
11852
11904
|
|
|
11853
11905
|
// src/application/chains/onboard/onboard-persist-leaves.ts
|
|
11854
|
-
import { mkdir as mkdir13, writeFile as
|
|
11855
|
-
import { dirname as
|
|
11906
|
+
import { mkdir as mkdir13, writeFile as writeFile8 } from "fs/promises";
|
|
11907
|
+
import { dirname as dirname12, join as join21 } from "path";
|
|
11856
11908
|
var HARNESS_MARKER_PREFIX = "<!-- ralphctl onboard:";
|
|
11857
11909
|
function makeMarker(now = () => /* @__PURE__ */ new Date()) {
|
|
11858
11910
|
return `${HARNESS_MARKER_PREFIX} ${now().toISOString()} -->`;
|
|
@@ -11865,14 +11917,14 @@ function writeContextFileLeaf(now = () => /* @__PURE__ */ new Date()) {
|
|
|
11865
11917
|
if (proposals === void 0 || accepted === null || accepted === void 0 || accepted.length === 0) {
|
|
11866
11918
|
return Result.ok(void 0);
|
|
11867
11919
|
}
|
|
11868
|
-
const targetPath =
|
|
11920
|
+
const targetPath = join21(repo.path, proposals.contextFilePath);
|
|
11869
11921
|
try {
|
|
11870
|
-
await mkdir13(
|
|
11922
|
+
await mkdir13(dirname12(targetPath), { recursive: true });
|
|
11871
11923
|
const marker = makeMarker(now);
|
|
11872
11924
|
const firstLine = accepted.split("\n", 1)[0] ?? "";
|
|
11873
11925
|
const body = firstLine.startsWith(HARNESS_MARKER_PREFIX) ? accepted : `${marker}
|
|
11874
11926
|
${accepted}`;
|
|
11875
|
-
await
|
|
11927
|
+
await writeFile8(targetPath, body, "utf-8");
|
|
11876
11928
|
return Result.ok(void 0);
|
|
11877
11929
|
} catch (err) {
|
|
11878
11930
|
return Result.error(
|
|
@@ -11944,7 +11996,7 @@ var PRE_EXISTING_CONTEXT_FILES = ["CLAUDE.md", ".github/copilot-instructions.md"
|
|
|
11944
11996
|
async function findExistingContextFiles(repoPath) {
|
|
11945
11997
|
const found = [];
|
|
11946
11998
|
for (const relPath of PRE_EXISTING_CONTEXT_FILES) {
|
|
11947
|
-
const fullPath =
|
|
11999
|
+
const fullPath = join22(repoPath, relPath);
|
|
11948
12000
|
let body;
|
|
11949
12001
|
try {
|
|
11950
12002
|
body = await readFile9(fullPath, "utf-8");
|
|
@@ -12064,7 +12116,7 @@ function detectExistingFilesLeaf(deps) {
|
|
|
12064
12116
|
|
|
12065
12117
|
// src/application/chains/onboard/onboard-ai-leaves.ts
|
|
12066
12118
|
import { readFile as readFile10 } from "fs/promises";
|
|
12067
|
-
import { join as
|
|
12119
|
+
import { join as join23 } from "path";
|
|
12068
12120
|
|
|
12069
12121
|
// src/business/usecases/onboard/onboard-repo.ts
|
|
12070
12122
|
function contextFilePathFor(provider) {
|
|
@@ -12221,7 +12273,7 @@ function runOnboardAiLeaf(deps) {
|
|
|
12221
12273
|
await deps.aiSession.ensureReady();
|
|
12222
12274
|
const provider = deps.aiSession.getProviderName();
|
|
12223
12275
|
const fileName = contextFilePathFor(provider);
|
|
12224
|
-
const targetPath =
|
|
12276
|
+
const targetPath = join23(input.repo.path, fileName);
|
|
12225
12277
|
const detected = await detectModeAndBody(targetPath);
|
|
12226
12278
|
const result = await useCase.execute({
|
|
12227
12279
|
project: input.project,
|
|
@@ -12378,11 +12430,11 @@ function createOnboardFlow(deps, opts) {
|
|
|
12378
12430
|
|
|
12379
12431
|
// src/application/chains/plan/plan-flow.ts
|
|
12380
12432
|
import { mkdir as mkdir14 } from "fs/promises";
|
|
12381
|
-
import { dirname as
|
|
12433
|
+
import { dirname as dirname14, join as join24 } from "path";
|
|
12382
12434
|
|
|
12383
12435
|
// src/business/usecases/plan/plan-sprint-tasks.ts
|
|
12384
12436
|
import { readFile as readFile11 } from "fs/promises";
|
|
12385
|
-
import { dirname as
|
|
12437
|
+
import { dirname as dirname13 } from "path";
|
|
12386
12438
|
var PlanSprintTasksUseCase = class {
|
|
12387
12439
|
constructor(ai, logger) {
|
|
12388
12440
|
this.ai = ai;
|
|
@@ -12454,8 +12506,8 @@ var PlanSprintTasksUseCase = class {
|
|
|
12454
12506
|
// the whole spec before the user sees any response.
|
|
12455
12507
|
async runInteractive(input, wrapper, log) {
|
|
12456
12508
|
const handover = input.runInTerminal ?? (async (fn) => fn());
|
|
12457
|
-
const promptDir =
|
|
12458
|
-
const outputDir = input.outputFilePath !== void 0 ?
|
|
12509
|
+
const promptDir = dirname13(input.promptFilePath);
|
|
12510
|
+
const outputDir = input.outputFilePath !== void 0 ? dirname13(input.outputFilePath) : promptDir;
|
|
12459
12511
|
const repoArgs = buildAdditionalCwdArgs(input.additionalRepoPaths);
|
|
12460
12512
|
const extraArgs = [...repoArgs, "--add-dir", promptDir];
|
|
12461
12513
|
if (outputDir !== promptDir) {
|
|
@@ -12626,7 +12678,7 @@ function createPlanFlow(deps, opts) {
|
|
|
12626
12678
|
if (!ctx.planningFolderRoot) {
|
|
12627
12679
|
throw new Error("render-prompt-to-file: ctx.planningFolderRoot must be set by build-planning-folder");
|
|
12628
12680
|
}
|
|
12629
|
-
return AbsolutePath.trustString(
|
|
12681
|
+
return AbsolutePath.trustString(join24(String(ctx.planningFolderRoot), "prompt.md"));
|
|
12630
12682
|
},
|
|
12631
12683
|
buildPrompt: (ctx) => {
|
|
12632
12684
|
if (!ctx.sprint) {
|
|
@@ -12748,7 +12800,7 @@ function planTasksLeaf(useCase, opts) {
|
|
|
12748
12800
|
useCase: {
|
|
12749
12801
|
async execute(input) {
|
|
12750
12802
|
if (opts.outputFilePath !== void 0 && opts.outputFilePath !== "") {
|
|
12751
|
-
await mkdir14(
|
|
12803
|
+
await mkdir14(dirname14(opts.outputFilePath), { recursive: true });
|
|
12752
12804
|
}
|
|
12753
12805
|
const result = await useCase.execute({
|
|
12754
12806
|
sprint: input.sprint,
|
|
@@ -12912,11 +12964,11 @@ function assertAllTicketsApprovedLeaf() {
|
|
|
12912
12964
|
}
|
|
12913
12965
|
|
|
12914
12966
|
// src/application/chains/refine/refine-flow.ts
|
|
12915
|
-
import { join as
|
|
12967
|
+
import { join as join25 } from "path";
|
|
12916
12968
|
|
|
12917
12969
|
// src/business/usecases/refine/refine-single-ticket.ts
|
|
12918
12970
|
import { readFile as readFile12 } from "fs/promises";
|
|
12919
|
-
import { dirname as
|
|
12971
|
+
import { dirname as dirname15 } from "path";
|
|
12920
12972
|
var RefineSingleTicketUseCase = class {
|
|
12921
12973
|
constructor(ai, logger) {
|
|
12922
12974
|
this.ai = ai;
|
|
@@ -12979,8 +13031,8 @@ var RefineSingleTicketUseCase = class {
|
|
|
12979
13031
|
// scroll past before it responds.
|
|
12980
13032
|
async runInteractive(input, wrapper, log) {
|
|
12981
13033
|
const handover = input.runInTerminal ?? (async (fn) => fn());
|
|
12982
|
-
const promptDir =
|
|
12983
|
-
const outputDir = input.outputFilePath !== void 0 ?
|
|
13034
|
+
const promptDir = dirname15(input.promptFilePath);
|
|
13035
|
+
const outputDir = input.outputFilePath !== void 0 ? dirname15(input.outputFilePath) : promptDir;
|
|
12984
13036
|
const addDirArgs = ["--add-dir", promptDir];
|
|
12985
13037
|
if (outputDir !== promptDir) {
|
|
12986
13038
|
addDirArgs.push("--add-dir", outputDir);
|
|
@@ -13193,7 +13245,7 @@ function buildPerTicketChain(deps, refineUseCase, ticket, opts) {
|
|
|
13193
13245
|
if (!ctx.refinementUnitRoot) {
|
|
13194
13246
|
throw new Error(`refine-${String(ticket.id)}: ctx.refinementUnitRoot must be set by build-refinement-unit`);
|
|
13195
13247
|
}
|
|
13196
|
-
return AbsolutePath.trustString(
|
|
13248
|
+
return AbsolutePath.trustString(join25(String(ctx.refinementUnitRoot), "prompt.md"));
|
|
13197
13249
|
},
|
|
13198
13250
|
buildPrompt: (ctx) => {
|
|
13199
13251
|
const outputFilePath = ctx.refinementRequirementsJsonPath !== void 0 ? String(ctx.refinementRequirementsJsonPath) : void 0;
|
|
@@ -14483,18 +14535,24 @@ function stepGlyph(status, spinnerFrame) {
|
|
|
14483
14535
|
return /* @__PURE__ */ jsx19(Text17, { color: inkColors.muted, bold: true, children: glyphs.emDash });
|
|
14484
14536
|
return /* @__PURE__ */ jsx19(Text17, { color: inkColors.muted, bold: true, children: glyphs.phasePending });
|
|
14485
14537
|
}
|
|
14538
|
+
var MAX_RENDERED_STEPS = 50;
|
|
14486
14539
|
function StepTrace({ steps, isRunning }) {
|
|
14487
14540
|
const spinnerFrame = useSpinnerFrame();
|
|
14488
14541
|
if (steps.length === 0) {
|
|
14489
14542
|
if (isRunning) return /* @__PURE__ */ jsx19(Spinner, { label: "Starting\u2026" });
|
|
14490
14543
|
return /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "No steps recorded." });
|
|
14491
14544
|
}
|
|
14492
|
-
|
|
14493
|
-
|
|
14494
|
-
|
|
14495
|
-
|
|
14496
|
-
step
|
|
14497
|
-
|
|
14545
|
+
const visible = steps.length > MAX_RENDERED_STEPS ? steps.slice(-MAX_RENDERED_STEPS) : steps;
|
|
14546
|
+
const elided = steps.length - visible.length;
|
|
14547
|
+
return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
|
|
14548
|
+
elided > 0 ? /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: `\u2026 ${String(elided)} earlier steps` }) : null,
|
|
14549
|
+
visible.map((step, i) => /* @__PURE__ */ jsxs18(Box18, { children: [
|
|
14550
|
+
stepGlyph(step.status, spinnerFrame),
|
|
14551
|
+
/* @__PURE__ */ jsx19(Text17, { bold: step.status === void 0, children: ` ${step.name}` }),
|
|
14552
|
+
step.durationMs !== void 0 ? /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: ` ${glyphs.inlineDot} ${durationLabel(step.durationMs)}` }) : null,
|
|
14553
|
+
step.errorMessage ? /* @__PURE__ */ jsx19(Text17, { color: inkColors.error, children: ` ${glyphs.emDash} ${step.errorMessage}` }) : null
|
|
14554
|
+
] }, i))
|
|
14555
|
+
] });
|
|
14498
14556
|
}
|
|
14499
14557
|
function CompactStepSummary({ steps }) {
|
|
14500
14558
|
if (steps.length === 0) return /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "No steps recorded." });
|
|
@@ -14974,6 +15032,7 @@ var EXECUTE_HINTS_TERMINAL = [
|
|
|
14974
15032
|
function isTaskStep2(name) {
|
|
14975
15033
|
return /^task-[a-zA-Z0-9_-]+$/.test(name);
|
|
14976
15034
|
}
|
|
15035
|
+
var MAX_LIVE_STEPS = 200;
|
|
14977
15036
|
function ExecuteView({ sessionId, sessionManager, signalBus }) {
|
|
14978
15037
|
const router = useRouterOptional();
|
|
14979
15038
|
const cancelInFlight = useRef2(false);
|
|
@@ -15043,12 +15102,14 @@ function ExecuteView({ sessionId, sessionManager, signalBus }) {
|
|
|
15043
15102
|
durationMs: entry.durationMs,
|
|
15044
15103
|
errorMessage: entry.error?.message
|
|
15045
15104
|
};
|
|
15105
|
+
let next;
|
|
15046
15106
|
if (idx >= 0) {
|
|
15047
|
-
|
|
15107
|
+
next = [...prev];
|
|
15048
15108
|
next[idx] = settled;
|
|
15049
|
-
|
|
15109
|
+
} else {
|
|
15110
|
+
next = [...prev, settled];
|
|
15050
15111
|
}
|
|
15051
|
-
return
|
|
15112
|
+
return next.length > MAX_LIVE_STEPS ? next.slice(-MAX_LIVE_STEPS) : next;
|
|
15052
15113
|
});
|
|
15053
15114
|
if (signalBus === void 0 || signalBus === null) {
|
|
15054
15115
|
if (entry.stepName === "rate-limit-paused") setRateLimitVisible(true);
|
|
@@ -18542,13 +18603,13 @@ async function currentSprintReadableCheck(deps) {
|
|
|
18542
18603
|
}
|
|
18543
18604
|
|
|
18544
18605
|
// src/application/doctor/checks/data-dir-writable.ts
|
|
18545
|
-
import { mkdir as mkdir15, unlink as unlink3, writeFile as
|
|
18546
|
-
import { join as
|
|
18606
|
+
import { mkdir as mkdir15, unlink as unlink3, writeFile as writeFile9 } from "fs/promises";
|
|
18607
|
+
import { join as join26 } from "path";
|
|
18547
18608
|
async function dataDirWritableCheck(deps) {
|
|
18548
|
-
const probe =
|
|
18609
|
+
const probe = join26(deps.storage.dataDir, `.doctor-write-${String(process.pid)}-${String(Date.now())}.tmp`);
|
|
18549
18610
|
try {
|
|
18550
18611
|
await mkdir15(deps.storage.dataDir, { recursive: true });
|
|
18551
|
-
await
|
|
18612
|
+
await writeFile9(probe, "doctor", { encoding: "utf-8", mode: 384 });
|
|
18552
18613
|
} catch (err) {
|
|
18553
18614
|
return {
|
|
18554
18615
|
name: "Data directory",
|
|
@@ -18655,7 +18716,7 @@ function nodeVersionCheck() {
|
|
|
18655
18716
|
|
|
18656
18717
|
// src/application/doctor/checks/onboarding-status.ts
|
|
18657
18718
|
import { readFile as readFile13 } from "fs/promises";
|
|
18658
|
-
import { join as
|
|
18719
|
+
import { join as join27 } from "path";
|
|
18659
18720
|
var HARNESS_MARKER_PREFIX2 = "<!-- ralphctl onboard:";
|
|
18660
18721
|
var MIN_HYBRID_PROSE_CHARS = 200;
|
|
18661
18722
|
var CONTEXT_FILE_BY_PROVIDER = {
|
|
@@ -18676,7 +18737,7 @@ function extractPreamble(body) {
|
|
|
18676
18737
|
return body.slice(0, idx);
|
|
18677
18738
|
}
|
|
18678
18739
|
async function classifyContextFile(repoPath, relPath) {
|
|
18679
|
-
const fullPath =
|
|
18740
|
+
const fullPath = join27(repoPath, relPath);
|
|
18680
18741
|
let body;
|
|
18681
18742
|
try {
|
|
18682
18743
|
body = await readFile13(fullPath, "utf-8");
|
|
@@ -18759,10 +18820,10 @@ async function onboardingStatusCheck(deps) {
|
|
|
18759
18820
|
|
|
18760
18821
|
// src/application/doctor/checks/project-paths-exist.ts
|
|
18761
18822
|
import { stat as stat5 } from "fs/promises";
|
|
18762
|
-
import { join as
|
|
18823
|
+
import { join as join28 } from "path";
|
|
18763
18824
|
async function isGitDir(path) {
|
|
18764
18825
|
try {
|
|
18765
|
-
const s = await stat5(
|
|
18826
|
+
const s = await stat5(join28(path, ".git"));
|
|
18766
18827
|
return s.isDirectory() || s.isFile();
|
|
18767
18828
|
} catch {
|
|
18768
18829
|
return false;
|
|
@@ -18823,9 +18884,9 @@ async function projectPathsExistCheck(deps) {
|
|
|
18823
18884
|
}
|
|
18824
18885
|
|
|
18825
18886
|
// src/application/doctor/checks/session-log-path.ts
|
|
18826
|
-
import { join as
|
|
18887
|
+
import { join as join29 } from "path";
|
|
18827
18888
|
function sessionLogPathCheck(deps) {
|
|
18828
|
-
const file =
|
|
18889
|
+
const file = join29(deps.storage.logsDir, `${deps.sessionId}.jsonl`);
|
|
18829
18890
|
return Promise.resolve({
|
|
18830
18891
|
name: "Session log path",
|
|
18831
18892
|
status: "pass",
|
|
@@ -18965,12 +19026,12 @@ function DoctorView() {
|
|
|
18965
19026
|
|
|
18966
19027
|
// src/application/tui/views/browse/progress-view.tsx
|
|
18967
19028
|
import { useEffect as useEffect39, useState as useState25 } from "react";
|
|
18968
|
-
import { join as
|
|
19029
|
+
import { join as join31 } from "path";
|
|
18969
19030
|
import { Box as Box34, Text as Text29 } from "ink";
|
|
18970
19031
|
|
|
18971
19032
|
// src/business/usecases/sprint/show-progress.ts
|
|
18972
19033
|
import { readFile as readFile14 } from "fs/promises";
|
|
18973
|
-
import { join as
|
|
19034
|
+
import { join as join30 } from "path";
|
|
18974
19035
|
var DEFAULT_STALE_THRESHOLD_HOURS = 24;
|
|
18975
19036
|
var STALE_THRESHOLD_HOURS = DEFAULT_STALE_THRESHOLD_HOURS;
|
|
18976
19037
|
var ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
|
|
@@ -19056,7 +19117,7 @@ function detectBranchInconsistency(sprint, projects, external) {
|
|
|
19056
19117
|
return out;
|
|
19057
19118
|
}
|
|
19058
19119
|
var ShowProgressUseCase = class {
|
|
19059
|
-
constructor(sprints, tasks, projects, external, readFileImpl = (path) => readFile14(path, "utf-8"), progressPathForSprint = (sprintId) =>
|
|
19120
|
+
constructor(sprints, tasks, projects, external, readFileImpl = (path) => readFile14(path, "utf-8"), progressPathForSprint = (sprintId) => join30(process.env["RALPHCTL_ROOT"] ?? "", "data", "sprints", String(sprintId), "progress.md")) {
|
|
19060
19121
|
this.sprints = sprints;
|
|
19061
19122
|
this.tasks = tasks;
|
|
19062
19123
|
this.projects = projects;
|
|
@@ -19152,7 +19213,7 @@ function ProgressView() {
|
|
|
19152
19213
|
deps.projectRepo,
|
|
19153
19214
|
deps.external,
|
|
19154
19215
|
void 0,
|
|
19155
|
-
(id) =>
|
|
19216
|
+
(id) => join31(String(deps.storage.sprintsDir), String(id), "progress.md")
|
|
19156
19217
|
);
|
|
19157
19218
|
const result = await uc.execute({ sprintId, now: IsoTimestamp.now() });
|
|
19158
19219
|
if (cancel.current) return;
|
|
@@ -19255,14 +19316,14 @@ function Timeline({ report }) {
|
|
|
19255
19316
|
|
|
19256
19317
|
// src/application/tui/views/crud/sprint-export-requirements-view.tsx
|
|
19257
19318
|
import { useEffect as useEffect40 } from "react";
|
|
19258
|
-
import { writeFile as
|
|
19319
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
19259
19320
|
import { isAbsolute, resolve } from "path";
|
|
19260
19321
|
|
|
19261
19322
|
// src/business/usecases/sprint/export-requirements.ts
|
|
19262
19323
|
import { readFile as readFile15 } from "fs/promises";
|
|
19263
19324
|
var ExportRequirementsUseCase = class {
|
|
19264
|
-
constructor(
|
|
19265
|
-
this.writeFile =
|
|
19325
|
+
constructor(writeFile14, readJsonFile2 = (p) => readFile15(p, "utf-8")) {
|
|
19326
|
+
this.writeFile = writeFile14;
|
|
19266
19327
|
this.readJsonFile = readJsonFile2;
|
|
19267
19328
|
}
|
|
19268
19329
|
writeFile;
|
|
@@ -19335,7 +19396,7 @@ function SprintExportRequirementsView() {
|
|
|
19335
19396
|
const finalPath = trimmed.length === 0 ? defaultPath : isAbsolute(trimmed) ? trimmed : resolve(process.cwd(), trimmed);
|
|
19336
19397
|
setStep("Writing file\u2026");
|
|
19337
19398
|
const aggregatePath = resolveStoragePaths().requirementsAggregateFile(sprintId);
|
|
19338
|
-
const uc = new ExportRequirementsUseCase((p, b) =>
|
|
19399
|
+
const uc = new ExportRequirementsUseCase((p, b) => writeFile10(p, b, "utf-8"));
|
|
19339
19400
|
const result = await uc.execute({
|
|
19340
19401
|
aggregatePath,
|
|
19341
19402
|
outputPath: AbsolutePath.trustString(finalPath)
|
|
@@ -19372,16 +19433,16 @@ function SprintExportRequirementsView() {
|
|
|
19372
19433
|
|
|
19373
19434
|
// src/application/tui/views/crud/sprint-export-context-view.tsx
|
|
19374
19435
|
import { useEffect as useEffect41 } from "react";
|
|
19375
|
-
import { writeFile as
|
|
19436
|
+
import { writeFile as writeFile11 } from "fs/promises";
|
|
19376
19437
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
19377
19438
|
|
|
19378
19439
|
// src/business/usecases/sprint/export-context.ts
|
|
19379
19440
|
var ExportContextUseCase = class {
|
|
19380
|
-
constructor(sprints, tasks, projects,
|
|
19441
|
+
constructor(sprints, tasks, projects, writeFile14) {
|
|
19381
19442
|
this.sprints = sprints;
|
|
19382
19443
|
this.tasks = tasks;
|
|
19383
19444
|
this.projects = projects;
|
|
19384
|
-
this.writeFile =
|
|
19445
|
+
this.writeFile = writeFile14;
|
|
19385
19446
|
}
|
|
19386
19447
|
sprints;
|
|
19387
19448
|
tasks;
|
|
@@ -19543,7 +19604,7 @@ function SprintExportContextView() {
|
|
|
19543
19604
|
deps.sprintRepo,
|
|
19544
19605
|
deps.taskRepo,
|
|
19545
19606
|
deps.projectRepo,
|
|
19546
|
-
(p, b) =>
|
|
19607
|
+
(p, b) => writeFile11(p, b, "utf-8")
|
|
19547
19608
|
);
|
|
19548
19609
|
const result = await uc.execute({
|
|
19549
19610
|
sprintId,
|
|
@@ -20126,7 +20187,7 @@ async function handleCompletionRequest(program, deps) {
|
|
|
20126
20187
|
// src/application/cli/commands/completion-install.ts
|
|
20127
20188
|
import { appendFile as appendFile3, readFile as readFile16 } from "fs/promises";
|
|
20128
20189
|
import { homedir as homedir2 } from "os";
|
|
20129
|
-
import { join as
|
|
20190
|
+
import { join as join32 } from "path";
|
|
20130
20191
|
import * as c from "colorette";
|
|
20131
20192
|
|
|
20132
20193
|
// src/application/cli/exit-codes.ts
|
|
@@ -20236,11 +20297,11 @@ function rcFileForShell(shell) {
|
|
|
20236
20297
|
const home = homedir2();
|
|
20237
20298
|
switch (shell) {
|
|
20238
20299
|
case "bash":
|
|
20239
|
-
return
|
|
20300
|
+
return join32(home, ".bashrc");
|
|
20240
20301
|
case "zsh":
|
|
20241
|
-
return
|
|
20302
|
+
return join32(home, ".zshrc");
|
|
20242
20303
|
case "fish":
|
|
20243
|
-
return
|
|
20304
|
+
return join32(home, ".config", "fish", "config.fish");
|
|
20244
20305
|
}
|
|
20245
20306
|
}
|
|
20246
20307
|
function runCompletionShow(requestedShell) {
|
|
@@ -21131,12 +21192,12 @@ async function runSprintClose(deps, id) {
|
|
|
21131
21192
|
}
|
|
21132
21193
|
|
|
21133
21194
|
// src/application/cli/commands/sprint-context.ts
|
|
21134
|
-
import { writeFile as
|
|
21195
|
+
import { writeFile as writeFile13 } from "fs/promises";
|
|
21135
21196
|
import { isAbsolute as isAbsolute4, resolve as resolve4 } from "path";
|
|
21136
21197
|
import * as c16 from "colorette";
|
|
21137
21198
|
|
|
21138
21199
|
// src/application/cli/commands/sprint-requirements.ts
|
|
21139
|
-
import { writeFile as
|
|
21200
|
+
import { writeFile as writeFile12 } from "fs/promises";
|
|
21140
21201
|
import { isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
|
|
21141
21202
|
import * as c15 from "colorette";
|
|
21142
21203
|
function attachSprintRequirements(group, deps) {
|
|
@@ -21153,7 +21214,7 @@ async function runSprintRequirements(deps, id, opts) {
|
|
|
21153
21214
|
if (!sprintR.ok) return sprintR;
|
|
21154
21215
|
const outPath = resolveOutputPath(opts.output, `${String(sprintR.value)}-requirements.md`);
|
|
21155
21216
|
const aggregatePath = resolveStoragePaths().requirementsAggregateFile(sprintR.value);
|
|
21156
|
-
const uc = new ExportRequirementsUseCase((path, body) =>
|
|
21217
|
+
const uc = new ExportRequirementsUseCase((path, body) => writeFile12(path, body, "utf-8"));
|
|
21157
21218
|
return uc.execute({ aggregatePath, outputPath: outPath });
|
|
21158
21219
|
},
|
|
21159
21220
|
format: (_d, out) => `${c15.green("wrote")} ${String(out.path)} (${String(out.byteCount)} bytes)`
|
|
@@ -21220,7 +21281,7 @@ async function runSprintContext(deps, id, opts) {
|
|
|
21220
21281
|
deps.sprintRepo,
|
|
21221
21282
|
deps.taskRepo,
|
|
21222
21283
|
deps.projectRepo,
|
|
21223
|
-
(path, body) =>
|
|
21284
|
+
(path, body) => writeFile13(path, body, "utf-8")
|
|
21224
21285
|
);
|
|
21225
21286
|
return uc.execute({ sprintId: sprintR.value, outputPath: outPath });
|
|
21226
21287
|
},
|
|
@@ -21488,7 +21549,7 @@ async function runSprintPlan(deps, opts) {
|
|
|
21488
21549
|
}
|
|
21489
21550
|
|
|
21490
21551
|
// src/application/cli/commands/sprint-progress.ts
|
|
21491
|
-
import { join as
|
|
21552
|
+
import { join as join33 } from "path";
|
|
21492
21553
|
import * as c19 from "colorette";
|
|
21493
21554
|
function attachSprintProgress(group, deps) {
|
|
21494
21555
|
group.command("progress [id]").description("show sprint progress, blockers, stale tasks, and dependency cycles").option("--log", "print the full timeline only (no diagnostics summary)").option("--lines <n>", "cap the number of timeline entries shown", "50").action(async (id, opts) => {
|
|
@@ -21532,7 +21593,7 @@ async function runSprintProgress(deps, id, opts) {
|
|
|
21532
21593
|
deps.projectRepo,
|
|
21533
21594
|
deps.external,
|
|
21534
21595
|
void 0,
|
|
21535
|
-
(sprintId) =>
|
|
21596
|
+
(sprintId) => join33(String(deps.storage.sprintsDir), String(sprintId), "progress.md")
|
|
21536
21597
|
);
|
|
21537
21598
|
return uc.execute({ sprintId: idR.value, now: IsoTimestamp.now() });
|
|
21538
21599
|
},
|
|
@@ -21749,7 +21810,10 @@ ${formatTicketsTable(sprint)}`
|
|
|
21749
21810
|
// src/application/cli/commands/sprint-start.ts
|
|
21750
21811
|
import * as c23 from "colorette";
|
|
21751
21812
|
function attachSprintStart(group, deps) {
|
|
21752
|
-
group.command("start").description("execute the active sprint").requiredOption("--sprint <id>", "sprint id").option("--cwd <abs>", "working directory for AI sessions", process.cwd()).option("--branch", "auto-generate sprint branch name (`ralphctl/<sprint-id>`)").option("--branch-name <name>", "use a custom branch name").option(
|
|
21813
|
+
group.command("start").description("execute the active sprint").requiredOption("--sprint <id>", "sprint id").option("--cwd <abs>", "working directory for AI sessions", process.cwd()).option("--branch", "auto-generate sprint branch name (`ralphctl/<sprint-id>`)").option("--branch-name <name>", "use a custom branch name").option(
|
|
21814
|
+
"--check-script <cmd>",
|
|
21815
|
+
"override the post-task check script for every task (otherwise auto-sourced from each repo)"
|
|
21816
|
+
).option("--no-commit", "do not auto-commit each task after the evaluator round").action(async (opts) => {
|
|
21753
21817
|
const code = await runSprintStart(deps, opts);
|
|
21754
21818
|
if (code !== EXIT_SUCCESS) process.exitCode = code;
|
|
21755
21819
|
});
|
|
@@ -22267,7 +22331,7 @@ async function runTicketRemove(deps, opts) {
|
|
|
22267
22331
|
// package.json
|
|
22268
22332
|
var package_default = {
|
|
22269
22333
|
name: "ralphctl",
|
|
22270
|
-
version: "0.6.
|
|
22334
|
+
version: "0.6.3",
|
|
22271
22335
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
|
|
22272
22336
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
22273
22337
|
type: "module",
|
|
@@ -22482,9 +22546,11 @@ function shouldAutoInvoke() {
|
|
|
22482
22546
|
if (process.env["VITEST"] !== void 0) return false;
|
|
22483
22547
|
const entry = process.argv[1];
|
|
22484
22548
|
if (entry === void 0) return false;
|
|
22485
|
-
|
|
22486
|
-
|
|
22487
|
-
|
|
22549
|
+
try {
|
|
22550
|
+
return pathToFileURL(realpathSync(entry)).href === import.meta.url;
|
|
22551
|
+
} catch {
|
|
22552
|
+
return false;
|
|
22553
|
+
}
|
|
22488
22554
|
}
|
|
22489
22555
|
if (shouldAutoInvoke()) {
|
|
22490
22556
|
void main(process.argv).then((code) => {
|