ralphctl 0.6.2 → 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 +475 -413
- package/dist/manifest.json +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -212,8 +212,8 @@ async function directoryHasEntries(p) {
|
|
|
212
212
|
try {
|
|
213
213
|
const s = await stat(p);
|
|
214
214
|
if (!s.isDirectory()) return false;
|
|
215
|
-
const { readdir:
|
|
216
|
-
const entries = await
|
|
215
|
+
const { readdir: readdir5 } = await import("fs/promises");
|
|
216
|
+
const entries = await readdir5(p);
|
|
217
217
|
return entries.length > 0;
|
|
218
218
|
} catch {
|
|
219
219
|
return false;
|
|
@@ -410,10 +410,9 @@ function renderEvaluateWorkspaceSection(workspaceDir) {
|
|
|
410
410
|
"",
|
|
411
411
|
"- `task.md` \u2014 the current task being evaluated (description, steps, verification criteria, status)",
|
|
412
412
|
"- `requirements/<ticket-id>.md` \u2014 the refined requirements + raw ticket text that motivated this task",
|
|
413
|
-
"- `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)",
|
|
414
414
|
"- `project-context.md` \u2014 the project's CLAUDE.md / .github/copilot-instructions.md (when present in the target repo)",
|
|
415
|
-
"- `dimensions.md` \u2014 the four floor dimensions plus any extra dimensions the planner emitted on this task"
|
|
416
|
-
"- `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"
|
|
417
416
|
].join("\n");
|
|
418
417
|
}
|
|
419
418
|
function renderDoneCriteriaSection(doneCriteriaBullet) {
|
|
@@ -572,8 +571,8 @@ ${input.task.steps.map((s, i) => `${String(i + 1)}. ${s}`).join("\n")}` : "";
|
|
|
572
571
|
${input.task.verificationCriteria.map((c34) => `- ${c34}`).join("\n")}` : "";
|
|
573
572
|
const branchLine = input.sprint.branch !== null ? `**Branch:** \`${input.sprint.branch}\`` : "";
|
|
574
573
|
const checkScriptSection = renderCheckScriptSection(input.checkScript);
|
|
575
|
-
const
|
|
576
|
-
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.";
|
|
577
576
|
const rendered = substitute(tpl.value, {
|
|
578
577
|
TASK_NAME: input.task.name,
|
|
579
578
|
TASK_ID: String(input.task.id),
|
|
@@ -915,8 +914,7 @@ function getAdapter(name) {
|
|
|
915
914
|
|
|
916
915
|
// src/integration/persistence/session-md-writer.ts
|
|
917
916
|
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
918
|
-
import { dirname as dirname2
|
|
919
|
-
import { readdir as readdir2 } from "fs/promises";
|
|
917
|
+
import { dirname as dirname2 } from "path";
|
|
920
918
|
var FRONTMATTER_DELIM = "---";
|
|
921
919
|
function renderFrontmatter(fields) {
|
|
922
920
|
const lines = [FRONTMATTER_DELIM];
|
|
@@ -996,7 +994,8 @@ async function writeSessionFinish(args) {
|
|
|
996
994
|
const fm2 = renderFrontmatter({
|
|
997
995
|
finished: args.finished,
|
|
998
996
|
exitCode: args.exitCode,
|
|
999
|
-
sessionId: args.sessionId
|
|
997
|
+
sessionId: args.sessionId,
|
|
998
|
+
model: args.model
|
|
1000
999
|
});
|
|
1001
1000
|
return writeFileSafe(args.path, `${fm2}
|
|
1002
1001
|
|
|
@@ -1010,6 +1009,7 @@ _(no prompt recorded \u2014 session finish without start)_
|
|
|
1010
1009
|
merged["finished"] = args.finished;
|
|
1011
1010
|
merged["exitCode"] = args.exitCode;
|
|
1012
1011
|
if (args.sessionId !== void 0) merged["sessionId"] = args.sessionId;
|
|
1012
|
+
if (args.model !== void 0) merged["model"] = args.model;
|
|
1013
1013
|
const fm = renderFrontmatter(merged);
|
|
1014
1014
|
const content = `${fm}
|
|
1015
1015
|
|
|
@@ -1056,22 +1056,6 @@ function unquote(s) {
|
|
|
1056
1056
|
}
|
|
1057
1057
|
return s;
|
|
1058
1058
|
}
|
|
1059
|
-
async function nextSessionPath(unitDir) {
|
|
1060
|
-
let entries;
|
|
1061
|
-
try {
|
|
1062
|
-
entries = await readdir2(unitDir);
|
|
1063
|
-
} catch {
|
|
1064
|
-
return join5(unitDir, "session-1.md");
|
|
1065
|
-
}
|
|
1066
|
-
let max = 0;
|
|
1067
|
-
for (const entry of entries) {
|
|
1068
|
-
const m = /^session-(\d+)\.md$/.exec(entry);
|
|
1069
|
-
if (!m) continue;
|
|
1070
|
-
const n = Number(m[1]);
|
|
1071
|
-
if (Number.isFinite(n) && n > max) max = n;
|
|
1072
|
-
}
|
|
1073
|
-
return join5(unitDir, `session-${String(max + 1)}.md`);
|
|
1074
|
-
}
|
|
1075
1059
|
|
|
1076
1060
|
// src/integration/ai/session/session-runner.ts
|
|
1077
1061
|
import { spawn } from "child_process";
|
|
@@ -1367,26 +1351,28 @@ var ProviderAiSessionAdapter = class {
|
|
|
1367
1351
|
* The actual provider exit code isn't surfaced through the runner's
|
|
1368
1352
|
* typed Result — `1` is a faithful "spawn failed" stand-in for audit
|
|
1369
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.
|
|
1370
1358
|
*/
|
|
1371
1359
|
async writeSessionMdFinishHeadless(options, result) {
|
|
1372
1360
|
const path = options.sessionMdPath;
|
|
1373
1361
|
if (path === void 0) return;
|
|
1374
1362
|
const sessionId = result.ok ? result.value.sessionId : void 0;
|
|
1363
|
+
const model = result.ok ? result.value.model : void 0;
|
|
1375
1364
|
const written = await writeSessionFinish({
|
|
1376
1365
|
path: String(path),
|
|
1377
1366
|
finished: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1378
1367
|
exitCode: result.ok ? 0 : 1,
|
|
1379
|
-
...sessionId !== void 0 ? { sessionId } : {}
|
|
1368
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
1369
|
+
...model !== void 0 ? { model } : {}
|
|
1380
1370
|
});
|
|
1381
1371
|
if (!written.ok) {
|
|
1382
1372
|
this.opts.logger?.warn("failed to write session.md (finish) \u2014 spawn already settled", {
|
|
1383
1373
|
path: String(path),
|
|
1384
1374
|
error: written.error.message
|
|
1385
1375
|
});
|
|
1386
|
-
return;
|
|
1387
|
-
}
|
|
1388
|
-
if (result.ok && result.value.model !== void 0) {
|
|
1389
|
-
await this.patchSessionMdModel(String(path), result.value.model);
|
|
1390
1376
|
}
|
|
1391
1377
|
}
|
|
1392
1378
|
/**
|
|
@@ -1409,33 +1395,6 @@ var ProviderAiSessionAdapter = class {
|
|
|
1409
1395
|
});
|
|
1410
1396
|
}
|
|
1411
1397
|
}
|
|
1412
|
-
/**
|
|
1413
|
-
* Patch the resolved `model` into an existing `session.md`. Reuses
|
|
1414
|
-
* `writeSessionFinish` to round-trip frontmatter without touching the
|
|
1415
|
-
* prompt body. Best-effort — write failures log a warn.
|
|
1416
|
-
*
|
|
1417
|
-
* Implementation note: `writeSessionFinish` was scoped to the
|
|
1418
|
-
* standard finish fields only. The narrow `model` patch here goes
|
|
1419
|
-
* through `writeSessionStart` would clobber the body, so we re-read,
|
|
1420
|
-
* splice the field, and re-emit via the same writer surface. This
|
|
1421
|
-
* keeps the YAML-handling logic in one place (`session-md-writer`)
|
|
1422
|
-
* even at the cost of a second IO round-trip — the audit pack is
|
|
1423
|
-
* cold-path and we'd rather centralise format knowledge.
|
|
1424
|
-
*/
|
|
1425
|
-
async patchSessionMdModel(path, model) {
|
|
1426
|
-
try {
|
|
1427
|
-
const fs = await import("fs/promises");
|
|
1428
|
-
const existing = await fs.readFile(path, "utf-8");
|
|
1429
|
-
const patched = upsertFrontmatterField(existing, "model", model);
|
|
1430
|
-
if (patched === existing) return;
|
|
1431
|
-
await fs.writeFile(path, patched, { encoding: "utf-8", mode: 384 });
|
|
1432
|
-
} catch (err) {
|
|
1433
|
-
this.opts.logger?.warn("failed to patch session.md model field", {
|
|
1434
|
-
path,
|
|
1435
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1436
|
-
});
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
1398
|
/**
|
|
1440
1399
|
* Build the flag list audited into session.md. Headless: the runner
|
|
1441
1400
|
* appends `--resume <id>` when `resumeSessionId` is set, so we mirror
|
|
@@ -1482,28 +1441,6 @@ function stripTrailingPromptSlot(args) {
|
|
|
1482
1441
|
if (end > 0 && args[end - 1] === "--") end -= 1;
|
|
1483
1442
|
return args.slice(0, end);
|
|
1484
1443
|
}
|
|
1485
|
-
function upsertFrontmatterField(content, key, value) {
|
|
1486
|
-
const lines = content.split("\n");
|
|
1487
|
-
if (lines[0]?.trim() !== "---") return content;
|
|
1488
|
-
let closeIdx = -1;
|
|
1489
|
-
for (let i = 1; i < lines.length; i += 1) {
|
|
1490
|
-
if (lines[i]?.trim() === "---") {
|
|
1491
|
-
closeIdx = i;
|
|
1492
|
-
break;
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
if (closeIdx < 0) return content;
|
|
1496
|
-
const keyRegex = new RegExp(`^${key}\\s*:`);
|
|
1497
|
-
for (let i = 1; i < closeIdx; i += 1) {
|
|
1498
|
-
const line = lines[i] ?? "";
|
|
1499
|
-
if (keyRegex.test(line.trim())) {
|
|
1500
|
-
lines[i] = `${key}: ${value}`;
|
|
1501
|
-
return lines.join("\n");
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
lines.splice(closeIdx, 0, `${key}: ${value}`);
|
|
1505
|
-
return lines.join("\n");
|
|
1506
|
-
}
|
|
1507
1444
|
|
|
1508
1445
|
// src/integration/ai/session/process-runner.ts
|
|
1509
1446
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -1605,20 +1542,20 @@ var NodeProcessRunner = class {
|
|
|
1605
1542
|
|
|
1606
1543
|
// src/integration/ai/skills/bundled-skills-copier.ts
|
|
1607
1544
|
import { existsSync as existsSync3 } from "fs";
|
|
1608
|
-
import { readdir as
|
|
1609
|
-
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";
|
|
1610
1547
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1611
1548
|
|
|
1612
1549
|
// src/integration/ai/skills/copy-tree.ts
|
|
1613
|
-
import { copyFile, mkdir as mkdir2, readdir as
|
|
1614
|
-
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";
|
|
1615
1552
|
async function copyTree(src, dst) {
|
|
1616
1553
|
try {
|
|
1617
1554
|
await mkdir2(dst, { recursive: true });
|
|
1618
|
-
const dirents = await
|
|
1555
|
+
const dirents = await readdir2(src, { withFileTypes: true });
|
|
1619
1556
|
for (const d of dirents) {
|
|
1620
|
-
const s =
|
|
1621
|
-
const t =
|
|
1557
|
+
const s = join5(src, d.name);
|
|
1558
|
+
const t = join5(dst, d.name);
|
|
1622
1559
|
if (d.isDirectory()) {
|
|
1623
1560
|
const r = await copyTree(s, t);
|
|
1624
1561
|
if (!r.ok) return r;
|
|
@@ -1654,12 +1591,12 @@ async function copyTree(src, dst) {
|
|
|
1654
1591
|
// src/integration/ai/skills/skill-git-exclude.ts
|
|
1655
1592
|
import { existsSync as existsSync2 } from "fs";
|
|
1656
1593
|
import { mkdir as mkdir3, readFile as readFile3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
1657
|
-
import { join as
|
|
1594
|
+
import { join as join6 } from "path";
|
|
1658
1595
|
var BEGIN_MARKER = "# >>> ralphctl-managed-skills (do not edit) >>>";
|
|
1659
1596
|
var END_MARKER = "# <<< ralphctl-managed-skills <<<";
|
|
1660
1597
|
var EXCLUDE_PATTERNS = [".claude/skills/"];
|
|
1661
1598
|
async function gitInfoDir(cwd) {
|
|
1662
|
-
const dotGit =
|
|
1599
|
+
const dotGit = join6(cwd, ".git");
|
|
1663
1600
|
if (!existsSync2(dotGit)) return null;
|
|
1664
1601
|
try {
|
|
1665
1602
|
const s = await stat3(dotGit);
|
|
@@ -1667,12 +1604,12 @@ async function gitInfoDir(cwd) {
|
|
|
1667
1604
|
} catch {
|
|
1668
1605
|
return null;
|
|
1669
1606
|
}
|
|
1670
|
-
return
|
|
1607
|
+
return join6(dotGit, "info");
|
|
1671
1608
|
}
|
|
1672
1609
|
async function addRalphctlSkillsExclude(cwd) {
|
|
1673
1610
|
const infoDir = await gitInfoDir(cwd);
|
|
1674
1611
|
if (infoDir === null) return Result.ok();
|
|
1675
|
-
const excludeFile =
|
|
1612
|
+
const excludeFile = join6(infoDir, "exclude");
|
|
1676
1613
|
try {
|
|
1677
1614
|
await mkdir3(infoDir, { recursive: true });
|
|
1678
1615
|
const existing = existsSync2(excludeFile) ? await readFile3(excludeFile, "utf8") : "";
|
|
@@ -1699,7 +1636,7 @@ ${block}`;
|
|
|
1699
1636
|
async function removeRalphctlSkillsExclude(cwd) {
|
|
1700
1637
|
const infoDir = await gitInfoDir(cwd);
|
|
1701
1638
|
if (infoDir === null) return Result.ok();
|
|
1702
|
-
const excludeFile =
|
|
1639
|
+
const excludeFile = join6(infoDir, "exclude");
|
|
1703
1640
|
if (!existsSync2(excludeFile)) return Result.ok();
|
|
1704
1641
|
try {
|
|
1705
1642
|
const body = await readFile3(excludeFile, "utf8");
|
|
@@ -1737,10 +1674,10 @@ function stripMarkerBlock(body) {
|
|
|
1737
1674
|
}
|
|
1738
1675
|
|
|
1739
1676
|
// src/integration/ai/skills/bundled-skills-copier.ts
|
|
1740
|
-
var SKILLS_SUBDIR =
|
|
1677
|
+
var SKILLS_SUBDIR = join7(".claude", "skills");
|
|
1741
1678
|
var HERE2 = dirname3(fileURLToPath2(import.meta.url));
|
|
1742
1679
|
function bundledSkillsRootDir() {
|
|
1743
|
-
const distRoot =
|
|
1680
|
+
const distRoot = join7(HERE2, "skills");
|
|
1744
1681
|
if (existsSync3(distRoot)) return AbsolutePath.trustString(distRoot);
|
|
1745
1682
|
return AbsolutePath.trustString(HERE2);
|
|
1746
1683
|
}
|
|
@@ -1756,12 +1693,12 @@ var FileBundledSkillsCopier = class {
|
|
|
1756
1693
|
this.bundledRootDir = opts.bundledRootDir ?? bundledSkillsRootDir();
|
|
1757
1694
|
}
|
|
1758
1695
|
async install(sessionDir, phase) {
|
|
1759
|
-
const skillsDir =
|
|
1696
|
+
const skillsDir = join7(sessionDir, SKILLS_SUBDIR);
|
|
1760
1697
|
const sources = await this.collectSourceSkills(phase);
|
|
1761
1698
|
if (!sources.ok) return Result.error(sources.error);
|
|
1762
1699
|
const tracked = this.installed.get(String(sessionDir)) ?? /* @__PURE__ */ new Set();
|
|
1763
1700
|
for (const [name, srcDir] of sources.value) {
|
|
1764
|
-
const dst =
|
|
1701
|
+
const dst = join7(skillsDir, name);
|
|
1765
1702
|
if (existsSync3(dst)) {
|
|
1766
1703
|
continue;
|
|
1767
1704
|
}
|
|
@@ -1788,10 +1725,10 @@ var FileBundledSkillsCopier = class {
|
|
|
1788
1725
|
await removeRalphctlSkillsExclude(sessionDir);
|
|
1789
1726
|
return Result.ok();
|
|
1790
1727
|
}
|
|
1791
|
-
const skillsDir =
|
|
1728
|
+
const skillsDir = join7(sessionDir, SKILLS_SUBDIR);
|
|
1792
1729
|
try {
|
|
1793
1730
|
for (const name of tracked) {
|
|
1794
|
-
await rm(
|
|
1731
|
+
await rm(join7(skillsDir, name), { recursive: true, force: true });
|
|
1795
1732
|
}
|
|
1796
1733
|
this.installed.delete(key);
|
|
1797
1734
|
} catch (err) {
|
|
@@ -1805,7 +1742,7 @@ var FileBundledSkillsCopier = class {
|
|
|
1805
1742
|
);
|
|
1806
1743
|
}
|
|
1807
1744
|
await tryRmdirIfEmpty(skillsDir);
|
|
1808
|
-
await tryRmdirIfEmpty(
|
|
1745
|
+
await tryRmdirIfEmpty(join7(sessionDir, ".claude"));
|
|
1809
1746
|
await removeRalphctlSkillsExclude(sessionDir);
|
|
1810
1747
|
return Result.ok();
|
|
1811
1748
|
}
|
|
@@ -1819,7 +1756,7 @@ var FileBundledSkillsCopier = class {
|
|
|
1819
1756
|
async collectSourceSkills(phase) {
|
|
1820
1757
|
const sources = /* @__PURE__ */ new Map();
|
|
1821
1758
|
for (const sub of ["default", phase]) {
|
|
1822
|
-
const dir =
|
|
1759
|
+
const dir = join7(this.bundledRootDir, sub);
|
|
1823
1760
|
const r = await listSkillDirs(dir);
|
|
1824
1761
|
if (!r.ok) return Result.error(r.error);
|
|
1825
1762
|
for (const [name, abs] of r.value) {
|
|
@@ -1832,10 +1769,10 @@ var FileBundledSkillsCopier = class {
|
|
|
1832
1769
|
async function listSkillDirs(dir) {
|
|
1833
1770
|
if (!existsSync3(dir)) return Result.ok([]);
|
|
1834
1771
|
try {
|
|
1835
|
-
const dirents = await
|
|
1772
|
+
const dirents = await readdir3(dir, { withFileTypes: true });
|
|
1836
1773
|
const out = [];
|
|
1837
1774
|
for (const d of dirents) {
|
|
1838
|
-
if (d.isDirectory()) out.push([d.name,
|
|
1775
|
+
if (d.isDirectory()) out.push([d.name, join7(dir, d.name)]);
|
|
1839
1776
|
}
|
|
1840
1777
|
return Result.ok(out);
|
|
1841
1778
|
} catch (err) {
|
|
@@ -1852,7 +1789,7 @@ async function listSkillDirs(dir) {
|
|
|
1852
1789
|
async function tryRmdirIfEmpty(dir) {
|
|
1853
1790
|
if (!existsSync3(dir)) return;
|
|
1854
1791
|
try {
|
|
1855
|
-
const entries = await
|
|
1792
|
+
const entries = await readdir3(dir);
|
|
1856
1793
|
if (entries.length === 0) await rmdir(dir);
|
|
1857
1794
|
} catch {
|
|
1858
1795
|
}
|
|
@@ -2156,7 +2093,12 @@ var DefaultExternalAdapter = class {
|
|
|
2156
2093
|
formatIssueContext(issue) {
|
|
2157
2094
|
return this.issues.format(issue);
|
|
2158
2095
|
}
|
|
2159
|
-
// ---
|
|
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
|
+
}
|
|
2160
2102
|
async runCheckScript(projectPath, script, phase, timeout) {
|
|
2161
2103
|
const r = await this.checkScripts.run(projectPath, script, phase, timeout);
|
|
2162
2104
|
if (r.ok) return r.value;
|
|
@@ -2932,9 +2874,9 @@ var JsonLogger = class _JsonLogger {
|
|
|
2932
2874
|
|
|
2933
2875
|
// src/integration/logging/jsonl-file-writer.ts
|
|
2934
2876
|
import { appendFile, mkdir as mkdir4 } from "fs/promises";
|
|
2935
|
-
import { dirname as dirname4, join as
|
|
2877
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
2936
2878
|
function fileFor(opts) {
|
|
2937
|
-
return AbsolutePath.trustString(
|
|
2879
|
+
return AbsolutePath.trustString(join8(opts.logsDir, `${opts.sessionId}.jsonl`));
|
|
2938
2880
|
}
|
|
2939
2881
|
var JsonlFileWriter = class {
|
|
2940
2882
|
constructor(opts) {
|
|
@@ -3304,7 +3246,7 @@ var NotFoundError = class extends Error {
|
|
|
3304
3246
|
|
|
3305
3247
|
// src/integration/persistence/json-io.ts
|
|
3306
3248
|
import { mkdir as mkdir6, readFile as readFile5, rename, writeFile as writeFile4 } from "fs/promises";
|
|
3307
|
-
import { dirname as dirname6, join as
|
|
3249
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
3308
3250
|
async function readJsonFile(path, schema2) {
|
|
3309
3251
|
let raw;
|
|
3310
3252
|
try {
|
|
@@ -3362,7 +3304,7 @@ ${issues}`,
|
|
|
3362
3304
|
);
|
|
3363
3305
|
}
|
|
3364
3306
|
const dir = dirname6(path);
|
|
3365
|
-
const tmp =
|
|
3307
|
+
const tmp = join9(dir, `.${pathBasename(path)}.${String(process.pid)}.${String(Date.now())}.${randomTail()}.tmp`);
|
|
3366
3308
|
try {
|
|
3367
3309
|
await mkdir6(dir, { recursive: true });
|
|
3368
3310
|
await writeFile4(tmp, JSON.stringify(validated.data, null, 2) + "\n", {
|
|
@@ -4038,7 +3980,7 @@ function errnoCode3(err) {
|
|
|
4038
3980
|
}
|
|
4039
3981
|
|
|
4040
3982
|
// src/integration/persistence/file-sprint-repository.ts
|
|
4041
|
-
import { mkdir as mkdir7, readdir as
|
|
3983
|
+
import { mkdir as mkdir7, readdir as readdir4, rm as rm2 } from "fs/promises";
|
|
4042
3984
|
|
|
4043
3985
|
// src/integration/persistence/schemas/sprint-schema.ts
|
|
4044
3986
|
import { z as z3 } from "zod";
|
|
@@ -4139,7 +4081,12 @@ var Sprint = class _Sprint {
|
|
|
4139
4081
|
activatedAt;
|
|
4140
4082
|
closedAt;
|
|
4141
4083
|
tickets;
|
|
4142
|
-
|
|
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;
|
|
4143
4090
|
branch;
|
|
4144
4091
|
/**
|
|
4145
4092
|
* Pull / merge request URL recorded after `sprint create-pr` runs.
|
|
@@ -4167,7 +4114,7 @@ var Sprint = class _Sprint {
|
|
|
4167
4114
|
this.activatedAt = props.activatedAt;
|
|
4168
4115
|
this.closedAt = props.closedAt;
|
|
4169
4116
|
this.tickets = props.tickets;
|
|
4170
|
-
this.
|
|
4117
|
+
this.setupRanAt = props.setupRanAt;
|
|
4171
4118
|
this.branch = props.branch;
|
|
4172
4119
|
this.pullRequestUrl = props.pullRequestUrl;
|
|
4173
4120
|
this.projectName = props.projectName;
|
|
@@ -4194,7 +4141,7 @@ var Sprint = class _Sprint {
|
|
|
4194
4141
|
activatedAt: null,
|
|
4195
4142
|
closedAt: null,
|
|
4196
4143
|
tickets: [],
|
|
4197
|
-
|
|
4144
|
+
setupRanAt: /* @__PURE__ */ new Map(),
|
|
4198
4145
|
branch: null,
|
|
4199
4146
|
pullRequestUrl: null,
|
|
4200
4147
|
projectName: input.projectName,
|
|
@@ -4231,7 +4178,7 @@ var Sprint = class _Sprint {
|
|
|
4231
4178
|
this.with({
|
|
4232
4179
|
status: "closed",
|
|
4233
4180
|
closedAt: now,
|
|
4234
|
-
|
|
4181
|
+
setupRanAt: /* @__PURE__ */ new Map()
|
|
4235
4182
|
})
|
|
4236
4183
|
);
|
|
4237
4184
|
}
|
|
@@ -4270,7 +4217,7 @@ var Sprint = class _Sprint {
|
|
|
4270
4217
|
activatedAt: this.activatedAt,
|
|
4271
4218
|
closedAt: this.closedAt,
|
|
4272
4219
|
tickets: this.tickets,
|
|
4273
|
-
|
|
4220
|
+
setupRanAt: this.setupRanAt,
|
|
4274
4221
|
branch: this.branch,
|
|
4275
4222
|
pullRequestUrl: this.pullRequestUrl,
|
|
4276
4223
|
projectName: this.projectName,
|
|
@@ -4366,13 +4313,13 @@ var Sprint = class _Sprint {
|
|
|
4366
4313
|
return Result8.ok(this.with({ branch }));
|
|
4367
4314
|
}
|
|
4368
4315
|
/**
|
|
4369
|
-
* Stamp a
|
|
4316
|
+
* Stamp a setup-script run for one repo. Never fails — the harness owns
|
|
4370
4317
|
* this audit trail and the entity should not gate it.
|
|
4371
4318
|
*/
|
|
4372
|
-
|
|
4373
|
-
const next = new Map(this.
|
|
4319
|
+
recordSetupRun(repo, at) {
|
|
4320
|
+
const next = new Map(this.setupRanAt);
|
|
4374
4321
|
next.set(repo, at);
|
|
4375
|
-
return this.with({
|
|
4322
|
+
return this.with({ setupRanAt: next });
|
|
4376
4323
|
}
|
|
4377
4324
|
/**
|
|
4378
4325
|
* Record the pull / merge request URL published for the sprint branch.
|
|
@@ -4458,7 +4405,7 @@ var Sprint = class _Sprint {
|
|
|
4458
4405
|
activatedAt: "activatedAt" in partial ? partial.activatedAt ?? null : this.activatedAt,
|
|
4459
4406
|
closedAt: "closedAt" in partial ? partial.closedAt ?? null : this.closedAt,
|
|
4460
4407
|
tickets: partial.tickets ?? this.tickets,
|
|
4461
|
-
|
|
4408
|
+
setupRanAt: partial.setupRanAt ?? this.setupRanAt,
|
|
4462
4409
|
branch: "branch" in partial ? partial.branch ?? null : this.branch,
|
|
4463
4410
|
pullRequestUrl: "pullRequestUrl" in partial ? partial.pullRequestUrl ?? null : this.pullRequestUrl,
|
|
4464
4411
|
projectName: this.projectName,
|
|
@@ -4642,7 +4589,13 @@ var sprintJsonSchema = z3.object({
|
|
|
4642
4589
|
// to `[]` on a fresh draft sprint, populated post-plan).
|
|
4643
4590
|
affectedRepositories: z3.array(z3.string()),
|
|
4644
4591
|
// `Map<AbsolutePath, IsoTimestamp>` — serialised as a plain object map.
|
|
4645
|
-
|
|
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({}),
|
|
4646
4599
|
tickets: z3.array(ticketJsonSchema)
|
|
4647
4600
|
});
|
|
4648
4601
|
var SYNTHETIC_SLUG_R = Slug.parse("rehydrate");
|
|
@@ -4655,9 +4608,9 @@ function toSprint(parsed) {
|
|
|
4655
4608
|
if (!r.ok) return Result.error(r.error);
|
|
4656
4609
|
tickets.push(r.value);
|
|
4657
4610
|
}
|
|
4658
|
-
const
|
|
4659
|
-
for (const [k, v] of Object.entries(parsed.
|
|
4660
|
-
|
|
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));
|
|
4661
4614
|
}
|
|
4662
4615
|
const created = Sprint.create({
|
|
4663
4616
|
id: SprintId.trustString(parsed.id),
|
|
@@ -4692,8 +4645,8 @@ function toSprint(parsed) {
|
|
|
4692
4645
|
const r = s.close(IsoTimestamp.trustString(at));
|
|
4693
4646
|
if (r.ok) s = r.value;
|
|
4694
4647
|
}
|
|
4695
|
-
for (const [path, at] of
|
|
4696
|
-
s = s.
|
|
4648
|
+
for (const [path, at] of setupRanAt) {
|
|
4649
|
+
s = s.recordSetupRun(path, at);
|
|
4697
4650
|
}
|
|
4698
4651
|
if (parsed.branch !== null) {
|
|
4699
4652
|
const r = s.setBranch(parsed.branch);
|
|
@@ -4706,9 +4659,9 @@ function toSprint(parsed) {
|
|
|
4706
4659
|
return Result.ok(s);
|
|
4707
4660
|
}
|
|
4708
4661
|
function fromSprint(sprint) {
|
|
4709
|
-
const
|
|
4710
|
-
for (const [k, v] of sprint.
|
|
4711
|
-
|
|
4662
|
+
const setupRanAt = {};
|
|
4663
|
+
for (const [k, v] of sprint.setupRanAt) {
|
|
4664
|
+
setupRanAt[k] = v;
|
|
4712
4665
|
}
|
|
4713
4666
|
return {
|
|
4714
4667
|
id: sprint.id,
|
|
@@ -4721,7 +4674,7 @@ function fromSprint(sprint) {
|
|
|
4721
4674
|
pullRequestUrl: sprint.pullRequestUrl,
|
|
4722
4675
|
projectName: sprint.projectName,
|
|
4723
4676
|
affectedRepositories: [...sprint.affectedRepositories],
|
|
4724
|
-
|
|
4677
|
+
setupRanAt,
|
|
4725
4678
|
tickets: sprint.tickets.map(fromTicket)
|
|
4726
4679
|
};
|
|
4727
4680
|
}
|
|
@@ -4809,7 +4762,7 @@ var FileSprintRepository = class {
|
|
|
4809
4762
|
async list() {
|
|
4810
4763
|
let entries;
|
|
4811
4764
|
try {
|
|
4812
|
-
const dirents = await
|
|
4765
|
+
const dirents = await readdir4(this.paths.sprintsDir, {
|
|
4813
4766
|
withFileTypes: true
|
|
4814
4767
|
});
|
|
4815
4768
|
entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
|
|
@@ -5498,8 +5451,8 @@ var FileWriteContextFileAdapter = class {
|
|
|
5498
5451
|
};
|
|
5499
5452
|
|
|
5500
5453
|
// src/integration/persistence/execution-unit-builder.ts
|
|
5501
|
-
import { readFile as readFile6
|
|
5502
|
-
import { join as
|
|
5454
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
5455
|
+
import { join as join11 } from "path";
|
|
5503
5456
|
|
|
5504
5457
|
// src/integration/persistence/unit-slug.ts
|
|
5505
5458
|
function unitSlug(id, name) {
|
|
@@ -5516,10 +5469,10 @@ function toSlug(name) {
|
|
|
5516
5469
|
|
|
5517
5470
|
// src/integration/persistence/session-folder-helpers.ts
|
|
5518
5471
|
import { cp, mkdir as mkdir10, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
5519
|
-
import { basename as basename2, dirname as dirname8, join as
|
|
5472
|
+
import { basename as basename2, dirname as dirname8, join as join10 } from "path";
|
|
5520
5473
|
function contextFileFor(provider) {
|
|
5521
5474
|
if (provider === "claude") return { path: "CLAUDE.md", needsGithubDir: false };
|
|
5522
|
-
return { path:
|
|
5475
|
+
return { path: join10(".github", "copilot-instructions.md"), needsGithubDir: true };
|
|
5523
5476
|
}
|
|
5524
5477
|
function renderContextFile(args) {
|
|
5525
5478
|
const { sprint, phase, affectedRepos, copilot } = args;
|
|
@@ -5541,7 +5494,7 @@ function renderInputsLine(phase) {
|
|
|
5541
5494
|
return "- Input: `./ticket.md` \u2014 the seed idea. Write the proposed sprint output to `./output.json`.\n";
|
|
5542
5495
|
if (phase === "plan")
|
|
5543
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";
|
|
5544
|
-
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";
|
|
5545
5498
|
}
|
|
5546
5499
|
function renderRepoLine(phase, affectedRepos, copilot) {
|
|
5547
5500
|
if (phase === "refine" || phase === "ideate")
|
|
@@ -5651,7 +5604,7 @@ async function mirrorRepo(src, dst) {
|
|
|
5651
5604
|
async function writeContextFile(args) {
|
|
5652
5605
|
const { path, needsGithubDir } = contextFileFor(args.provider);
|
|
5653
5606
|
if (needsGithubDir) {
|
|
5654
|
-
const ensure = await ensureDirSafe(
|
|
5607
|
+
const ensure = await ensureDirSafe(join10(args.root, ".github"));
|
|
5655
5608
|
if (!ensure.ok) return Result.error(ensure.error);
|
|
5656
5609
|
}
|
|
5657
5610
|
const body = renderContextFile({
|
|
@@ -5660,7 +5613,7 @@ async function writeContextFile(args) {
|
|
|
5660
5613
|
affectedRepos: args.affectedRepos,
|
|
5661
5614
|
copilot: args.provider === "copilot"
|
|
5662
5615
|
});
|
|
5663
|
-
return writeFileSafe2(
|
|
5616
|
+
return writeFileSafe2(join10(args.root, path), body);
|
|
5664
5617
|
}
|
|
5665
5618
|
|
|
5666
5619
|
// src/integration/persistence/execution-unit-builder.ts
|
|
@@ -5723,7 +5676,7 @@ function renderTaskInput(task) {
|
|
|
5723
5676
|
}
|
|
5724
5677
|
return lines.join("\n");
|
|
5725
5678
|
}
|
|
5726
|
-
function renderTasksList(tasks) {
|
|
5679
|
+
function renderTasksList(tasks, priorEvaluations) {
|
|
5727
5680
|
const ordered = [...tasks].sort((a, b) => a.order - b.order);
|
|
5728
5681
|
const lines = ["# Task plan", ""];
|
|
5729
5682
|
for (const t of ordered) {
|
|
@@ -5747,6 +5700,10 @@ function renderTasksList(tasks) {
|
|
|
5747
5700
|
for (const c34 of t.verificationCriteria) lines.push(`- ${c34}`);
|
|
5748
5701
|
lines.push("");
|
|
5749
5702
|
}
|
|
5703
|
+
const priorEval = priorEvaluations.get(t.id);
|
|
5704
|
+
if (priorEval !== void 0 && priorEval.length > 0) {
|
|
5705
|
+
lines.push("### Evaluator output", "", "```text", priorEval, "```", "");
|
|
5706
|
+
}
|
|
5750
5707
|
}
|
|
5751
5708
|
return lines.join("\n");
|
|
5752
5709
|
}
|
|
@@ -5777,7 +5734,7 @@ function renderDimensions(task) {
|
|
|
5777
5734
|
return lines.join("\n");
|
|
5778
5735
|
}
|
|
5779
5736
|
async function readProjectContext(repoPath, provider) {
|
|
5780
|
-
const target = provider === "claude" ?
|
|
5737
|
+
const target = provider === "claude" ? join11(repoPath, "CLAUDE.md") : join11(repoPath, ".github", "copilot-instructions.md");
|
|
5781
5738
|
try {
|
|
5782
5739
|
const body = await readFile6(target, "utf-8");
|
|
5783
5740
|
return [`<!-- copied from ${target} -->`, "", body].join("\n");
|
|
@@ -5792,59 +5749,20 @@ async function readProjectContext(repoPath, provider) {
|
|
|
5792
5749
|
].join("\n");
|
|
5793
5750
|
}
|
|
5794
5751
|
}
|
|
5795
|
-
function serialiseTasks(tasks) {
|
|
5796
|
-
return [...tasks].sort((a, b) => a.order - b.order).map((t) => ({
|
|
5797
|
-
id: t.id,
|
|
5798
|
-
name: t.name,
|
|
5799
|
-
description: t.description,
|
|
5800
|
-
steps: t.steps,
|
|
5801
|
-
verificationCriteria: t.verificationCriteria,
|
|
5802
|
-
status: t.status,
|
|
5803
|
-
order: t.order,
|
|
5804
|
-
ticketId: t.ticketId,
|
|
5805
|
-
blockedBy: t.blockedBy,
|
|
5806
|
-
projectPath: t.projectPath,
|
|
5807
|
-
extraDimensions: t.extraDimensions
|
|
5808
|
-
}));
|
|
5809
|
-
}
|
|
5810
5752
|
async function writeExecutionVolatile(args) {
|
|
5811
|
-
const taskMd = await writeFileSafe2(
|
|
5753
|
+
const taskMd = await writeFileSafe2(join11(args.root, "task.md"), renderTaskInput(args.task));
|
|
5812
5754
|
if (!taskMd.ok) return Result.error(taskMd.error);
|
|
5813
|
-
const tasksMd = await writeFileSafe2(
|
|
5755
|
+
const tasksMd = await writeFileSafe2(join11(args.root, "tasks.md"), renderTasksList(args.tasks, args.priorEvaluations));
|
|
5814
5756
|
if (!tasksMd.ok) return Result.error(tasksMd.error);
|
|
5815
|
-
const tasksJson = await writeFileSafe2(
|
|
5816
|
-
join12(args.root, "tasks.json"),
|
|
5817
|
-
JSON.stringify(serialiseTasks(args.tasks), null, 2)
|
|
5818
|
-
);
|
|
5819
|
-
if (!tasksJson.ok) return Result.error(tasksJson.error);
|
|
5820
5757
|
const projectContext = await readProjectContext(args.task.projectPath, args.aiProvider);
|
|
5821
|
-
const projectCtxFile = await writeFileSafe2(
|
|
5758
|
+
const projectCtxFile = await writeFileSafe2(join11(args.root, "project-context.md"), projectContext);
|
|
5822
5759
|
if (!projectCtxFile.ok) return Result.error(projectCtxFile.error);
|
|
5823
|
-
const evaluationsDir = join12(args.root, "evaluations");
|
|
5824
|
-
try {
|
|
5825
|
-
await rm4(evaluationsDir, { recursive: true, force: true });
|
|
5826
|
-
} catch (err) {
|
|
5827
|
-
return Result.error(
|
|
5828
|
-
new StorageError({
|
|
5829
|
-
subCode: "io",
|
|
5830
|
-
message: `failed to clear ${evaluationsDir}: ${err instanceof Error ? err.message : String(err)}`,
|
|
5831
|
-
path: evaluationsDir,
|
|
5832
|
-
cause: err
|
|
5833
|
-
})
|
|
5834
|
-
);
|
|
5835
|
-
}
|
|
5836
|
-
const ensureEvals = await ensureDirSafe(evaluationsDir);
|
|
5837
|
-
if (!ensureEvals.ok) return Result.error(ensureEvals.error);
|
|
5838
|
-
for (const [taskId, body] of args.priorEvaluations) {
|
|
5839
|
-
const w = await writeFileSafe2(join12(evaluationsDir, `${taskId}.md`), body);
|
|
5840
|
-
if (!w.ok) return Result.error(w.error);
|
|
5841
|
-
}
|
|
5842
5760
|
return Result.ok();
|
|
5843
5761
|
}
|
|
5844
5762
|
async function buildExecutionUnit(storage2, input) {
|
|
5845
5763
|
const slug = unitSlug(String(input.task.id), input.task.name);
|
|
5846
5764
|
const root = storage2.executionUnitDir(input.sprint.id, slug);
|
|
5847
|
-
const requirementsDir = AbsolutePath.trustString(
|
|
5765
|
+
const requirementsDir = AbsolutePath.trustString(join11(root, "requirements"));
|
|
5848
5766
|
const ensure = await ensureDirSafe(requirementsDir);
|
|
5849
5767
|
if (!ensure.ok) return Result.error(ensure.error);
|
|
5850
5768
|
const ctx = await writeContextFile({
|
|
@@ -5856,14 +5774,14 @@ async function buildExecutionUnit(storage2, input) {
|
|
|
5856
5774
|
});
|
|
5857
5775
|
if (!ctx.ok) return Result.error(ctx.error);
|
|
5858
5776
|
for (const ticket of input.sprint.tickets) {
|
|
5859
|
-
const filePath =
|
|
5777
|
+
const filePath = join11(requirementsDir, `${ticket.id}.md`);
|
|
5860
5778
|
const w = await writeFileSafe2(filePath, renderRequirementsInput(ticket));
|
|
5861
5779
|
if (!w.ok) return Result.error(w.error);
|
|
5862
5780
|
}
|
|
5863
|
-
const dims = await writeFileSafe2(
|
|
5781
|
+
const dims = await writeFileSafe2(join11(root, "dimensions.md"), renderDimensions(input.task));
|
|
5864
5782
|
if (!dims.ok) return Result.error(dims.error);
|
|
5865
5783
|
const sprintCriteriaPath = String(storage2.doneCriteriaFile(input.sprint.id));
|
|
5866
|
-
const unitCriteriaPath =
|
|
5784
|
+
const unitCriteriaPath = join11(root, "done-criteria.md");
|
|
5867
5785
|
const copied = await copyFileSafe(sprintCriteriaPath, unitCriteriaPath);
|
|
5868
5786
|
if (!copied.ok) {
|
|
5869
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`;
|
|
@@ -5883,7 +5801,7 @@ async function buildExecutionUnit(storage2, input) {
|
|
|
5883
5801
|
let addDirs;
|
|
5884
5802
|
let sessionCwd;
|
|
5885
5803
|
if (input.aiProvider === "copilot") {
|
|
5886
|
-
const repoMirror =
|
|
5804
|
+
const repoMirror = join11(root, "repo");
|
|
5887
5805
|
const m = await mirrorRepo(input.task.projectPath, repoMirror);
|
|
5888
5806
|
if (!m.ok) return Result.error(m.error);
|
|
5889
5807
|
addDirs = [];
|
|
@@ -5895,8 +5813,7 @@ async function buildExecutionUnit(storage2, input) {
|
|
|
5895
5813
|
return Result.ok({
|
|
5896
5814
|
root,
|
|
5897
5815
|
addDirs,
|
|
5898
|
-
sessionCwd
|
|
5899
|
-
evaluationMdPath: AbsolutePath.trustString(join12(root, "evaluation.md"))
|
|
5816
|
+
sessionCwd
|
|
5900
5817
|
});
|
|
5901
5818
|
}
|
|
5902
5819
|
async function refreshExecutionUnit(storage2, input) {
|
|
@@ -5913,7 +5830,7 @@ async function refreshExecutionUnit(storage2, input) {
|
|
|
5913
5830
|
}
|
|
5914
5831
|
|
|
5915
5832
|
// src/integration/persistence/ideate-unit-builder.ts
|
|
5916
|
-
import { join as
|
|
5833
|
+
import { join as join12 } from "path";
|
|
5917
5834
|
async function buildIdeationUnit(storage2, input) {
|
|
5918
5835
|
const slug = unitSlug(String(input.ticket.id), input.ticket.title);
|
|
5919
5836
|
const root = storage2.ideationUnitDir(input.sprint.id, slug);
|
|
@@ -5927,19 +5844,19 @@ async function buildIdeationUnit(storage2, input) {
|
|
|
5927
5844
|
affectedRepos: []
|
|
5928
5845
|
});
|
|
5929
5846
|
if (!ctx.ok) return Result.error(ctx.error);
|
|
5930
|
-
const ticketMdPath = AbsolutePath.trustString(
|
|
5847
|
+
const ticketMdPath = AbsolutePath.trustString(join12(root, "ticket.md"));
|
|
5931
5848
|
const wrote = await writeFileSafe2(ticketMdPath, renderTicketInput(input.ticket));
|
|
5932
5849
|
if (!wrote.ok) return Result.error(wrote.error);
|
|
5933
5850
|
return Result.ok({
|
|
5934
5851
|
root,
|
|
5935
|
-
sessionMdPath: AbsolutePath.trustString(
|
|
5852
|
+
sessionMdPath: AbsolutePath.trustString(join12(root, "session.md")),
|
|
5936
5853
|
ticketMdPath,
|
|
5937
|
-
outputJsonPath: AbsolutePath.trustString(
|
|
5854
|
+
outputJsonPath: AbsolutePath.trustString(join12(root, "output.json"))
|
|
5938
5855
|
});
|
|
5939
5856
|
}
|
|
5940
5857
|
|
|
5941
5858
|
// src/integration/persistence/planning-folder-builder.ts
|
|
5942
|
-
import { basename as basename3, join as
|
|
5859
|
+
import { basename as basename3, join as join13 } from "path";
|
|
5943
5860
|
|
|
5944
5861
|
// src/business/usecases/sprint/sprint-requirements-aggregate.ts
|
|
5945
5862
|
function buildSprintRequirementsAggregate(sprint, now = /* @__PURE__ */ new Date()) {
|
|
@@ -6015,7 +5932,7 @@ async function buildPlanningFolder(storage2, input) {
|
|
|
6015
5932
|
});
|
|
6016
5933
|
if (!ctx.ok) return Result.error(ctx.error);
|
|
6017
5934
|
const reqSrc = String(storage2.requirementsAggregateFile(input.sprint.id));
|
|
6018
|
-
const reqDst =
|
|
5935
|
+
const reqDst = join13(root, "requirements.json");
|
|
6019
5936
|
const copied = await copyFileSafe(reqSrc, reqDst);
|
|
6020
5937
|
if (!copied.ok) {
|
|
6021
5938
|
const inlineBody = serialiseSprintRequirementsAggregate(buildSprintRequirementsAggregate(input.sprint));
|
|
@@ -6024,11 +5941,11 @@ async function buildPlanningFolder(storage2, input) {
|
|
|
6024
5941
|
}
|
|
6025
5942
|
let addDirs;
|
|
6026
5943
|
if (isCopilot) {
|
|
6027
|
-
const reposDir =
|
|
5944
|
+
const reposDir = join13(root, "repos");
|
|
6028
5945
|
const ensureRepos = await ensureDirSafe(reposDir);
|
|
6029
5946
|
if (!ensureRepos.ok) return Result.error(ensureRepos.error);
|
|
6030
5947
|
for (const repoPath of input.sprint.affectedRepositories) {
|
|
6031
|
-
const dst =
|
|
5948
|
+
const dst = join13(reposDir, basename3(repoPath));
|
|
6032
5949
|
const m = await mirrorRepo(repoPath, dst);
|
|
6033
5950
|
if (!m.ok) return Result.error(m.error);
|
|
6034
5951
|
}
|
|
@@ -6038,14 +5955,14 @@ async function buildPlanningFolder(storage2, input) {
|
|
|
6038
5955
|
}
|
|
6039
5956
|
return Result.ok({
|
|
6040
5957
|
root,
|
|
6041
|
-
sessionMdPath: AbsolutePath.trustString(
|
|
6042
|
-
rawTasksJsonPath: AbsolutePath.trustString(
|
|
5958
|
+
sessionMdPath: AbsolutePath.trustString(join13(root, "session.md")),
|
|
5959
|
+
rawTasksJsonPath: AbsolutePath.trustString(join13(root, "tasks.json")),
|
|
6043
5960
|
addDirs
|
|
6044
5961
|
});
|
|
6045
5962
|
}
|
|
6046
5963
|
|
|
6047
5964
|
// src/integration/persistence/refine-unit-builder.ts
|
|
6048
|
-
import { join as
|
|
5965
|
+
import { join as join14 } from "path";
|
|
6049
5966
|
async function buildRefinementUnit(storage2, input) {
|
|
6050
5967
|
const slug = unitSlug(String(input.ticket.id), input.ticket.title);
|
|
6051
5968
|
const root = storage2.refinementUnitDir(input.sprint.id, slug);
|
|
@@ -6059,14 +5976,14 @@ async function buildRefinementUnit(storage2, input) {
|
|
|
6059
5976
|
affectedRepos: []
|
|
6060
5977
|
});
|
|
6061
5978
|
if (!ctx.ok) return Result.error(ctx.error);
|
|
6062
|
-
const ticketMdPath = AbsolutePath.trustString(
|
|
5979
|
+
const ticketMdPath = AbsolutePath.trustString(join14(root, "ticket.md"));
|
|
6063
5980
|
const wrote = await writeFileSafe2(ticketMdPath, renderTicketInput(input.ticket));
|
|
6064
5981
|
if (!wrote.ok) return Result.error(wrote.error);
|
|
6065
5982
|
return Result.ok({
|
|
6066
5983
|
root,
|
|
6067
|
-
sessionMdPath: AbsolutePath.trustString(
|
|
5984
|
+
sessionMdPath: AbsolutePath.trustString(join14(root, "session.md")),
|
|
6068
5985
|
ticketMdPath,
|
|
6069
|
-
requirementsJsonPath: AbsolutePath.trustString(
|
|
5986
|
+
requirementsJsonPath: AbsolutePath.trustString(join14(root, "requirements.json"))
|
|
6070
5987
|
});
|
|
6071
5988
|
}
|
|
6072
5989
|
|
|
@@ -6198,15 +6115,11 @@ var InMemorySignalBus = class {
|
|
|
6198
6115
|
};
|
|
6199
6116
|
|
|
6200
6117
|
// src/integration/signals/file-system-handler.ts
|
|
6201
|
-
import { appendFile as appendFile2, mkdir as mkdir11
|
|
6202
|
-
import { dirname as dirname9
|
|
6118
|
+
import { appendFile as appendFile2, mkdir as mkdir11 } from "fs/promises";
|
|
6119
|
+
import { dirname as dirname9 } from "path";
|
|
6203
6120
|
function progressPath(paths, sprintId) {
|
|
6204
6121
|
return paths.progressFile(sprintId);
|
|
6205
6122
|
}
|
|
6206
|
-
function evaluationPath(paths, sprintId, taskId, taskName) {
|
|
6207
|
-
const slug = unitSlug(taskId, taskName);
|
|
6208
|
-
return AbsolutePath.trustString(join16(paths.executionUnitDir(sprintId, slug), "evaluation.md"));
|
|
6209
|
-
}
|
|
6210
6123
|
async function appendLine(path, line) {
|
|
6211
6124
|
try {
|
|
6212
6125
|
await mkdir11(dirname9(path), { recursive: true });
|
|
@@ -6224,26 +6137,6 @@ async function appendLine(path, line) {
|
|
|
6224
6137
|
);
|
|
6225
6138
|
}
|
|
6226
6139
|
}
|
|
6227
|
-
async function writeText(path, body) {
|
|
6228
|
-
try {
|
|
6229
|
-
await mkdir11(dirname9(path), { recursive: true });
|
|
6230
|
-
await writeFile7(path, body.endsWith("\n") ? body : `${body}
|
|
6231
|
-
`, {
|
|
6232
|
-
encoding: "utf-8",
|
|
6233
|
-
mode: 384
|
|
6234
|
-
});
|
|
6235
|
-
return Result.ok();
|
|
6236
|
-
} catch (err) {
|
|
6237
|
-
return Result.error(
|
|
6238
|
-
new StorageError({
|
|
6239
|
-
subCode: "io",
|
|
6240
|
-
message: `failed to write ${path}: ${err instanceof Error ? err.message : String(err)}`,
|
|
6241
|
-
path,
|
|
6242
|
-
cause: err
|
|
6243
|
-
})
|
|
6244
|
-
);
|
|
6245
|
-
}
|
|
6246
|
-
}
|
|
6247
6140
|
var FileSystemSignalHandler = class {
|
|
6248
6141
|
constructor(paths, fileLocker = new FileLocker()) {
|
|
6249
6142
|
this.paths = paths;
|
|
@@ -6275,14 +6168,10 @@ var FileSystemSignalHandler = class {
|
|
|
6275
6168
|
return Result.error(
|
|
6276
6169
|
new StorageError({
|
|
6277
6170
|
subCode: "io",
|
|
6278
|
-
message: "evaluation signal requires taskName in meta
|
|
6171
|
+
message: "evaluation signal requires taskName in meta"
|
|
6279
6172
|
})
|
|
6280
6173
|
);
|
|
6281
6174
|
}
|
|
6282
|
-
const file = evaluationPath(this.paths, meta.sprintId, String(meta.taskId), meta.taskName);
|
|
6283
|
-
const body = renderEvaluationBody(signal);
|
|
6284
|
-
const w = await writeText(file, body);
|
|
6285
|
-
if (!w.ok) return w;
|
|
6286
6175
|
const scoreSuffix = signal.overallScore !== void 0 ? `, score ${String(signal.overallScore)}/5` : "";
|
|
6287
6176
|
return this.appendProgress(
|
|
6288
6177
|
meta.sprintId,
|
|
@@ -6321,33 +6210,6 @@ function formatProgress(timestamp, message, files) {
|
|
|
6321
6210
|
const filesSuffix = files !== void 0 && files.length > 0 ? ` (files: ${files.join(", ")})` : "";
|
|
6322
6211
|
return `- ${timestamp} \u2014 ${message}${filesSuffix}`;
|
|
6323
6212
|
}
|
|
6324
|
-
function renderEvaluationBody(signal) {
|
|
6325
|
-
const lines = [];
|
|
6326
|
-
lines.push(`# Evaluation \u2014 ${signal.status}`);
|
|
6327
|
-
lines.push("");
|
|
6328
|
-
lines.push(`Recorded: ${signal.timestamp}`);
|
|
6329
|
-
if (signal.overallScore !== void 0) {
|
|
6330
|
-
lines.push(`Overall score: ${String(signal.overallScore)}/5`);
|
|
6331
|
-
}
|
|
6332
|
-
lines.push("");
|
|
6333
|
-
if (signal.dimensions.length > 0) {
|
|
6334
|
-
lines.push("## Dimensions");
|
|
6335
|
-
lines.push("");
|
|
6336
|
-
for (const d of signal.dimensions) {
|
|
6337
|
-
const verdict = d.passed ? "PASS" : "FAIL";
|
|
6338
|
-
const scoreLabel = d.score !== void 0 ? ` (score ${String(d.score)}/5)` : "";
|
|
6339
|
-
lines.push(`- **${d.dimension}**${scoreLabel}: ${verdict} \u2014 ${d.finding}`);
|
|
6340
|
-
}
|
|
6341
|
-
lines.push("");
|
|
6342
|
-
}
|
|
6343
|
-
if (signal.critique !== void 0 && signal.critique.length > 0) {
|
|
6344
|
-
lines.push("## Critique");
|
|
6345
|
-
lines.push("");
|
|
6346
|
-
lines.push(signal.critique);
|
|
6347
|
-
lines.push("");
|
|
6348
|
-
}
|
|
6349
|
-
return lines.join("\n");
|
|
6350
|
-
}
|
|
6351
6213
|
|
|
6352
6214
|
// src/integration/signals/parser.ts
|
|
6353
6215
|
function buildPatterns() {
|
|
@@ -7357,18 +7219,18 @@ async function isFirstLaunch(deps) {
|
|
|
7357
7219
|
// src/application/runtime/legacy-detector.ts
|
|
7358
7220
|
import { access } from "fs/promises";
|
|
7359
7221
|
import { homedir } from "os";
|
|
7360
|
-
import { join as
|
|
7222
|
+
import { join as join15 } from "path";
|
|
7361
7223
|
var NOT_LEGACY = { isLegacy: false, legacyConfigPath: null, hint: "" };
|
|
7362
7224
|
function defaultRoot() {
|
|
7363
7225
|
const fromEnv = process.env["RALPHCTL_ROOT"];
|
|
7364
7226
|
if (fromEnv !== void 0 && fromEnv.length > 0) {
|
|
7365
7227
|
return AbsolutePath.trustString(fromEnv);
|
|
7366
7228
|
}
|
|
7367
|
-
return AbsolutePath.trustString(
|
|
7229
|
+
return AbsolutePath.trustString(join15(homedir(), ".ralphctl"));
|
|
7368
7230
|
}
|
|
7369
7231
|
async function detectLegacyLayout(deps = {}) {
|
|
7370
7232
|
const root = deps.root ?? defaultRoot();
|
|
7371
|
-
const legacyConfigPath = AbsolutePath.trustString(
|
|
7233
|
+
const legacyConfigPath = AbsolutePath.trustString(join15(root, "config.json"));
|
|
7372
7234
|
try {
|
|
7373
7235
|
await access(legacyConfigPath);
|
|
7374
7236
|
} catch {
|
|
@@ -7389,7 +7251,7 @@ Then re-run ralphctl to start fresh, and use 'ralphctl project add' to register
|
|
|
7389
7251
|
// src/integration/ai/dist-asset-manifest.ts
|
|
7390
7252
|
import { existsSync as existsSync4 } from "fs";
|
|
7391
7253
|
import { readFile as readFile7, stat as stat4 } from "fs/promises";
|
|
7392
|
-
import { dirname as dirname10, join as
|
|
7254
|
+
import { dirname as dirname10, join as join16 } from "path";
|
|
7393
7255
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7394
7256
|
var HERE3 = dirname10(fileURLToPath3(import.meta.url));
|
|
7395
7257
|
var cached2 = { state: "unverified" };
|
|
@@ -7397,7 +7259,7 @@ async function verifyDistAssets(distRootOverride) {
|
|
|
7397
7259
|
if (cached2.state === "pass") return Result.ok();
|
|
7398
7260
|
if (cached2.state === "fail") return Result.error(cached2.error);
|
|
7399
7261
|
const distRoot = distRootOverride ?? HERE3;
|
|
7400
|
-
const manifestPath =
|
|
7262
|
+
const manifestPath = join16(distRoot, "manifest.json");
|
|
7401
7263
|
if (!existsSync4(manifestPath)) {
|
|
7402
7264
|
cached2 = { state: "pass" };
|
|
7403
7265
|
return Result.ok();
|
|
@@ -7409,7 +7271,7 @@ async function verifyDistAssets(distRootOverride) {
|
|
|
7409
7271
|
}
|
|
7410
7272
|
const missing = [];
|
|
7411
7273
|
for (const rel of parsed.value.assets) {
|
|
7412
|
-
const abs =
|
|
7274
|
+
const abs = join16(distRoot, rel);
|
|
7413
7275
|
try {
|
|
7414
7276
|
const s = await stat4(abs);
|
|
7415
7277
|
if (!s.isFile()) missing.push(rel);
|
|
@@ -9683,7 +9545,22 @@ function unlinkSkillsLeaf(deps, opts = {}) {
|
|
|
9683
9545
|
}
|
|
9684
9546
|
|
|
9685
9547
|
// src/application/chains/execute/per-task-flow.ts
|
|
9686
|
-
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
|
+
}
|
|
9687
9564
|
|
|
9688
9565
|
// src/integration/persistence/done-criteria-reader.ts
|
|
9689
9566
|
import { readFile as readFile8 } from "fs/promises";
|
|
@@ -9737,7 +9614,7 @@ var BranchPreflightUseCase = class {
|
|
|
9737
9614
|
};
|
|
9738
9615
|
|
|
9739
9616
|
// src/business/usecases/evaluate/evaluate-and-fix-loop.ts
|
|
9740
|
-
import { join as
|
|
9617
|
+
import { join as join18 } from "path";
|
|
9741
9618
|
|
|
9742
9619
|
// src/business/usecases/_shared/add-dir-args.ts
|
|
9743
9620
|
function buildAdditionalCwdArgs(paths) {
|
|
@@ -9758,6 +9635,24 @@ function renderFileHandoffWrapper(promptFilePath) {
|
|
|
9758
9635
|
"Follow the protocol in that file exactly."
|
|
9759
9636
|
].join("\n");
|
|
9760
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
|
+
}
|
|
9761
9656
|
|
|
9762
9657
|
// src/business/usecases/evaluate/evaluate-task.ts
|
|
9763
9658
|
var MAX_MALFORMED_CRITIQUE_CHARS = 500;
|
|
@@ -9897,9 +9792,6 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9897
9792
|
history: []
|
|
9898
9793
|
});
|
|
9899
9794
|
}
|
|
9900
|
-
const evaluatorPromptPath = AbsolutePath.trustString(
|
|
9901
|
-
input.evaluateWorkspaceDir !== void 0 ? join19(input.evaluateWorkspaceDir, "evaluator-prompt.md") : join19(String(input.contextsDir), `evaluate-${String(input.task.id)}.md`)
|
|
9902
|
-
);
|
|
9903
9795
|
const history = [];
|
|
9904
9796
|
let previousSignal;
|
|
9905
9797
|
let previousCritique;
|
|
@@ -9923,18 +9815,21 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9923
9815
|
});
|
|
9924
9816
|
}
|
|
9925
9817
|
}
|
|
9818
|
+
const evaluatorPromptPath = AbsolutePath.trustString(
|
|
9819
|
+
join18(evaluatorRoundDir(input.evaluateWorkspaceDir, round), "prompt.md")
|
|
9820
|
+
);
|
|
9926
9821
|
const evalPromptResult = await this.prompts.buildEvaluatePrompt({
|
|
9927
9822
|
task: input.task,
|
|
9928
9823
|
sprint: input.sprint,
|
|
9824
|
+
evaluateWorkspaceDir: input.evaluateWorkspaceDir,
|
|
9929
9825
|
...previousCritique !== void 0 ? { previousCritique } : {},
|
|
9930
|
-
...input.evaluateWorkspaceDir !== void 0 ? { evaluateWorkspaceDir: input.evaluateWorkspaceDir } : {},
|
|
9931
9826
|
...input.doneCriteriaBullet !== void 0 ? { doneCriteriaBullet: input.doneCriteriaBullet } : {}
|
|
9932
9827
|
});
|
|
9933
9828
|
if (!evalPromptResult.ok) return Result.error(evalPromptResult.error);
|
|
9934
9829
|
const written = await this.writeContextFile.write(evaluatorPromptPath, evalPromptResult.value);
|
|
9935
9830
|
if (!written.ok) return Result.error(written.error);
|
|
9936
9831
|
const evaluatorCwd = input.evaluateSessionCwd ?? input.cwd;
|
|
9937
|
-
const evaluatorSessionMdPath = input.nextSessionMdPath ? await input.nextSessionMdPath("evaluator") : void 0;
|
|
9832
|
+
const evaluatorSessionMdPath = input.nextSessionMdPath ? await input.nextSessionMdPath("evaluator", round) : void 0;
|
|
9938
9833
|
const evalResult = await this.evaluator.execute({
|
|
9939
9834
|
task: input.task,
|
|
9940
9835
|
sprint: input.sprint,
|
|
@@ -9947,6 +9842,16 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9947
9842
|
if (!evalResult.ok) return Result.error(evalResult.error);
|
|
9948
9843
|
const { outcome, signal, fullCritique } = evalResult.value;
|
|
9949
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
|
+
}
|
|
9950
9855
|
log.info(`evaluator round complete for task ${String(input.task.id)}`, { round, outcome });
|
|
9951
9856
|
if (outcome === "passed") break;
|
|
9952
9857
|
if (outcome === "malformed") {
|
|
@@ -9972,7 +9877,8 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9972
9877
|
break;
|
|
9973
9878
|
}
|
|
9974
9879
|
log.info("resuming generator with critique", { round });
|
|
9975
|
-
const
|
|
9880
|
+
const fixRound = round + 1;
|
|
9881
|
+
const generatorSessionMdPath = input.nextSessionMdPath ? await input.nextSessionMdPath("generator", fixRound) : void 0;
|
|
9976
9882
|
const fixResult = await this.generator.execute({
|
|
9977
9883
|
task: input.task,
|
|
9978
9884
|
sprint: input.sprint,
|
|
@@ -9980,6 +9886,7 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9980
9886
|
promptFilePath: input.executePromptFilePath,
|
|
9981
9887
|
...resumeSessionId !== void 0 ? { resumeSessionId } : {},
|
|
9982
9888
|
...generatorSessionMdPath !== void 0 ? { sessionMdPath: generatorSessionMdPath } : {},
|
|
9889
|
+
fixContext: { critique: fullCritique },
|
|
9983
9890
|
...input.abortSignal !== void 0 ? { abortSignal: input.abortSignal } : {}
|
|
9984
9891
|
});
|
|
9985
9892
|
if (!fixResult.ok) return Result.error(fixResult.error);
|
|
@@ -9991,8 +9898,8 @@ var EvaluateAndFixLoopUseCase = class {
|
|
|
9991
9898
|
projectPath: input.cwd,
|
|
9992
9899
|
checkScript: input.checkScript
|
|
9993
9900
|
});
|
|
9994
|
-
if (!checkResult.ok)
|
|
9995
|
-
|
|
9901
|
+
if (!checkResult.ok) {
|
|
9902
|
+
if (checkResult.error.code !== "check-failed") return Result.error(checkResult.error);
|
|
9996
9903
|
log.warn("post-task check failed after fix attempt \u2014 re-evaluating anyway", { round });
|
|
9997
9904
|
}
|
|
9998
9905
|
}
|
|
@@ -10039,7 +9946,7 @@ var ExecuteSingleTaskUseCase = class {
|
|
|
10039
9946
|
taskId: input.task.id,
|
|
10040
9947
|
projectPath: input.cwd
|
|
10041
9948
|
});
|
|
10042
|
-
const wrapper = renderFileHandoffWrapper(input.promptFilePath);
|
|
9949
|
+
const wrapper = input.fixContext !== void 0 ? renderFixHandoffWrapper(input.promptFilePath, input.fixContext.critique) : renderFileHandoffWrapper(input.promptFilePath);
|
|
10043
9950
|
log.info(`executing task ${String(input.task.id)}${formatNameSuffix2(input.task.name)}`);
|
|
10044
9951
|
const sessionOptions = {
|
|
10045
9952
|
cwd: input.cwd,
|
|
@@ -10109,6 +10016,22 @@ function formatNameSuffix2(name) {
|
|
|
10109
10016
|
return ` \u2014 "${slice}"`;
|
|
10110
10017
|
}
|
|
10111
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
|
+
|
|
10112
10035
|
// src/business/usecases/execute/post-task-check.ts
|
|
10113
10036
|
var PostTaskCheckUseCase = class {
|
|
10114
10037
|
constructor(external, logger) {
|
|
@@ -10131,10 +10054,11 @@ var PostTaskCheckUseCase = class {
|
|
|
10131
10054
|
input.timeoutMs
|
|
10132
10055
|
);
|
|
10133
10056
|
if (!result.passed) {
|
|
10134
|
-
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 }));
|
|
10135
10059
|
}
|
|
10136
10060
|
return Result.ok({
|
|
10137
|
-
passed:
|
|
10061
|
+
passed: true,
|
|
10138
10062
|
output: result.output,
|
|
10139
10063
|
skipped: false
|
|
10140
10064
|
});
|
|
@@ -10292,8 +10216,7 @@ function buildExecutionUnitLeaf(deps, opts = {}) {
|
|
|
10292
10216
|
...ctx,
|
|
10293
10217
|
executionUnitRoot: out.root,
|
|
10294
10218
|
executionAddDirs: out.addDirs,
|
|
10295
|
-
executionSessionCwd: out.sessionCwd
|
|
10296
|
-
executionEvaluationMdPath: out.evaluationMdPath
|
|
10219
|
+
executionSessionCwd: out.sessionCwd
|
|
10297
10220
|
})
|
|
10298
10221
|
});
|
|
10299
10222
|
}
|
|
@@ -10307,8 +10230,21 @@ function collectPriorEvaluations(tasks) {
|
|
|
10307
10230
|
return map;
|
|
10308
10231
|
}
|
|
10309
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
|
+
|
|
10310
10246
|
// src/application/chains/leaves/render-prompt-to-file.ts
|
|
10311
|
-
import { join as
|
|
10247
|
+
import { join as join19 } from "path";
|
|
10312
10248
|
function renderPromptToFileLeaf(deps, opts) {
|
|
10313
10249
|
return new Leaf("render-prompt-to-file", {
|
|
10314
10250
|
useCase: {
|
|
@@ -10338,7 +10274,7 @@ function defaultPromptPath(ctx, opts) {
|
|
|
10338
10274
|
const sprintDir = storagePaths.sprintDir(ctx.sprintId);
|
|
10339
10275
|
const id = opts.identifier(ctx);
|
|
10340
10276
|
const basename4 = id.length > 0 ? `${opts.flowName}-${id}.md` : `${opts.flowName}.md`;
|
|
10341
|
-
return AbsolutePath.trustString(
|
|
10277
|
+
return AbsolutePath.trustString(join19(sprintDir, "contexts", basename4));
|
|
10342
10278
|
}
|
|
10343
10279
|
|
|
10344
10280
|
// src/application/chains/execute/per-task-flow.ts
|
|
@@ -10385,18 +10321,30 @@ function createPerTaskFlow(deps, opts) {
|
|
|
10385
10321
|
catchIf: (err) => err.code === "aborted",
|
|
10386
10322
|
fallback: markCancelledFallbackLeaf(deps)
|
|
10387
10323
|
});
|
|
10388
|
-
const buildEvalWorkspaceStep =
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
new Sequential("evaluate", [buildEvalWorkspaceStep, evaluateLoopLeaf(deps, evaluateLoop)]),
|
|
10324
|
+
const buildEvalWorkspaceStep = new OnError(
|
|
10325
|
+
buildExecutionUnitLeaf({
|
|
10326
|
+
sessionFolderBuilder: deps.sessionFolderBuilder,
|
|
10327
|
+
aiSession: deps.aiSession
|
|
10328
|
+
}),
|
|
10394
10329
|
{
|
|
10395
10330
|
catchIf: (err) => err.code !== "aborted",
|
|
10396
|
-
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")
|
|
10397
10346
|
}
|
|
10398
10347
|
);
|
|
10399
|
-
const postTaskStep = postTaskCheckLeaf(postCheck);
|
|
10400
10348
|
const renderPromptStep = renderPromptToFileLeaf(
|
|
10401
10349
|
{ writeContextFile: deps.writeContextFile },
|
|
10402
10350
|
{
|
|
@@ -10405,7 +10353,7 @@ function createPerTaskFlow(deps, opts) {
|
|
|
10405
10353
|
path: (ctx) => {
|
|
10406
10354
|
const slug = unitSlug(String(ctx.task.id), ctx.task.name);
|
|
10407
10355
|
const root = resolveStoragePaths().executionUnitDir(ctx.sprintId, slug);
|
|
10408
|
-
return AbsolutePath.trustString(
|
|
10356
|
+
return AbsolutePath.trustString(join20(String(root), "prompt.md"));
|
|
10409
10357
|
},
|
|
10410
10358
|
buildPrompt: (ctx) => deps.prompts.buildExecutePrompt({
|
|
10411
10359
|
task: ctx.task,
|
|
@@ -10418,9 +10366,10 @@ function createPerTaskFlow(deps, opts) {
|
|
|
10418
10366
|
branchPreflightStep,
|
|
10419
10367
|
markInProgressLeaf(deps),
|
|
10420
10368
|
renderPromptStep,
|
|
10369
|
+
buildEvalWorkspaceStep,
|
|
10421
10370
|
executeTaskStep,
|
|
10422
10371
|
postTaskStep,
|
|
10423
|
-
|
|
10372
|
+
evaluateLoopStep,
|
|
10424
10373
|
commitTaskLeaf(deps),
|
|
10425
10374
|
markDoneLeaf(deps)
|
|
10426
10375
|
]);
|
|
@@ -10463,6 +10412,22 @@ function markBlockedFallbackLeaf(deps) {
|
|
|
10463
10412
|
output: (ctx, task) => ({ ...ctx, task, taskBlocked: true })
|
|
10464
10413
|
});
|
|
10465
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
|
+
}
|
|
10466
10431
|
function markCancelledFallbackLeaf(deps) {
|
|
10467
10432
|
return new Leaf("mark-cancelled", {
|
|
10468
10433
|
useCase: {
|
|
@@ -10498,6 +10463,7 @@ function markInProgressLeaf(deps) {
|
|
|
10498
10463
|
});
|
|
10499
10464
|
}
|
|
10500
10465
|
function executeTaskLeaf(useCase) {
|
|
10466
|
+
let attempt = 0;
|
|
10501
10467
|
return new Leaf("execute-task", {
|
|
10502
10468
|
useCase: {
|
|
10503
10469
|
async execute(input) {
|
|
@@ -10510,7 +10476,9 @@ function executeTaskLeaf(useCase) {
|
|
|
10510
10476
|
message: "execute-task: promptFilePath is missing \u2014 render-prompt-to-file must run first"
|
|
10511
10477
|
});
|
|
10512
10478
|
}
|
|
10513
|
-
|
|
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;
|
|
10514
10482
|
const result = await useCase.execute({
|
|
10515
10483
|
sprint: input.sprint,
|
|
10516
10484
|
task: input.task,
|
|
@@ -10618,19 +10586,21 @@ function evaluateLoopLeaf(deps, loop) {
|
|
|
10618
10586
|
if (input.taskBlocked) {
|
|
10619
10587
|
return Result.ok({ task: input.task });
|
|
10620
10588
|
}
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
) : "";
|
|
10589
|
+
if (input.executionUnitRoot === void 0) {
|
|
10590
|
+
return Result.ok({ task: input.task });
|
|
10591
|
+
}
|
|
10625
10592
|
if (input.promptFilePath === void 0) {
|
|
10626
10593
|
return Result.error({
|
|
10627
10594
|
code: "invalid-state",
|
|
10628
10595
|
message: "evaluate-task: promptFilePath is missing \u2014 render-prompt-to-file must run first"
|
|
10629
10596
|
});
|
|
10630
10597
|
}
|
|
10631
|
-
const
|
|
10632
|
-
const
|
|
10633
|
-
|
|
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 () => {
|
|
10634
10604
|
await deps.aiSession.ensureReady();
|
|
10635
10605
|
const aiProvider = deps.aiSession.getProviderName();
|
|
10636
10606
|
const priorEvaluations = collectPriorEvaluations2(input.tasks);
|
|
@@ -10641,30 +10611,34 @@ function evaluateLoopLeaf(deps, loop) {
|
|
|
10641
10611
|
aiProvider,
|
|
10642
10612
|
priorEvaluations
|
|
10643
10613
|
});
|
|
10644
|
-
}
|
|
10645
|
-
const
|
|
10646
|
-
|
|
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
|
+
};
|
|
10647
10619
|
const result = await loop.execute({
|
|
10648
10620
|
task: input.task,
|
|
10649
10621
|
sprint: input.sprint,
|
|
10650
10622
|
cwd: input.cwd,
|
|
10651
10623
|
executePromptFilePath: String(input.promptFilePath),
|
|
10652
|
-
|
|
10624
|
+
evaluateWorkspaceDir: String(unitRoot),
|
|
10625
|
+
refreshWorkspace,
|
|
10626
|
+
nextSessionMdPath,
|
|
10653
10627
|
...input.checkScript !== void 0 ? { checkScript: input.checkScript } : {},
|
|
10654
10628
|
...input.resumeSessionId !== void 0 ? { resumeSessionId: input.resumeSessionId } : {},
|
|
10655
10629
|
...input.executionAddDirs !== void 0 ? { addDirs: input.executionAddDirs } : {},
|
|
10656
10630
|
...input.executionSessionCwd !== void 0 ? { evaluateSessionCwd: input.executionSessionCwd } : {},
|
|
10657
|
-
...input.executionUnitRoot !== void 0 ? { evaluateWorkspaceDir: String(input.executionUnitRoot) } : {},
|
|
10658
|
-
...refreshWorkspace !== void 0 ? { refreshWorkspace } : {},
|
|
10659
|
-
...nextSessionMdPath !== void 0 ? { nextSessionMdPath } : {},
|
|
10660
10631
|
...doneCriteriaBullet.length > 0 ? { doneCriteriaBullet } : {}
|
|
10661
10632
|
});
|
|
10662
10633
|
if (!result.ok) return Result.error(result.error);
|
|
10663
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);
|
|
10664
10638
|
const recorded = input.task.recordEvaluation({
|
|
10665
10639
|
status: result.value.finalSignal.status,
|
|
10666
10640
|
output: result.value.finalCritique.slice(0, MAX_PREVIEW_CHARS),
|
|
10667
|
-
file
|
|
10641
|
+
file
|
|
10668
10642
|
});
|
|
10669
10643
|
const saved = await deps.taskRepo.update(input.sprintId, recorded);
|
|
10670
10644
|
if (!saved.ok) return Result.error(saved.error);
|
|
@@ -10685,8 +10659,7 @@ function evaluateLoopLeaf(deps, loop) {
|
|
|
10685
10659
|
taskBlocked: ctx.taskBlocked === true,
|
|
10686
10660
|
...ctx.executionUnitRoot !== void 0 ? { executionUnitRoot: ctx.executionUnitRoot } : {},
|
|
10687
10661
|
...ctx.executionAddDirs !== void 0 ? { executionAddDirs: ctx.executionAddDirs } : {},
|
|
10688
|
-
...ctx.executionSessionCwd !== void 0 ? { executionSessionCwd: ctx.executionSessionCwd } : {}
|
|
10689
|
-
...ctx.executionEvaluationMdPath !== void 0 ? { executionEvaluationMdPath: ctx.executionEvaluationMdPath } : {}
|
|
10662
|
+
...ctx.executionSessionCwd !== void 0 ? { executionSessionCwd: ctx.executionSessionCwd } : {}
|
|
10690
10663
|
}),
|
|
10691
10664
|
// Push the recorded task back onto the context so the downstream
|
|
10692
10665
|
// `mark-done` leaf carries the evaluation forward when it flips
|
|
@@ -10751,17 +10724,6 @@ function commitTaskLeaf(deps) {
|
|
|
10751
10724
|
output: (ctx, task) => ({ ...ctx, task })
|
|
10752
10725
|
});
|
|
10753
10726
|
}
|
|
10754
|
-
function noopLeaf(name) {
|
|
10755
|
-
return new Leaf(name, {
|
|
10756
|
-
useCase: {
|
|
10757
|
-
execute(input) {
|
|
10758
|
-
return Promise.resolve(Result.ok(input));
|
|
10759
|
-
}
|
|
10760
|
-
},
|
|
10761
|
-
input: (ctx) => ctx,
|
|
10762
|
-
output: (_, ctx) => ctx
|
|
10763
|
-
});
|
|
10764
|
-
}
|
|
10765
10727
|
|
|
10766
10728
|
// src/application/chains/execute/execute-flow.ts
|
|
10767
10729
|
function createExecuteFlow(deps, opts) {
|
|
@@ -10784,7 +10746,8 @@ function createExecuteFlow(deps, opts) {
|
|
|
10784
10746
|
const initializeStep = new Sequential("initialize", [
|
|
10785
10747
|
resolveBranchLeaf(deps),
|
|
10786
10748
|
dirtyTreePreflightLeaf(deps, opts),
|
|
10787
|
-
|
|
10749
|
+
resolveCheckScriptsLeaf(deps),
|
|
10750
|
+
setupScriptsSprintStartLeaf(deps)
|
|
10788
10751
|
]);
|
|
10789
10752
|
return new Sequential("execute", [
|
|
10790
10753
|
loadSprintLeaf({ sprintRepo: deps.sprintRepo }),
|
|
@@ -10974,14 +10937,16 @@ function bridgePerTaskChain(task, inner, opts, taskRepo) {
|
|
|
10974
10937
|
async execute(input) {
|
|
10975
10938
|
const fresh = await taskRepo.findBySprintId(input.sprintId);
|
|
10976
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);
|
|
10977
10942
|
const innerCtx = {
|
|
10978
10943
|
sprintId: input.sprintId,
|
|
10979
|
-
sprint:
|
|
10944
|
+
sprint: liveSprint,
|
|
10980
10945
|
task,
|
|
10981
10946
|
tasks: liveTasks,
|
|
10982
10947
|
cwd: task.projectPath,
|
|
10983
10948
|
expectedBranch: input.expectedBranch,
|
|
10984
|
-
...
|
|
10949
|
+
...resolvedCheckScript !== void 0 ? { checkScript: resolvedCheckScript } : {},
|
|
10985
10950
|
...input.noCommit === true ? { noCommit: true } : {}
|
|
10986
10951
|
};
|
|
10987
10952
|
const innerResult = await inner.execute(innerCtx);
|
|
@@ -11083,33 +11048,118 @@ function assertTasksAcyclicLeaf(sortResult) {
|
|
|
11083
11048
|
output: (ctx) => ctx
|
|
11084
11049
|
});
|
|
11085
11050
|
}
|
|
11086
|
-
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) {
|
|
11087
11086
|
return new Leaf(
|
|
11088
|
-
"
|
|
11087
|
+
"setup-scripts-sprint-start",
|
|
11089
11088
|
{
|
|
11090
11089
|
useCase: {
|
|
11091
11090
|
async execute(input) {
|
|
11092
|
-
|
|
11093
|
-
|
|
11091
|
+
const repoPaths = input.sprint.affectedRepositories;
|
|
11092
|
+
if (repoPaths.length === 0) {
|
|
11093
|
+
return Result.ok(void 0);
|
|
11094
11094
|
}
|
|
11095
|
-
const
|
|
11096
|
-
if (!
|
|
11097
|
-
|
|
11098
|
-
|
|
11099
|
-
|
|
11100
|
-
currentState: "check-failed",
|
|
11101
|
-
attemptedAction: "execute",
|
|
11102
|
-
message: "sprint-start check script failed"
|
|
11103
|
-
})
|
|
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 }
|
|
11104
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
|
+
}
|
|
11105
11155
|
}
|
|
11106
11156
|
return Result.ok(void 0);
|
|
11107
11157
|
}
|
|
11108
11158
|
},
|
|
11109
|
-
input: (ctx) =>
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
}
|
|
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
|
+
},
|
|
11113
11163
|
output: (ctx) => ctx
|
|
11114
11164
|
}
|
|
11115
11165
|
);
|
|
@@ -11206,10 +11256,10 @@ function applyFeedbackLeaf(useCase) {
|
|
|
11206
11256
|
});
|
|
11207
11257
|
}
|
|
11208
11258
|
const { resolveStoragePaths: resolveStoragePaths2 } = await import("./storage-paths-IPNZZM5D.mjs");
|
|
11209
|
-
const { join:
|
|
11259
|
+
const { join: join34 } = await import("path");
|
|
11210
11260
|
const sprintDir = resolveStoragePaths2().sprintDir(input.sprintId);
|
|
11211
11261
|
const { AbsolutePath: APV } = await import("./absolute-path-WUTZQ37D.mjs");
|
|
11212
|
-
const sessionMdPath = APV.trustString(
|
|
11262
|
+
const sessionMdPath = APV.trustString(join34(sprintDir, "feedback", `session-${String(input.iteration)}.md`));
|
|
11213
11263
|
const result = await useCase.execute({
|
|
11214
11264
|
sprint: input.sprint,
|
|
11215
11265
|
promptFilePath: String(input.promptFilePath),
|
|
@@ -11246,7 +11296,7 @@ function recordFeedbackIterationLeaf(logger) {
|
|
|
11246
11296
|
try {
|
|
11247
11297
|
const { resolveStoragePaths: resolveStoragePaths2 } = await import("./storage-paths-IPNZZM5D.mjs");
|
|
11248
11298
|
const { mkdir: mkdir16, appendFile: appendFile4 } = await import("fs/promises");
|
|
11249
|
-
const { dirname:
|
|
11299
|
+
const { dirname: dirname16 } = await import("path");
|
|
11250
11300
|
const path = resolveStoragePaths2().feedbackFile(input.sprintId);
|
|
11251
11301
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11252
11302
|
const block = `
|
|
@@ -11256,7 +11306,7 @@ function recordFeedbackIterationLeaf(logger) {
|
|
|
11256
11306
|
|
|
11257
11307
|
${input.feedbackText.trim()}
|
|
11258
11308
|
`;
|
|
11259
|
-
await mkdir16(
|
|
11309
|
+
await mkdir16(dirname16(path), { recursive: true });
|
|
11260
11310
|
await appendFile4(path, block, "utf-8");
|
|
11261
11311
|
} catch (err) {
|
|
11262
11312
|
logger.warn("feedback: failed to append to feedback.md", {
|
|
@@ -11718,8 +11768,8 @@ function saveSprintLeaf(deps, name = "save-sprint") {
|
|
|
11718
11768
|
}
|
|
11719
11769
|
|
|
11720
11770
|
// src/application/chains/leaves/save-tasks.ts
|
|
11721
|
-
import { writeFile as
|
|
11722
|
-
import { dirname as
|
|
11771
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
11772
|
+
import { dirname as dirname11 } from "path";
|
|
11723
11773
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
11724
11774
|
var FALLBACK_CRITERION = "(no explicit criteria \u2014 use task description as proxy)";
|
|
11725
11775
|
function renderDoneCriteria(tasks) {
|
|
@@ -11745,9 +11795,9 @@ function saveTasksLeaf(deps, name = "save-tasks") {
|
|
|
11745
11795
|
const storage2 = resolveStoragePaths();
|
|
11746
11796
|
const criteriaPath = String(storage2.doneCriteriaFile(input.sprintId));
|
|
11747
11797
|
try {
|
|
11748
|
-
await mkdir12(
|
|
11798
|
+
await mkdir12(dirname11(criteriaPath), { recursive: true });
|
|
11749
11799
|
const body = renderDoneCriteria(input.tasks);
|
|
11750
|
-
await
|
|
11800
|
+
await writeFile7(criteriaPath, body, { encoding: "utf-8", mode: 384 });
|
|
11751
11801
|
} catch (err) {
|
|
11752
11802
|
return Result.error(
|
|
11753
11803
|
new StorageError({
|
|
@@ -11850,11 +11900,11 @@ function ideateAndPlanLeaf(useCase) {
|
|
|
11850
11900
|
|
|
11851
11901
|
// src/application/chains/onboard/onboard-load-leaves.ts
|
|
11852
11902
|
import { readFile as readFile9 } from "fs/promises";
|
|
11853
|
-
import { join as
|
|
11903
|
+
import { join as join22 } from "path";
|
|
11854
11904
|
|
|
11855
11905
|
// src/application/chains/onboard/onboard-persist-leaves.ts
|
|
11856
|
-
import { mkdir as mkdir13, writeFile as
|
|
11857
|
-
import { dirname as
|
|
11906
|
+
import { mkdir as mkdir13, writeFile as writeFile8 } from "fs/promises";
|
|
11907
|
+
import { dirname as dirname12, join as join21 } from "path";
|
|
11858
11908
|
var HARNESS_MARKER_PREFIX = "<!-- ralphctl onboard:";
|
|
11859
11909
|
function makeMarker(now = () => /* @__PURE__ */ new Date()) {
|
|
11860
11910
|
return `${HARNESS_MARKER_PREFIX} ${now().toISOString()} -->`;
|
|
@@ -11867,14 +11917,14 @@ function writeContextFileLeaf(now = () => /* @__PURE__ */ new Date()) {
|
|
|
11867
11917
|
if (proposals === void 0 || accepted === null || accepted === void 0 || accepted.length === 0) {
|
|
11868
11918
|
return Result.ok(void 0);
|
|
11869
11919
|
}
|
|
11870
|
-
const targetPath =
|
|
11920
|
+
const targetPath = join21(repo.path, proposals.contextFilePath);
|
|
11871
11921
|
try {
|
|
11872
|
-
await mkdir13(
|
|
11922
|
+
await mkdir13(dirname12(targetPath), { recursive: true });
|
|
11873
11923
|
const marker = makeMarker(now);
|
|
11874
11924
|
const firstLine = accepted.split("\n", 1)[0] ?? "";
|
|
11875
11925
|
const body = firstLine.startsWith(HARNESS_MARKER_PREFIX) ? accepted : `${marker}
|
|
11876
11926
|
${accepted}`;
|
|
11877
|
-
await
|
|
11927
|
+
await writeFile8(targetPath, body, "utf-8");
|
|
11878
11928
|
return Result.ok(void 0);
|
|
11879
11929
|
} catch (err) {
|
|
11880
11930
|
return Result.error(
|
|
@@ -11946,7 +11996,7 @@ var PRE_EXISTING_CONTEXT_FILES = ["CLAUDE.md", ".github/copilot-instructions.md"
|
|
|
11946
11996
|
async function findExistingContextFiles(repoPath) {
|
|
11947
11997
|
const found = [];
|
|
11948
11998
|
for (const relPath of PRE_EXISTING_CONTEXT_FILES) {
|
|
11949
|
-
const fullPath =
|
|
11999
|
+
const fullPath = join22(repoPath, relPath);
|
|
11950
12000
|
let body;
|
|
11951
12001
|
try {
|
|
11952
12002
|
body = await readFile9(fullPath, "utf-8");
|
|
@@ -12066,7 +12116,7 @@ function detectExistingFilesLeaf(deps) {
|
|
|
12066
12116
|
|
|
12067
12117
|
// src/application/chains/onboard/onboard-ai-leaves.ts
|
|
12068
12118
|
import { readFile as readFile10 } from "fs/promises";
|
|
12069
|
-
import { join as
|
|
12119
|
+
import { join as join23 } from "path";
|
|
12070
12120
|
|
|
12071
12121
|
// src/business/usecases/onboard/onboard-repo.ts
|
|
12072
12122
|
function contextFilePathFor(provider) {
|
|
@@ -12223,7 +12273,7 @@ function runOnboardAiLeaf(deps) {
|
|
|
12223
12273
|
await deps.aiSession.ensureReady();
|
|
12224
12274
|
const provider = deps.aiSession.getProviderName();
|
|
12225
12275
|
const fileName = contextFilePathFor(provider);
|
|
12226
|
-
const targetPath =
|
|
12276
|
+
const targetPath = join23(input.repo.path, fileName);
|
|
12227
12277
|
const detected = await detectModeAndBody(targetPath);
|
|
12228
12278
|
const result = await useCase.execute({
|
|
12229
12279
|
project: input.project,
|
|
@@ -12380,11 +12430,11 @@ function createOnboardFlow(deps, opts) {
|
|
|
12380
12430
|
|
|
12381
12431
|
// src/application/chains/plan/plan-flow.ts
|
|
12382
12432
|
import { mkdir as mkdir14 } from "fs/promises";
|
|
12383
|
-
import { dirname as
|
|
12433
|
+
import { dirname as dirname14, join as join24 } from "path";
|
|
12384
12434
|
|
|
12385
12435
|
// src/business/usecases/plan/plan-sprint-tasks.ts
|
|
12386
12436
|
import { readFile as readFile11 } from "fs/promises";
|
|
12387
|
-
import { dirname as
|
|
12437
|
+
import { dirname as dirname13 } from "path";
|
|
12388
12438
|
var PlanSprintTasksUseCase = class {
|
|
12389
12439
|
constructor(ai, logger) {
|
|
12390
12440
|
this.ai = ai;
|
|
@@ -12456,8 +12506,8 @@ var PlanSprintTasksUseCase = class {
|
|
|
12456
12506
|
// the whole spec before the user sees any response.
|
|
12457
12507
|
async runInteractive(input, wrapper, log) {
|
|
12458
12508
|
const handover = input.runInTerminal ?? (async (fn) => fn());
|
|
12459
|
-
const promptDir =
|
|
12460
|
-
const outputDir = input.outputFilePath !== void 0 ?
|
|
12509
|
+
const promptDir = dirname13(input.promptFilePath);
|
|
12510
|
+
const outputDir = input.outputFilePath !== void 0 ? dirname13(input.outputFilePath) : promptDir;
|
|
12461
12511
|
const repoArgs = buildAdditionalCwdArgs(input.additionalRepoPaths);
|
|
12462
12512
|
const extraArgs = [...repoArgs, "--add-dir", promptDir];
|
|
12463
12513
|
if (outputDir !== promptDir) {
|
|
@@ -12628,7 +12678,7 @@ function createPlanFlow(deps, opts) {
|
|
|
12628
12678
|
if (!ctx.planningFolderRoot) {
|
|
12629
12679
|
throw new Error("render-prompt-to-file: ctx.planningFolderRoot must be set by build-planning-folder");
|
|
12630
12680
|
}
|
|
12631
|
-
return AbsolutePath.trustString(
|
|
12681
|
+
return AbsolutePath.trustString(join24(String(ctx.planningFolderRoot), "prompt.md"));
|
|
12632
12682
|
},
|
|
12633
12683
|
buildPrompt: (ctx) => {
|
|
12634
12684
|
if (!ctx.sprint) {
|
|
@@ -12750,7 +12800,7 @@ function planTasksLeaf(useCase, opts) {
|
|
|
12750
12800
|
useCase: {
|
|
12751
12801
|
async execute(input) {
|
|
12752
12802
|
if (opts.outputFilePath !== void 0 && opts.outputFilePath !== "") {
|
|
12753
|
-
await mkdir14(
|
|
12803
|
+
await mkdir14(dirname14(opts.outputFilePath), { recursive: true });
|
|
12754
12804
|
}
|
|
12755
12805
|
const result = await useCase.execute({
|
|
12756
12806
|
sprint: input.sprint,
|
|
@@ -12914,11 +12964,11 @@ function assertAllTicketsApprovedLeaf() {
|
|
|
12914
12964
|
}
|
|
12915
12965
|
|
|
12916
12966
|
// src/application/chains/refine/refine-flow.ts
|
|
12917
|
-
import { join as
|
|
12967
|
+
import { join as join25 } from "path";
|
|
12918
12968
|
|
|
12919
12969
|
// src/business/usecases/refine/refine-single-ticket.ts
|
|
12920
12970
|
import { readFile as readFile12 } from "fs/promises";
|
|
12921
|
-
import { dirname as
|
|
12971
|
+
import { dirname as dirname15 } from "path";
|
|
12922
12972
|
var RefineSingleTicketUseCase = class {
|
|
12923
12973
|
constructor(ai, logger) {
|
|
12924
12974
|
this.ai = ai;
|
|
@@ -12981,8 +13031,8 @@ var RefineSingleTicketUseCase = class {
|
|
|
12981
13031
|
// scroll past before it responds.
|
|
12982
13032
|
async runInteractive(input, wrapper, log) {
|
|
12983
13033
|
const handover = input.runInTerminal ?? (async (fn) => fn());
|
|
12984
|
-
const promptDir =
|
|
12985
|
-
const outputDir = input.outputFilePath !== void 0 ?
|
|
13034
|
+
const promptDir = dirname15(input.promptFilePath);
|
|
13035
|
+
const outputDir = input.outputFilePath !== void 0 ? dirname15(input.outputFilePath) : promptDir;
|
|
12986
13036
|
const addDirArgs = ["--add-dir", promptDir];
|
|
12987
13037
|
if (outputDir !== promptDir) {
|
|
12988
13038
|
addDirArgs.push("--add-dir", outputDir);
|
|
@@ -13195,7 +13245,7 @@ function buildPerTicketChain(deps, refineUseCase, ticket, opts) {
|
|
|
13195
13245
|
if (!ctx.refinementUnitRoot) {
|
|
13196
13246
|
throw new Error(`refine-${String(ticket.id)}: ctx.refinementUnitRoot must be set by build-refinement-unit`);
|
|
13197
13247
|
}
|
|
13198
|
-
return AbsolutePath.trustString(
|
|
13248
|
+
return AbsolutePath.trustString(join25(String(ctx.refinementUnitRoot), "prompt.md"));
|
|
13199
13249
|
},
|
|
13200
13250
|
buildPrompt: (ctx) => {
|
|
13201
13251
|
const outputFilePath = ctx.refinementRequirementsJsonPath !== void 0 ? String(ctx.refinementRequirementsJsonPath) : void 0;
|
|
@@ -14485,18 +14535,24 @@ function stepGlyph(status, spinnerFrame) {
|
|
|
14485
14535
|
return /* @__PURE__ */ jsx19(Text17, { color: inkColors.muted, bold: true, children: glyphs.emDash });
|
|
14486
14536
|
return /* @__PURE__ */ jsx19(Text17, { color: inkColors.muted, bold: true, children: glyphs.phasePending });
|
|
14487
14537
|
}
|
|
14538
|
+
var MAX_RENDERED_STEPS = 50;
|
|
14488
14539
|
function StepTrace({ steps, isRunning }) {
|
|
14489
14540
|
const spinnerFrame = useSpinnerFrame();
|
|
14490
14541
|
if (steps.length === 0) {
|
|
14491
14542
|
if (isRunning) return /* @__PURE__ */ jsx19(Spinner, { label: "Starting\u2026" });
|
|
14492
14543
|
return /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "No steps recorded." });
|
|
14493
14544
|
}
|
|
14494
|
-
|
|
14495
|
-
|
|
14496
|
-
|
|
14497
|
-
|
|
14498
|
-
step
|
|
14499
|
-
|
|
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
|
+
] });
|
|
14500
14556
|
}
|
|
14501
14557
|
function CompactStepSummary({ steps }) {
|
|
14502
14558
|
if (steps.length === 0) return /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "No steps recorded." });
|
|
@@ -14976,6 +15032,7 @@ var EXECUTE_HINTS_TERMINAL = [
|
|
|
14976
15032
|
function isTaskStep2(name) {
|
|
14977
15033
|
return /^task-[a-zA-Z0-9_-]+$/.test(name);
|
|
14978
15034
|
}
|
|
15035
|
+
var MAX_LIVE_STEPS = 200;
|
|
14979
15036
|
function ExecuteView({ sessionId, sessionManager, signalBus }) {
|
|
14980
15037
|
const router = useRouterOptional();
|
|
14981
15038
|
const cancelInFlight = useRef2(false);
|
|
@@ -15045,12 +15102,14 @@ function ExecuteView({ sessionId, sessionManager, signalBus }) {
|
|
|
15045
15102
|
durationMs: entry.durationMs,
|
|
15046
15103
|
errorMessage: entry.error?.message
|
|
15047
15104
|
};
|
|
15105
|
+
let next;
|
|
15048
15106
|
if (idx >= 0) {
|
|
15049
|
-
|
|
15107
|
+
next = [...prev];
|
|
15050
15108
|
next[idx] = settled;
|
|
15051
|
-
|
|
15109
|
+
} else {
|
|
15110
|
+
next = [...prev, settled];
|
|
15052
15111
|
}
|
|
15053
|
-
return
|
|
15112
|
+
return next.length > MAX_LIVE_STEPS ? next.slice(-MAX_LIVE_STEPS) : next;
|
|
15054
15113
|
});
|
|
15055
15114
|
if (signalBus === void 0 || signalBus === null) {
|
|
15056
15115
|
if (entry.stepName === "rate-limit-paused") setRateLimitVisible(true);
|
|
@@ -18544,13 +18603,13 @@ async function currentSprintReadableCheck(deps) {
|
|
|
18544
18603
|
}
|
|
18545
18604
|
|
|
18546
18605
|
// src/application/doctor/checks/data-dir-writable.ts
|
|
18547
|
-
import { mkdir as mkdir15, unlink as unlink3, writeFile as
|
|
18548
|
-
import { join as
|
|
18606
|
+
import { mkdir as mkdir15, unlink as unlink3, writeFile as writeFile9 } from "fs/promises";
|
|
18607
|
+
import { join as join26 } from "path";
|
|
18549
18608
|
async function dataDirWritableCheck(deps) {
|
|
18550
|
-
const probe =
|
|
18609
|
+
const probe = join26(deps.storage.dataDir, `.doctor-write-${String(process.pid)}-${String(Date.now())}.tmp`);
|
|
18551
18610
|
try {
|
|
18552
18611
|
await mkdir15(deps.storage.dataDir, { recursive: true });
|
|
18553
|
-
await
|
|
18612
|
+
await writeFile9(probe, "doctor", { encoding: "utf-8", mode: 384 });
|
|
18554
18613
|
} catch (err) {
|
|
18555
18614
|
return {
|
|
18556
18615
|
name: "Data directory",
|
|
@@ -18657,7 +18716,7 @@ function nodeVersionCheck() {
|
|
|
18657
18716
|
|
|
18658
18717
|
// src/application/doctor/checks/onboarding-status.ts
|
|
18659
18718
|
import { readFile as readFile13 } from "fs/promises";
|
|
18660
|
-
import { join as
|
|
18719
|
+
import { join as join27 } from "path";
|
|
18661
18720
|
var HARNESS_MARKER_PREFIX2 = "<!-- ralphctl onboard:";
|
|
18662
18721
|
var MIN_HYBRID_PROSE_CHARS = 200;
|
|
18663
18722
|
var CONTEXT_FILE_BY_PROVIDER = {
|
|
@@ -18678,7 +18737,7 @@ function extractPreamble(body) {
|
|
|
18678
18737
|
return body.slice(0, idx);
|
|
18679
18738
|
}
|
|
18680
18739
|
async function classifyContextFile(repoPath, relPath) {
|
|
18681
|
-
const fullPath =
|
|
18740
|
+
const fullPath = join27(repoPath, relPath);
|
|
18682
18741
|
let body;
|
|
18683
18742
|
try {
|
|
18684
18743
|
body = await readFile13(fullPath, "utf-8");
|
|
@@ -18761,10 +18820,10 @@ async function onboardingStatusCheck(deps) {
|
|
|
18761
18820
|
|
|
18762
18821
|
// src/application/doctor/checks/project-paths-exist.ts
|
|
18763
18822
|
import { stat as stat5 } from "fs/promises";
|
|
18764
|
-
import { join as
|
|
18823
|
+
import { join as join28 } from "path";
|
|
18765
18824
|
async function isGitDir(path) {
|
|
18766
18825
|
try {
|
|
18767
|
-
const s = await stat5(
|
|
18826
|
+
const s = await stat5(join28(path, ".git"));
|
|
18768
18827
|
return s.isDirectory() || s.isFile();
|
|
18769
18828
|
} catch {
|
|
18770
18829
|
return false;
|
|
@@ -18825,9 +18884,9 @@ async function projectPathsExistCheck(deps) {
|
|
|
18825
18884
|
}
|
|
18826
18885
|
|
|
18827
18886
|
// src/application/doctor/checks/session-log-path.ts
|
|
18828
|
-
import { join as
|
|
18887
|
+
import { join as join29 } from "path";
|
|
18829
18888
|
function sessionLogPathCheck(deps) {
|
|
18830
|
-
const file =
|
|
18889
|
+
const file = join29(deps.storage.logsDir, `${deps.sessionId}.jsonl`);
|
|
18831
18890
|
return Promise.resolve({
|
|
18832
18891
|
name: "Session log path",
|
|
18833
18892
|
status: "pass",
|
|
@@ -18967,12 +19026,12 @@ function DoctorView() {
|
|
|
18967
19026
|
|
|
18968
19027
|
// src/application/tui/views/browse/progress-view.tsx
|
|
18969
19028
|
import { useEffect as useEffect39, useState as useState25 } from "react";
|
|
18970
|
-
import { join as
|
|
19029
|
+
import { join as join31 } from "path";
|
|
18971
19030
|
import { Box as Box34, Text as Text29 } from "ink";
|
|
18972
19031
|
|
|
18973
19032
|
// src/business/usecases/sprint/show-progress.ts
|
|
18974
19033
|
import { readFile as readFile14 } from "fs/promises";
|
|
18975
|
-
import { join as
|
|
19034
|
+
import { join as join30 } from "path";
|
|
18976
19035
|
var DEFAULT_STALE_THRESHOLD_HOURS = 24;
|
|
18977
19036
|
var STALE_THRESHOLD_HOURS = DEFAULT_STALE_THRESHOLD_HOURS;
|
|
18978
19037
|
var ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
|
|
@@ -19058,7 +19117,7 @@ function detectBranchInconsistency(sprint, projects, external) {
|
|
|
19058
19117
|
return out;
|
|
19059
19118
|
}
|
|
19060
19119
|
var ShowProgressUseCase = class {
|
|
19061
|
-
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")) {
|
|
19062
19121
|
this.sprints = sprints;
|
|
19063
19122
|
this.tasks = tasks;
|
|
19064
19123
|
this.projects = projects;
|
|
@@ -19154,7 +19213,7 @@ function ProgressView() {
|
|
|
19154
19213
|
deps.projectRepo,
|
|
19155
19214
|
deps.external,
|
|
19156
19215
|
void 0,
|
|
19157
|
-
(id) =>
|
|
19216
|
+
(id) => join31(String(deps.storage.sprintsDir), String(id), "progress.md")
|
|
19158
19217
|
);
|
|
19159
19218
|
const result = await uc.execute({ sprintId, now: IsoTimestamp.now() });
|
|
19160
19219
|
if (cancel.current) return;
|
|
@@ -19257,14 +19316,14 @@ function Timeline({ report }) {
|
|
|
19257
19316
|
|
|
19258
19317
|
// src/application/tui/views/crud/sprint-export-requirements-view.tsx
|
|
19259
19318
|
import { useEffect as useEffect40 } from "react";
|
|
19260
|
-
import { writeFile as
|
|
19319
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
19261
19320
|
import { isAbsolute, resolve } from "path";
|
|
19262
19321
|
|
|
19263
19322
|
// src/business/usecases/sprint/export-requirements.ts
|
|
19264
19323
|
import { readFile as readFile15 } from "fs/promises";
|
|
19265
19324
|
var ExportRequirementsUseCase = class {
|
|
19266
|
-
constructor(
|
|
19267
|
-
this.writeFile =
|
|
19325
|
+
constructor(writeFile14, readJsonFile2 = (p) => readFile15(p, "utf-8")) {
|
|
19326
|
+
this.writeFile = writeFile14;
|
|
19268
19327
|
this.readJsonFile = readJsonFile2;
|
|
19269
19328
|
}
|
|
19270
19329
|
writeFile;
|
|
@@ -19337,7 +19396,7 @@ function SprintExportRequirementsView() {
|
|
|
19337
19396
|
const finalPath = trimmed.length === 0 ? defaultPath : isAbsolute(trimmed) ? trimmed : resolve(process.cwd(), trimmed);
|
|
19338
19397
|
setStep("Writing file\u2026");
|
|
19339
19398
|
const aggregatePath = resolveStoragePaths().requirementsAggregateFile(sprintId);
|
|
19340
|
-
const uc = new ExportRequirementsUseCase((p, b) =>
|
|
19399
|
+
const uc = new ExportRequirementsUseCase((p, b) => writeFile10(p, b, "utf-8"));
|
|
19341
19400
|
const result = await uc.execute({
|
|
19342
19401
|
aggregatePath,
|
|
19343
19402
|
outputPath: AbsolutePath.trustString(finalPath)
|
|
@@ -19374,16 +19433,16 @@ function SprintExportRequirementsView() {
|
|
|
19374
19433
|
|
|
19375
19434
|
// src/application/tui/views/crud/sprint-export-context-view.tsx
|
|
19376
19435
|
import { useEffect as useEffect41 } from "react";
|
|
19377
|
-
import { writeFile as
|
|
19436
|
+
import { writeFile as writeFile11 } from "fs/promises";
|
|
19378
19437
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
19379
19438
|
|
|
19380
19439
|
// src/business/usecases/sprint/export-context.ts
|
|
19381
19440
|
var ExportContextUseCase = class {
|
|
19382
|
-
constructor(sprints, tasks, projects,
|
|
19441
|
+
constructor(sprints, tasks, projects, writeFile14) {
|
|
19383
19442
|
this.sprints = sprints;
|
|
19384
19443
|
this.tasks = tasks;
|
|
19385
19444
|
this.projects = projects;
|
|
19386
|
-
this.writeFile =
|
|
19445
|
+
this.writeFile = writeFile14;
|
|
19387
19446
|
}
|
|
19388
19447
|
sprints;
|
|
19389
19448
|
tasks;
|
|
@@ -19545,7 +19604,7 @@ function SprintExportContextView() {
|
|
|
19545
19604
|
deps.sprintRepo,
|
|
19546
19605
|
deps.taskRepo,
|
|
19547
19606
|
deps.projectRepo,
|
|
19548
|
-
(p, b) =>
|
|
19607
|
+
(p, b) => writeFile11(p, b, "utf-8")
|
|
19549
19608
|
);
|
|
19550
19609
|
const result = await uc.execute({
|
|
19551
19610
|
sprintId,
|
|
@@ -20128,7 +20187,7 @@ async function handleCompletionRequest(program, deps) {
|
|
|
20128
20187
|
// src/application/cli/commands/completion-install.ts
|
|
20129
20188
|
import { appendFile as appendFile3, readFile as readFile16 } from "fs/promises";
|
|
20130
20189
|
import { homedir as homedir2 } from "os";
|
|
20131
|
-
import { join as
|
|
20190
|
+
import { join as join32 } from "path";
|
|
20132
20191
|
import * as c from "colorette";
|
|
20133
20192
|
|
|
20134
20193
|
// src/application/cli/exit-codes.ts
|
|
@@ -20238,11 +20297,11 @@ function rcFileForShell(shell) {
|
|
|
20238
20297
|
const home = homedir2();
|
|
20239
20298
|
switch (shell) {
|
|
20240
20299
|
case "bash":
|
|
20241
|
-
return
|
|
20300
|
+
return join32(home, ".bashrc");
|
|
20242
20301
|
case "zsh":
|
|
20243
|
-
return
|
|
20302
|
+
return join32(home, ".zshrc");
|
|
20244
20303
|
case "fish":
|
|
20245
|
-
return
|
|
20304
|
+
return join32(home, ".config", "fish", "config.fish");
|
|
20246
20305
|
}
|
|
20247
20306
|
}
|
|
20248
20307
|
function runCompletionShow(requestedShell) {
|
|
@@ -21133,12 +21192,12 @@ async function runSprintClose(deps, id) {
|
|
|
21133
21192
|
}
|
|
21134
21193
|
|
|
21135
21194
|
// src/application/cli/commands/sprint-context.ts
|
|
21136
|
-
import { writeFile as
|
|
21195
|
+
import { writeFile as writeFile13 } from "fs/promises";
|
|
21137
21196
|
import { isAbsolute as isAbsolute4, resolve as resolve4 } from "path";
|
|
21138
21197
|
import * as c16 from "colorette";
|
|
21139
21198
|
|
|
21140
21199
|
// src/application/cli/commands/sprint-requirements.ts
|
|
21141
|
-
import { writeFile as
|
|
21200
|
+
import { writeFile as writeFile12 } from "fs/promises";
|
|
21142
21201
|
import { isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
|
|
21143
21202
|
import * as c15 from "colorette";
|
|
21144
21203
|
function attachSprintRequirements(group, deps) {
|
|
@@ -21155,7 +21214,7 @@ async function runSprintRequirements(deps, id, opts) {
|
|
|
21155
21214
|
if (!sprintR.ok) return sprintR;
|
|
21156
21215
|
const outPath = resolveOutputPath(opts.output, `${String(sprintR.value)}-requirements.md`);
|
|
21157
21216
|
const aggregatePath = resolveStoragePaths().requirementsAggregateFile(sprintR.value);
|
|
21158
|
-
const uc = new ExportRequirementsUseCase((path, body) =>
|
|
21217
|
+
const uc = new ExportRequirementsUseCase((path, body) => writeFile12(path, body, "utf-8"));
|
|
21159
21218
|
return uc.execute({ aggregatePath, outputPath: outPath });
|
|
21160
21219
|
},
|
|
21161
21220
|
format: (_d, out) => `${c15.green("wrote")} ${String(out.path)} (${String(out.byteCount)} bytes)`
|
|
@@ -21222,7 +21281,7 @@ async function runSprintContext(deps, id, opts) {
|
|
|
21222
21281
|
deps.sprintRepo,
|
|
21223
21282
|
deps.taskRepo,
|
|
21224
21283
|
deps.projectRepo,
|
|
21225
|
-
(path, body) =>
|
|
21284
|
+
(path, body) => writeFile13(path, body, "utf-8")
|
|
21226
21285
|
);
|
|
21227
21286
|
return uc.execute({ sprintId: sprintR.value, outputPath: outPath });
|
|
21228
21287
|
},
|
|
@@ -21490,7 +21549,7 @@ async function runSprintPlan(deps, opts) {
|
|
|
21490
21549
|
}
|
|
21491
21550
|
|
|
21492
21551
|
// src/application/cli/commands/sprint-progress.ts
|
|
21493
|
-
import { join as
|
|
21552
|
+
import { join as join33 } from "path";
|
|
21494
21553
|
import * as c19 from "colorette";
|
|
21495
21554
|
function attachSprintProgress(group, deps) {
|
|
21496
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) => {
|
|
@@ -21534,7 +21593,7 @@ async function runSprintProgress(deps, id, opts) {
|
|
|
21534
21593
|
deps.projectRepo,
|
|
21535
21594
|
deps.external,
|
|
21536
21595
|
void 0,
|
|
21537
|
-
(sprintId) =>
|
|
21596
|
+
(sprintId) => join33(String(deps.storage.sprintsDir), String(sprintId), "progress.md")
|
|
21538
21597
|
);
|
|
21539
21598
|
return uc.execute({ sprintId: idR.value, now: IsoTimestamp.now() });
|
|
21540
21599
|
},
|
|
@@ -21751,7 +21810,10 @@ ${formatTicketsTable(sprint)}`
|
|
|
21751
21810
|
// src/application/cli/commands/sprint-start.ts
|
|
21752
21811
|
import * as c23 from "colorette";
|
|
21753
21812
|
function attachSprintStart(group, deps) {
|
|
21754
|
-
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) => {
|
|
21755
21817
|
const code = await runSprintStart(deps, opts);
|
|
21756
21818
|
if (code !== EXIT_SUCCESS) process.exitCode = code;
|
|
21757
21819
|
});
|
|
@@ -22269,7 +22331,7 @@ async function runTicketRemove(deps, opts) {
|
|
|
22269
22331
|
// package.json
|
|
22270
22332
|
var package_default = {
|
|
22271
22333
|
name: "ralphctl",
|
|
22272
|
-
version: "0.6.
|
|
22334
|
+
version: "0.6.3",
|
|
22273
22335
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
|
|
22274
22336
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
22275
22337
|
type: "module",
|