tldr-experts 0.14.2 → 0.14.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/CHANGELOG.md +111 -0
- package/README.md +1 -0
- package/dist/hooks/answer-capture.js +2 -2
- package/dist/hooks/budget-gate.js +1 -1
- package/dist/hooks/{chunk-dnat9z30.js → chunk-mt8f2czb.js} +2 -0
- package/dist/hooks/{chunk-97zs34wb.js → chunk-ppgbs5ag.js} +1 -1
- package/dist/hooks/{chunk-w8qm1jbn.js → chunk-taj1vrbe.js} +1 -1
- package/dist/hooks/session-start.js +3 -3
- package/dist/hooks/statusline.js +2 -2
- package/dist/tldrx.js +978 -551
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/dist/tldrx.js
CHANGED
|
@@ -7285,6 +7285,8 @@ var EVENT_TYPES = [
|
|
|
7285
7285
|
"story.review_retried",
|
|
7286
7286
|
"story.work_rescued",
|
|
7287
7287
|
"story.touches_widened",
|
|
7288
|
+
"worktree.foreign_work_aside",
|
|
7289
|
+
"worktree.foreign_work_restored",
|
|
7288
7290
|
"result.unreadable",
|
|
7289
7291
|
"operator_note",
|
|
7290
7292
|
"check.passed",
|
|
@@ -24219,65 +24221,9 @@ function effortFlag(args, name = "effort") {
|
|
|
24219
24221
|
}
|
|
24220
24222
|
|
|
24221
24223
|
// src/core/facilitator/runAuto.ts
|
|
24224
|
+
import { readFileSync as readFileSync70 } from "node:fs";
|
|
24222
24225
|
import { join as join93 } from "node:path";
|
|
24223
24226
|
|
|
24224
|
-
// src/core/budget/wouldExceed.ts
|
|
24225
|
-
function totalSpent(budget) {
|
|
24226
|
-
return budget.phases.reduce((sum, p) => sum + p.spent_usd, 0);
|
|
24227
|
-
}
|
|
24228
|
-
function remaining(budget, phaseId) {
|
|
24229
|
-
if (phaseId !== undefined) {
|
|
24230
|
-
const phase = budget.phases.find((p) => p.id === phaseId);
|
|
24231
|
-
if (phase !== undefined)
|
|
24232
|
-
return round5(phase.ceiling_usd - phase.spent_usd);
|
|
24233
|
-
}
|
|
24234
|
-
return round5(budget.ceiling_usd - totalSpent(budget));
|
|
24235
|
-
}
|
|
24236
|
-
function wouldExceed(budget, phaseId, estimate) {
|
|
24237
|
-
const phase = phaseId === null ? undefined : budget.phases.find((p) => p.id === phaseId);
|
|
24238
|
-
const scope = phase === undefined ? "run" : "phase";
|
|
24239
|
-
const ceiling = phase === undefined ? budget.ceiling_usd : phase.ceiling_usd;
|
|
24240
|
-
const spent = phase === undefined ? totalSpent(budget) : phase.spent_usd;
|
|
24241
|
-
const left = round5(ceiling - spent);
|
|
24242
|
-
const exceeds = round5(spent + estimate) > ceiling;
|
|
24243
|
-
const pct = ceiling === 0 ? 100 : (spent + estimate) / ceiling * 100;
|
|
24244
|
-
return {
|
|
24245
|
-
exceeds,
|
|
24246
|
-
blocked: exceeds && budget.on_exceed === "block",
|
|
24247
|
-
scope,
|
|
24248
|
-
phaseId: phase === undefined ? null : phase.id,
|
|
24249
|
-
remaining: left,
|
|
24250
|
-
ceiling,
|
|
24251
|
-
estimate,
|
|
24252
|
-
warns: !exceeds && pct >= budget.warn_at_pct
|
|
24253
|
-
};
|
|
24254
|
-
}
|
|
24255
|
-
function round5(n) {
|
|
24256
|
-
return Math.round(n * 1e6) / 1e6;
|
|
24257
|
-
}
|
|
24258
|
-
function wouldExceedHostTokens(budget, phaseId, spentTokens) {
|
|
24259
|
-
const ceiling = hostTokenCeiling(budget, phaseId);
|
|
24260
|
-
if (ceiling === null)
|
|
24261
|
-
return null;
|
|
24262
|
-
const phase = phaseId === null ? undefined : budget.phases.find((p) => p.id === phaseId);
|
|
24263
|
-
const over = ceiling > 0 && spentTokens > ceiling;
|
|
24264
|
-
return {
|
|
24265
|
-
over,
|
|
24266
|
-
blocked: over && budget.on_host_tokens_exceed === "block",
|
|
24267
|
-
scope: phase === undefined ? "run" : "phase",
|
|
24268
|
-
spent: spentTokens,
|
|
24269
|
-
ceiling
|
|
24270
|
-
};
|
|
24271
|
-
}
|
|
24272
|
-
|
|
24273
|
-
// src/core/budget/remainingWork.ts
|
|
24274
|
-
import { existsSync as existsSync39, readFileSync as readFileSync35 } from "node:fs";
|
|
24275
|
-
import { join as join58 } from "node:path";
|
|
24276
|
-
|
|
24277
|
-
// src/core/run/buildProgress.ts
|
|
24278
|
-
import { existsSync as existsSync37, readFileSync as readFileSync33 } from "node:fs";
|
|
24279
|
-
import { join as join56 } from "node:path";
|
|
24280
|
-
|
|
24281
24227
|
// src/core/build/implicitPlan.ts
|
|
24282
24228
|
import { existsSync as existsSync36, mkdirSync as mkdirSync10, readFileSync as readFileSync32, readdirSync as readdirSync18, rmSync as rmSync7, statSync as statSync15, writeFileSync as writeFileSync9 } from "node:fs";
|
|
24283
24229
|
import { join as join55 } from "node:path";
|
|
@@ -24918,13 +24864,125 @@ function repoDirOf(workspace, name) {
|
|
|
24918
24864
|
throw new GitError(`repo \`${name}\` resolves to ${resolved}, which does not exist`);
|
|
24919
24865
|
return resolved;
|
|
24920
24866
|
}
|
|
24921
|
-
async function
|
|
24922
|
-
const result2 = await git(["status", "--porcelain"], cwd);
|
|
24867
|
+
async function dirtyEntries(cwd) {
|
|
24868
|
+
const result2 = await git(["status", "--porcelain", "-z"], cwd);
|
|
24923
24869
|
if (!result2.ok)
|
|
24924
24870
|
throw new GitError(`\`git status\` failed in ${cwd}: ${firstLine7(result2.stderr)}`);
|
|
24871
|
+
const fields = result2.stdout.split("\x00");
|
|
24872
|
+
const out = [];
|
|
24873
|
+
for (let i = 0;i < fields.length; i += 1) {
|
|
24874
|
+
const record2 = fields[i] ?? "";
|
|
24875
|
+
if (record2 === "")
|
|
24876
|
+
continue;
|
|
24877
|
+
const code = record2.slice(0, 2);
|
|
24878
|
+
const path = record2.slice(3);
|
|
24879
|
+
if (code.startsWith("R") || code.startsWith("C") || code[1] === "R" || code[1] === "C")
|
|
24880
|
+
i += 1;
|
|
24881
|
+
out.push({ code, path, entry: `${code} ${path}`.trim() });
|
|
24882
|
+
}
|
|
24883
|
+
return out;
|
|
24884
|
+
}
|
|
24885
|
+
async function dirtyPaths(cwd) {
|
|
24886
|
+
return (await dirtyEntries(cwd)).map((entry) => entry.entry);
|
|
24887
|
+
}
|
|
24888
|
+
function literalPathspec(path) {
|
|
24889
|
+
return `:(literal)${path}`;
|
|
24890
|
+
}
|
|
24891
|
+
async function submodulePaths(cwd) {
|
|
24892
|
+
const result2 = await git(["config", "--file", ".gitmodules", "--get-regexp", "^submodule\\..*\\.path$"], cwd);
|
|
24893
|
+
const paths = new Set;
|
|
24894
|
+
if (!result2.ok)
|
|
24895
|
+
return paths;
|
|
24896
|
+
for (const line of result2.stdout.split(`
|
|
24897
|
+
`)) {
|
|
24898
|
+
const at = line.indexOf(" ");
|
|
24899
|
+
if (at === -1)
|
|
24900
|
+
continue;
|
|
24901
|
+
const path = line.slice(at + 1).trim();
|
|
24902
|
+
if (path !== "")
|
|
24903
|
+
paths.add(path);
|
|
24904
|
+
}
|
|
24905
|
+
return paths;
|
|
24906
|
+
}
|
|
24907
|
+
async function operationInProgress(cwd) {
|
|
24908
|
+
const gitDir = await git(["rev-parse", "--git-dir"], cwd);
|
|
24909
|
+
if (!gitDir.ok)
|
|
24910
|
+
return null;
|
|
24911
|
+
const dir = resolve9(cwd, gitDir.stdout.trim());
|
|
24912
|
+
for (const [name, what] of [
|
|
24913
|
+
["MERGE_HEAD", "a merge"],
|
|
24914
|
+
["rebase-merge", "a rebase"],
|
|
24915
|
+
["rebase-apply", "a rebase or `git am`"],
|
|
24916
|
+
["CHERRY_PICK_HEAD", "a cherry-pick"],
|
|
24917
|
+
["REVERT_HEAD", "a revert"],
|
|
24918
|
+
["BISECT_LOG", "a bisect"]
|
|
24919
|
+
]) {
|
|
24920
|
+
if (existsSync32(join51(dir, name)))
|
|
24921
|
+
return what;
|
|
24922
|
+
}
|
|
24923
|
+
return null;
|
|
24924
|
+
}
|
|
24925
|
+
async function unmergedPaths(cwd) {
|
|
24926
|
+
const result2 = await git(["diff", "--name-only", "--diff-filter=U"], cwd);
|
|
24925
24927
|
return result2.stdout.split(`
|
|
24926
24928
|
`).map((line) => line.trim()).filter((line) => line !== "");
|
|
24927
24929
|
}
|
|
24930
|
+
async function stashPushPaths(cwd, message4, paths) {
|
|
24931
|
+
if (paths.length === 0)
|
|
24932
|
+
return { ok: false, hash: "", message: message4, detail: "no paths to set aside" };
|
|
24933
|
+
const pushed = await git(["stash", "push", "-u", "-m", message4, "--", ...paths.map(literalPathspec)], cwd);
|
|
24934
|
+
if (!pushed.ok) {
|
|
24935
|
+
return { ok: false, hash: "", message: message4, detail: firstLine7(pushed.stderr) || firstLine7(pushed.stdout) };
|
|
24936
|
+
}
|
|
24937
|
+
const hash = await fullShaOf(cwd, "refs/stash");
|
|
24938
|
+
if (hash === "")
|
|
24939
|
+
return { ok: false, hash: "", message: message4, detail: "`git stash push` left no refs/stash to name" };
|
|
24940
|
+
return { ok: true, hash, message: message4, detail: firstLine7(pushed.stdout) };
|
|
24941
|
+
}
|
|
24942
|
+
async function stashRefFor(cwd, hash) {
|
|
24943
|
+
const listed = await git(["stash", "list", "--format=%H %gd"], cwd);
|
|
24944
|
+
if (!listed.ok)
|
|
24945
|
+
return "";
|
|
24946
|
+
for (const line of listed.stdout.split(`
|
|
24947
|
+
`)) {
|
|
24948
|
+
const [sha, ref] = line.trim().split(" ");
|
|
24949
|
+
if (sha === hash && ref !== undefined)
|
|
24950
|
+
return ref;
|
|
24951
|
+
}
|
|
24952
|
+
return "";
|
|
24953
|
+
}
|
|
24954
|
+
async function stashPop(cwd, hash) {
|
|
24955
|
+
const ref = await stashRefFor(cwd, hash);
|
|
24956
|
+
if (ref === "") {
|
|
24957
|
+
return {
|
|
24958
|
+
restored: false,
|
|
24959
|
+
indexRestored: false,
|
|
24960
|
+
conflicts: [],
|
|
24961
|
+
ref: "",
|
|
24962
|
+
detail: "the stash entry is no longer in `git stash list`"
|
|
24963
|
+
};
|
|
24964
|
+
}
|
|
24965
|
+
const withIndex = await git(["stash", "pop", "--index", ref], cwd);
|
|
24966
|
+
if (withIndex.ok)
|
|
24967
|
+
return { restored: true, indexRestored: true, conflicts: [], ref, detail: firstLine7(withIndex.stdout) };
|
|
24968
|
+
const plain = await git(["stash", "pop", ref], cwd);
|
|
24969
|
+
if (plain.ok) {
|
|
24970
|
+
return {
|
|
24971
|
+
restored: true,
|
|
24972
|
+
indexRestored: false,
|
|
24973
|
+
conflicts: [],
|
|
24974
|
+
ref,
|
|
24975
|
+
detail: `${firstLine7(plain.stdout)} — the index could not be reinstated (${firstLine7(withIndex.stderr)})`
|
|
24976
|
+
};
|
|
24977
|
+
}
|
|
24978
|
+
return {
|
|
24979
|
+
restored: false,
|
|
24980
|
+
indexRestored: false,
|
|
24981
|
+
conflicts: await unmergedPaths(cwd),
|
|
24982
|
+
ref: await stashRefFor(cwd, hash),
|
|
24983
|
+
detail: firstLine7(plain.stderr) || firstLine7(plain.stdout) || `git stash pop exited ${String(plain.exitCode)}`
|
|
24984
|
+
};
|
|
24985
|
+
}
|
|
24928
24986
|
function stateDirPrefixes(workspaceRoot, repoDir) {
|
|
24929
24987
|
const base = resolve9(repoDir);
|
|
24930
24988
|
const prefixes = [];
|
|
@@ -26621,7 +26679,13 @@ function readOrEmpty2(path) {
|
|
|
26621
26679
|
}
|
|
26622
26680
|
}
|
|
26623
26681
|
|
|
26682
|
+
// src/core/run/boundary.ts
|
|
26683
|
+
import { existsSync as existsSync38, readFileSync as readFileSync34, readdirSync as readdirSync19 } from "node:fs";
|
|
26684
|
+
import { join as join57 } from "node:path";
|
|
26685
|
+
|
|
26624
26686
|
// src/core/run/buildProgress.ts
|
|
26687
|
+
import { existsSync as existsSync37, readFileSync as readFileSync33 } from "node:fs";
|
|
26688
|
+
import { join as join56 } from "node:path";
|
|
26625
26689
|
var PLAN_DIR = "03-plan";
|
|
26626
26690
|
var BUILD_PHASE2 = "04-build";
|
|
26627
26691
|
var STATUS_RE2 = /^status\s*:\s*(\w+)\s*$/m;
|
|
@@ -26658,7 +26722,7 @@ function buildProgress(runDir) {
|
|
|
26658
26722
|
}
|
|
26659
26723
|
waves.push({ id: wave.id, stories });
|
|
26660
26724
|
}
|
|
26661
|
-
return { waves, done, total, cost_usd:
|
|
26725
|
+
return { waves, done, total, cost_usd: round5(cost), implicit };
|
|
26662
26726
|
}
|
|
26663
26727
|
function renderBuildProgress(progress) {
|
|
26664
26728
|
return progress.waves.map((wave) => `${wave.id} [${wave.stories.map((s) => `${s.id} ${s.status}`).join(", ")}]`).join(" ");
|
|
@@ -26713,17 +26777,569 @@ function costByStory(runDir) {
|
|
|
26713
26777
|
const story = typeof payload.story === "string" ? payload.story : payload.key;
|
|
26714
26778
|
if (typeof story !== "string" || story === "")
|
|
26715
26779
|
continue;
|
|
26716
|
-
costs.set(story,
|
|
26780
|
+
costs.set(story, round5((costs.get(story) ?? 0) + (typeof event2.cost_usd === "number" ? event2.cost_usd : 0)));
|
|
26717
26781
|
}
|
|
26718
26782
|
return costs;
|
|
26719
26783
|
}
|
|
26720
|
-
function
|
|
26784
|
+
function round5(n) {
|
|
26721
26785
|
return Math.round(n * 100) / 100;
|
|
26722
26786
|
}
|
|
26723
26787
|
|
|
26788
|
+
// src/core/run/boundary.ts
|
|
26789
|
+
var OUTSIDE_SURFACE = "work outside the declared surface is a boundary change — a human decides whether to widen the scope";
|
|
26790
|
+
var NAMED_PATHS = 8;
|
|
26791
|
+
var SURFACE_HANDOFFS = ["01-what/handoff.md", "02-how/handoff.md"];
|
|
26792
|
+
var STORIES_DIR2 = "stories";
|
|
26793
|
+
var EPICS_DIR2 = "epics";
|
|
26794
|
+
function normalisePath2(path) {
|
|
26795
|
+
let out = path.trim().split("\\").join("/");
|
|
26796
|
+
while (out.startsWith("./"))
|
|
26797
|
+
out = out.slice(2);
|
|
26798
|
+
while (out.endsWith("/"))
|
|
26799
|
+
out = out.slice(0, -1);
|
|
26800
|
+
return out;
|
|
26801
|
+
}
|
|
26802
|
+
function inSurface(changed, surface) {
|
|
26803
|
+
const path = normalisePath2(changed);
|
|
26804
|
+
for (const raw of surface) {
|
|
26805
|
+
const entry = normalisePath2(raw);
|
|
26806
|
+
if (entry === "")
|
|
26807
|
+
continue;
|
|
26808
|
+
if (path === entry || path.startsWith(`${entry}/`))
|
|
26809
|
+
return true;
|
|
26810
|
+
}
|
|
26811
|
+
return false;
|
|
26812
|
+
}
|
|
26813
|
+
function unqualifiedCitedPaths(handoffText, repos) {
|
|
26814
|
+
const out = [];
|
|
26815
|
+
const seen = new Set;
|
|
26816
|
+
if (handoffText === "")
|
|
26817
|
+
return out;
|
|
26818
|
+
for (const line of handoffText.split(`
|
|
26819
|
+
`)) {
|
|
26820
|
+
const token = parseSrcToken(line, repos);
|
|
26821
|
+
if (token === null)
|
|
26822
|
+
continue;
|
|
26823
|
+
for (const ref of token.refs) {
|
|
26824
|
+
if (ref.kind !== "file" || ref.repo !== null)
|
|
26825
|
+
continue;
|
|
26826
|
+
const path = normalisePath2(ref.path);
|
|
26827
|
+
if (path === "" || seen.has(path))
|
|
26828
|
+
continue;
|
|
26829
|
+
seen.add(path);
|
|
26830
|
+
out.push(path);
|
|
26831
|
+
}
|
|
26832
|
+
}
|
|
26833
|
+
return out;
|
|
26834
|
+
}
|
|
26835
|
+
function storyTouches(runDir) {
|
|
26836
|
+
const dir = join57(runDir, PLAN_DIR, STORIES_DIR2);
|
|
26837
|
+
if (!existsSync38(dir))
|
|
26838
|
+
return [];
|
|
26839
|
+
const out = [];
|
|
26840
|
+
let names;
|
|
26841
|
+
try {
|
|
26842
|
+
names = readdirSync19(dir).filter((name) => name.endsWith(".md")).sort();
|
|
26843
|
+
} catch {
|
|
26844
|
+
return [];
|
|
26845
|
+
}
|
|
26846
|
+
for (const name of names) {
|
|
26847
|
+
const front2 = readFront(join57(dir, name));
|
|
26848
|
+
if (front2 === null)
|
|
26849
|
+
continue;
|
|
26850
|
+
const repo = typeof front2.repo === "string" ? front2.repo : "";
|
|
26851
|
+
const id = typeof front2.id === "string" && front2.id !== "" ? front2.id : name.replace(/\.md$/, "");
|
|
26852
|
+
out.push({ story: id, repo, touches: stringList2(front2.touches) });
|
|
26853
|
+
}
|
|
26854
|
+
return out;
|
|
26855
|
+
}
|
|
26856
|
+
function declaredTouchesFor(runDir, storyId) {
|
|
26857
|
+
for (const row2 of storyTouches(runDir)) {
|
|
26858
|
+
if (row2.story === storyId)
|
|
26859
|
+
return row2.touches;
|
|
26860
|
+
}
|
|
26861
|
+
const implicit = implicitStorySurface(runDir);
|
|
26862
|
+
return implicit !== null && implicit.story === storyId ? implicit.touches : null;
|
|
26863
|
+
}
|
|
26864
|
+
function implicitStorySurface(runDir) {
|
|
26865
|
+
const doc = readYaml(join57(runDir, BUILD_PHASE2, IMPLICIT_PLAN_FILE));
|
|
26866
|
+
const story = doc?.story;
|
|
26867
|
+
if (story === null || typeof story !== "object")
|
|
26868
|
+
return null;
|
|
26869
|
+
const row2 = story;
|
|
26870
|
+
return {
|
|
26871
|
+
story: typeof row2.id === "string" && row2.id !== "" ? row2.id : IMPLICIT_STORY_ID,
|
|
26872
|
+
repo: typeof row2.repo === "string" ? row2.repo : "",
|
|
26873
|
+
touches: stringList2(row2.touches)
|
|
26874
|
+
};
|
|
26875
|
+
}
|
|
26876
|
+
function deriveSurface(runDir, workspace) {
|
|
26877
|
+
const byRepo = new Map;
|
|
26878
|
+
const unqualified = [];
|
|
26879
|
+
const excluded = [];
|
|
26880
|
+
let cited = 0;
|
|
26881
|
+
let declared = 0;
|
|
26882
|
+
const add = (repo, path, count) => {
|
|
26883
|
+
const entry = normalisePath2(path);
|
|
26884
|
+
if (entry === "")
|
|
26885
|
+
return;
|
|
26886
|
+
if (isStatePath(entry)) {
|
|
26887
|
+
if (!excluded.includes(entry))
|
|
26888
|
+
excluded.push(entry);
|
|
26889
|
+
return;
|
|
26890
|
+
}
|
|
26891
|
+
count();
|
|
26892
|
+
if (repo === "") {
|
|
26893
|
+
if (!unqualified.includes(entry))
|
|
26894
|
+
unqualified.push(entry);
|
|
26895
|
+
return;
|
|
26896
|
+
}
|
|
26897
|
+
const list = byRepo.get(repo) ?? [];
|
|
26898
|
+
if (!list.includes(entry))
|
|
26899
|
+
list.push(entry);
|
|
26900
|
+
byRepo.set(repo, list);
|
|
26901
|
+
};
|
|
26902
|
+
const repoNames = new Set(workspace.repos.keys());
|
|
26903
|
+
for (const rel of SURFACE_HANDOFFS) {
|
|
26904
|
+
const text2 = readText2(join57(runDir, rel));
|
|
26905
|
+
if (text2 === "")
|
|
26906
|
+
continue;
|
|
26907
|
+
for (const entry of citedRepoPaths(text2, workspace)) {
|
|
26908
|
+
add(entry.repo, entry.path, () => {
|
|
26909
|
+
cited += 1;
|
|
26910
|
+
});
|
|
26911
|
+
}
|
|
26912
|
+
for (const path of unqualifiedCitedPaths(text2, repoNames)) {
|
|
26913
|
+
add("", path, () => {
|
|
26914
|
+
cited += 1;
|
|
26915
|
+
});
|
|
26916
|
+
}
|
|
26917
|
+
}
|
|
26918
|
+
const stories = storyTouches(runDir);
|
|
26919
|
+
const sources = stories.length > 0 ? stories : [implicitStorySurface(runDir)].filter((s) => s !== null);
|
|
26920
|
+
for (const story of sources) {
|
|
26921
|
+
for (const path of story.touches) {
|
|
26922
|
+
add(story.repo, path, () => {
|
|
26923
|
+
declared += 1;
|
|
26924
|
+
});
|
|
26925
|
+
}
|
|
26926
|
+
}
|
|
26927
|
+
return { byRepo, unqualified, cited, declared, excluded };
|
|
26928
|
+
}
|
|
26929
|
+
function epicTargets2(runDir, workspace) {
|
|
26930
|
+
const out = [];
|
|
26931
|
+
const seen = new Set;
|
|
26932
|
+
const push2 = (epic2, repo, branch) => {
|
|
26933
|
+
if (repo === "" || branch === "")
|
|
26934
|
+
return;
|
|
26935
|
+
const key = `${repo}\x00${branch}`;
|
|
26936
|
+
if (seen.has(key))
|
|
26937
|
+
return;
|
|
26938
|
+
seen.add(key);
|
|
26939
|
+
const rel = repoPath(workspace, repo);
|
|
26940
|
+
out.push({
|
|
26941
|
+
epic: epic2,
|
|
26942
|
+
repo,
|
|
26943
|
+
branch,
|
|
26944
|
+
base: workspace.defaultBranches.get(repo) ?? "main",
|
|
26945
|
+
dir: rel
|
|
26946
|
+
});
|
|
26947
|
+
};
|
|
26948
|
+
const dir = join57(runDir, PLAN_DIR, EPICS_DIR2);
|
|
26949
|
+
if (existsSync38(dir)) {
|
|
26950
|
+
let names = [];
|
|
26951
|
+
try {
|
|
26952
|
+
names = readdirSync19(dir).filter((name) => name.endsWith(".md")).sort();
|
|
26953
|
+
} catch {
|
|
26954
|
+
names = [];
|
|
26955
|
+
}
|
|
26956
|
+
for (const name of names) {
|
|
26957
|
+
const front2 = readFront(join57(dir, name));
|
|
26958
|
+
if (front2 === null)
|
|
26959
|
+
continue;
|
|
26960
|
+
const id = typeof front2.id === "string" ? front2.id : name.replace(/\.md$/, "");
|
|
26961
|
+
const branch = typeof front2.branch === "string" ? front2.branch : "";
|
|
26962
|
+
const repos = stringList2(front2.repos);
|
|
26963
|
+
const fallback = storyTouches(runDir).map((story) => story.repo).filter((repo) => repo !== "");
|
|
26964
|
+
for (const repo of repos.length > 0 ? repos : [...new Set(fallback)])
|
|
26965
|
+
push2(id, repo, branch);
|
|
26966
|
+
}
|
|
26967
|
+
if (out.length > 0)
|
|
26968
|
+
return out;
|
|
26969
|
+
}
|
|
26970
|
+
const doc = readYaml(join57(runDir, BUILD_PHASE2, IMPLICIT_PLAN_FILE));
|
|
26971
|
+
const epic = doc?.epic;
|
|
26972
|
+
if (epic !== null && typeof epic === "object") {
|
|
26973
|
+
const row2 = epic;
|
|
26974
|
+
const id = typeof row2.id === "string" ? row2.id : "E1";
|
|
26975
|
+
const branch = typeof row2.branch === "string" ? row2.branch : "";
|
|
26976
|
+
for (const repo of stringList2(row2.repos))
|
|
26977
|
+
push2(id, repo, branch);
|
|
26978
|
+
}
|
|
26979
|
+
return out;
|
|
26980
|
+
}
|
|
26981
|
+
async function diffRepo(target) {
|
|
26982
|
+
if (target.dir === null)
|
|
26983
|
+
return { changed: null, reason: `${target.repo} is not in ${PROJECT_WORKSPACE_FILE}` };
|
|
26984
|
+
if (!existsSync38(target.dir))
|
|
26985
|
+
return { changed: null, reason: `${target.repo} is not on disk` };
|
|
26986
|
+
const baseCheck = await git(["rev-parse", "--verify", "--quiet", `${target.base}^{commit}`], target.dir);
|
|
26987
|
+
if (!baseCheck.ok) {
|
|
26988
|
+
return {
|
|
26989
|
+
changed: null,
|
|
26990
|
+
reason: `\`${target.base}\`, the \`default_branch\` ${PROJECT_WORKSPACE_FILE} records for ${target.repo}, does not ` + "resolve there — the record is stale, so nothing in this repo has a base to be diffed against " + "(run `tldrx doctor`)"
|
|
26991
|
+
};
|
|
26992
|
+
}
|
|
26993
|
+
const branchCheck = await git(["rev-parse", "--verify", "--quiet", `${target.branch}^{commit}`], target.dir);
|
|
26994
|
+
if (!branchCheck.ok) {
|
|
26995
|
+
return { changed: null, reason: `\`${target.branch}\` does not resolve in ${target.repo}` };
|
|
26996
|
+
}
|
|
26997
|
+
const range = `${target.base}...${target.branch}`;
|
|
26998
|
+
const diff = await git(["diff", "--name-only", range], target.dir);
|
|
26999
|
+
if (!diff.ok)
|
|
27000
|
+
return { changed: null, reason: `\`git diff ${range}\` failed in ${target.repo}` };
|
|
27001
|
+
const changed = diff.stdout.split(`
|
|
27002
|
+
`).map(normalisePath2).filter((path) => path !== "");
|
|
27003
|
+
return { changed, reason: null };
|
|
27004
|
+
}
|
|
27005
|
+
async function evaluateBoundary(input) {
|
|
27006
|
+
if (input.phaseId !== BUILD_PHASE2)
|
|
27007
|
+
return { ok: true, detail: "n/a (not a build stage)" };
|
|
27008
|
+
const workspace = loadWorkspace(input.root);
|
|
27009
|
+
const targets = epicTargets2(input.runDir, workspace);
|
|
27010
|
+
if (targets.length === 0)
|
|
27011
|
+
return { ok: true, detail: "n/a (no plan naming an epic branch)" };
|
|
27012
|
+
const surface = deriveSurface(input.runDir, workspace);
|
|
27013
|
+
const excludedPart = surface.excluded.length === 0 ? "" : `; ${String(surface.excluded.length)} state path(s) excluded from the surface: ` + surface.excluded.slice(0, NAMED_PATHS).join(", ") + (surface.excluded.length > NAMED_PATHS ? `, +${String(surface.excluded.length - NAMED_PATHS)} more` : "");
|
|
27014
|
+
if (surface.cited + surface.declared === 0) {
|
|
27015
|
+
return {
|
|
27016
|
+
ok: true,
|
|
27017
|
+
detail: `n/a (the run declares no surface: no cited path, no \`touches:\`)${excludedPart}`
|
|
27018
|
+
};
|
|
27019
|
+
}
|
|
27020
|
+
const repos = [];
|
|
27021
|
+
for (const target of targets) {
|
|
27022
|
+
const { changed, reason } = await diffRepo(target);
|
|
27023
|
+
if (changed === null) {
|
|
27024
|
+
repos.push({ target, changed: null, reason, outside: [], state: [] });
|
|
27025
|
+
continue;
|
|
27026
|
+
}
|
|
27027
|
+
const allowed = [...surface.byRepo.get(target.repo) ?? [], ...surface.unqualified];
|
|
27028
|
+
const state = changed.filter(isStatePath);
|
|
27029
|
+
const product = changed.filter((path) => !isStatePath(path));
|
|
27030
|
+
repos.push({
|
|
27031
|
+
target,
|
|
27032
|
+
changed: product,
|
|
27033
|
+
reason: null,
|
|
27034
|
+
outside: product.filter((path) => !inSurface(path, allowed)),
|
|
27035
|
+
state
|
|
27036
|
+
});
|
|
27037
|
+
}
|
|
27038
|
+
const measured = repos.filter((repo) => repo.changed !== null);
|
|
27039
|
+
if (measured.length === 0) {
|
|
27040
|
+
const why = repos.map((repo) => repo.reason ?? "unavailable").join("; ");
|
|
27041
|
+
return { ok: true, detail: `n/a (nothing could be diffed: ${why})${excludedPart}` };
|
|
27042
|
+
}
|
|
27043
|
+
const audited = measured.reduce((sum, repo) => sum + (repo.changed?.length ?? 0), 0);
|
|
27044
|
+
const outside = measured.flatMap((repo) => repo.outside.map((path) => `${repo.target.repo}:${path}`));
|
|
27045
|
+
const unread = repos.filter((repo) => repo.changed === null);
|
|
27046
|
+
const unreadPart = unread.length === 0 ? "" : `; not diffed: ${unread.map((repo) => repo.reason ?? "unavailable").join(", ")}`;
|
|
27047
|
+
const range = measured.map((repo) => `${repo.target.repo} ${repo.target.base}...${repo.target.branch}`).join(", ");
|
|
27048
|
+
if (outside.length === 0) {
|
|
27049
|
+
return {
|
|
27050
|
+
ok: true,
|
|
27051
|
+
detail: `${String(audited)} changed path(s), 0 outside the surface (${range})${unreadPart}${excludedPart}`
|
|
27052
|
+
};
|
|
27053
|
+
}
|
|
27054
|
+
const named = outside.slice(0, NAMED_PATHS);
|
|
27055
|
+
const rest = outside.length - named.length;
|
|
27056
|
+
return {
|
|
27057
|
+
ok: false,
|
|
27058
|
+
detail: `${String(audited)} changed path(s), ${String(outside.length)} outside the surface: ` + `${named.join(", ")}${rest > 0 ? `, +${String(rest)} more` : ""}` + `${unreadPart}${excludedPart}; ${OUTSIDE_SURFACE}`
|
|
27059
|
+
};
|
|
27060
|
+
}
|
|
27061
|
+
function readText2(path) {
|
|
27062
|
+
if (!existsSync38(path))
|
|
27063
|
+
return "";
|
|
27064
|
+
try {
|
|
27065
|
+
return readFileSync34(path, "utf8");
|
|
27066
|
+
} catch {
|
|
27067
|
+
return "";
|
|
27068
|
+
}
|
|
27069
|
+
}
|
|
27070
|
+
function readYaml(path) {
|
|
27071
|
+
const text2 = readText2(path);
|
|
27072
|
+
if (text2 === "")
|
|
27073
|
+
return null;
|
|
27074
|
+
try {
|
|
27075
|
+
return parseYaml(text2);
|
|
27076
|
+
} catch {
|
|
27077
|
+
return null;
|
|
27078
|
+
}
|
|
27079
|
+
}
|
|
27080
|
+
function readFront(path) {
|
|
27081
|
+
const text2 = readText2(path);
|
|
27082
|
+
if (text2 === "")
|
|
27083
|
+
return null;
|
|
27084
|
+
const parsed = parseFrontMatter(text2);
|
|
27085
|
+
const doc = parsed.doc;
|
|
27086
|
+
if (doc === null || typeof doc !== "object" || Array.isArray(doc))
|
|
27087
|
+
return null;
|
|
27088
|
+
return doc;
|
|
27089
|
+
}
|
|
27090
|
+
function stringList2(value) {
|
|
27091
|
+
if (!Array.isArray(value))
|
|
27092
|
+
return [];
|
|
27093
|
+
return value.filter((item) => typeof item === "string");
|
|
27094
|
+
}
|
|
27095
|
+
|
|
27096
|
+
// src/core/build/foreignWork.ts
|
|
27097
|
+
var FOREIGN_ASIDE_EVENT = "worktree.foreign_work_aside";
|
|
27098
|
+
var FOREIGN_RESTORED_EVENT = "worktree.foreign_work_restored";
|
|
27099
|
+
var NAMED_PATHS2 = 5;
|
|
27100
|
+
function classifyDirty(input) {
|
|
27101
|
+
const own = [];
|
|
27102
|
+
const foreign = [];
|
|
27103
|
+
const overlapping = [];
|
|
27104
|
+
for (const entry of input.entries) {
|
|
27105
|
+
if (input.statePrefixes.some((p) => entry.path === p || entry.path.startsWith(`${p}/`))) {
|
|
27106
|
+
own.push(entry);
|
|
27107
|
+
continue;
|
|
27108
|
+
}
|
|
27109
|
+
if (isStatePath(entry.path)) {
|
|
27110
|
+
overlapping.push({
|
|
27111
|
+
entry,
|
|
27112
|
+
why: "this is your own directory that happens to be named like tldrx state — the framework " + "never stashes a `tldrx-work/`, `.tldrx/` or `.agent/` path, so this one is yours to move"
|
|
27113
|
+
});
|
|
27114
|
+
continue;
|
|
27115
|
+
}
|
|
27116
|
+
const submodule = [...input.submodules].find((path) => entry.path === path || entry.path.startsWith(`${path}/`));
|
|
27117
|
+
if (submodule !== undefined) {
|
|
27118
|
+
overlapping.push({
|
|
27119
|
+
entry,
|
|
27120
|
+
why: `\`${submodule}\` is a submodule — its uncommitted state is another repository's, ` + "and a stash here does not carry it"
|
|
27121
|
+
});
|
|
27122
|
+
continue;
|
|
27123
|
+
}
|
|
27124
|
+
if (inSurface(entry.path, input.touches)) {
|
|
27125
|
+
overlapping.push({
|
|
27126
|
+
entry,
|
|
27127
|
+
why: "a pending story declares this path in its `touches:` — setting your version of it " + "aside would hand the developer a tree you did not agree to"
|
|
27128
|
+
});
|
|
27129
|
+
continue;
|
|
27130
|
+
}
|
|
27131
|
+
foreign.push(entry);
|
|
27132
|
+
}
|
|
27133
|
+
return { own, foreign, overlapping };
|
|
27134
|
+
}
|
|
27135
|
+
function stashMessage(runId) {
|
|
27136
|
+
return `tldrx ${runId} foreign work`;
|
|
27137
|
+
}
|
|
27138
|
+
function shellQuote2(word) {
|
|
27139
|
+
return `'${word.split("'").join(`'\\''`)}'`;
|
|
27140
|
+
}
|
|
27141
|
+
function stashCommand(repoDir, runId, paths) {
|
|
27142
|
+
const pathspecs = paths.map((path) => shellQuote2(literalPathspec(path))).join(" ");
|
|
27143
|
+
return `git -C ${shellQuote2(repoDir)} stash push -u -m ${shellQuote2(stashMessage(runId))} -- ${pathspecs}`;
|
|
27144
|
+
}
|
|
27145
|
+
async function setAsideForeignWork(repo, repoDir, runId, paths) {
|
|
27146
|
+
if (paths.length === 0)
|
|
27147
|
+
return { stash: null, reason: null };
|
|
27148
|
+
const busy = await operationInProgress(repoDir);
|
|
27149
|
+
if (busy !== null)
|
|
27150
|
+
return { stash: null, reason: `${repo} is in the middle of ${busy}` };
|
|
27151
|
+
const pushed = await stashPushPaths(repoDir, stashMessage(runId), paths);
|
|
27152
|
+
if (!pushed.ok)
|
|
27153
|
+
return { stash: null, reason: pushed.detail };
|
|
27154
|
+
return { stash: { repo, repoDir, paths, hash: pushed.hash, message: pushed.message }, reason: null };
|
|
27155
|
+
}
|
|
27156
|
+
async function restoreForeignWork(stash) {
|
|
27157
|
+
const popped = await stashPop(stash.repoDir, stash.hash);
|
|
27158
|
+
return {
|
|
27159
|
+
stash,
|
|
27160
|
+
restored: popped.restored,
|
|
27161
|
+
indexRestored: popped.indexRestored,
|
|
27162
|
+
conflicts: popped.conflicts,
|
|
27163
|
+
command: popped.ref === "" ? "" : `git -C ${stash.repoDir} stash pop ${popped.ref}`,
|
|
27164
|
+
detail: popped.detail
|
|
27165
|
+
};
|
|
27166
|
+
}
|
|
27167
|
+
function namePaths(paths, max = NAMED_PATHS2) {
|
|
27168
|
+
const named = paths.slice(0, max).join(", ");
|
|
27169
|
+
return paths.length > max ? `${named}, +${String(paths.length - max)} more` : named;
|
|
27170
|
+
}
|
|
27171
|
+
function notRestoredLine(outcome) {
|
|
27172
|
+
return notRestoredSentence({
|
|
27173
|
+
repo: outcome.stash.repo,
|
|
27174
|
+
hash: outcome.stash.hash,
|
|
27175
|
+
paths: outcome.conflicts.length > 0 ? outcome.conflicts : outcome.stash.paths,
|
|
27176
|
+
command: outcome.command,
|
|
27177
|
+
detail: outcome.detail
|
|
27178
|
+
});
|
|
27179
|
+
}
|
|
27180
|
+
var NOT_RESTORED_MARK = "foreign work NOT restored";
|
|
27181
|
+
function notRestoredSentence(row2) {
|
|
27182
|
+
const how = row2.command === "" ? row2.detail : row2.command;
|
|
27183
|
+
return `${NOT_RESTORED_MARK} in ${row2.repo} — stash ${row2.hash.slice(0, 12)} ` + `still holds ${namePaths(row2.paths)}: ${how}`;
|
|
27184
|
+
}
|
|
27185
|
+
function notRestoredSummary(eventsText) {
|
|
27186
|
+
const failed2 = new Map;
|
|
27187
|
+
for (const line of eventsText.split(`
|
|
27188
|
+
`)) {
|
|
27189
|
+
if (line.trim() === "")
|
|
27190
|
+
continue;
|
|
27191
|
+
let event2;
|
|
27192
|
+
try {
|
|
27193
|
+
event2 = JSON.parse(line);
|
|
27194
|
+
} catch {
|
|
27195
|
+
continue;
|
|
27196
|
+
}
|
|
27197
|
+
if (event2.type !== FOREIGN_RESTORED_EVENT)
|
|
27198
|
+
continue;
|
|
27199
|
+
const payload = typeof event2.payload === "object" && event2.payload !== null ? event2.payload : {};
|
|
27200
|
+
const hash = typeof payload.stash_ref === "string" ? payload.stash_ref : "";
|
|
27201
|
+
if (hash === "")
|
|
27202
|
+
continue;
|
|
27203
|
+
if (payload.restored === true) {
|
|
27204
|
+
failed2.delete(hash);
|
|
27205
|
+
continue;
|
|
27206
|
+
}
|
|
27207
|
+
failed2.set(hash, notRestoredSentence({
|
|
27208
|
+
repo: typeof payload.repo === "string" ? payload.repo : "a repo",
|
|
27209
|
+
hash,
|
|
27210
|
+
paths: Array.isArray(payload.paths) ? payload.paths.filter((p) => typeof p === "string") : [],
|
|
27211
|
+
command: typeof payload.command === "string" ? payload.command : "",
|
|
27212
|
+
detail: typeof payload.detail === "string" ? payload.detail : "git refused the pop"
|
|
27213
|
+
}));
|
|
27214
|
+
}
|
|
27215
|
+
return failed2.size === 0 ? null : [...failed2.values()].join(" ");
|
|
27216
|
+
}
|
|
27217
|
+
function restoredLine(outcome) {
|
|
27218
|
+
const head2 = `${outcome.stash.repo}: foreign work restored from stash ` + `${outcome.stash.hash.slice(0, 12)} (${namePaths(outcome.stash.paths)})`;
|
|
27219
|
+
return outcome.indexRestored ? head2 : `${head2} — the staged snapshot could NOT be reinstated, so those paths are back UNSTAGED; ` + "`git add` them as you had them";
|
|
27220
|
+
}
|
|
27221
|
+
function unrestored(outcomes) {
|
|
27222
|
+
return outcomes.filter((outcome) => !outcome.restored);
|
|
27223
|
+
}
|
|
27224
|
+
var MAX_RECORDED_PATHS = 40;
|
|
27225
|
+
function asidePayload(stash, reason) {
|
|
27226
|
+
const omitted = stash.paths.length - MAX_RECORDED_PATHS;
|
|
27227
|
+
return {
|
|
27228
|
+
repo: stash.repo,
|
|
27229
|
+
paths: stash.paths.slice(0, MAX_RECORDED_PATHS),
|
|
27230
|
+
...omitted > 0 ? { paths_omitted: omitted } : {},
|
|
27231
|
+
stash_ref: stash.hash,
|
|
27232
|
+
reason
|
|
27233
|
+
};
|
|
27234
|
+
}
|
|
27235
|
+
function restoredPayload(outcome) {
|
|
27236
|
+
const paths = outcome.restored ? outcome.stash.paths : outcome.conflicts.length > 0 ? outcome.conflicts : outcome.stash.paths;
|
|
27237
|
+
const omitted = paths.length - MAX_RECORDED_PATHS;
|
|
27238
|
+
return {
|
|
27239
|
+
repo: outcome.stash.repo,
|
|
27240
|
+
paths: paths.slice(0, MAX_RECORDED_PATHS),
|
|
27241
|
+
...omitted > 0 ? { paths_omitted: omitted } : {},
|
|
27242
|
+
stash_ref: outcome.stash.hash,
|
|
27243
|
+
restored: outcome.restored,
|
|
27244
|
+
index_restored: outcome.indexRestored,
|
|
27245
|
+
...outcome.restored ? {} : { conflicts: outcome.conflicts.slice(0, MAX_RECORDED_PATHS) },
|
|
27246
|
+
...outcome.restored ? {} : { command: outcome.command },
|
|
27247
|
+
...outcome.restored ? {} : { detail: outcome.detail }
|
|
27248
|
+
};
|
|
27249
|
+
}
|
|
27250
|
+
function pendingAsides(eventsText, repoDirOf2) {
|
|
27251
|
+
const open = new Map;
|
|
27252
|
+
for (const line of eventsText.split(`
|
|
27253
|
+
`)) {
|
|
27254
|
+
if (line.trim() === "")
|
|
27255
|
+
continue;
|
|
27256
|
+
let event2;
|
|
27257
|
+
try {
|
|
27258
|
+
event2 = JSON.parse(line);
|
|
27259
|
+
} catch {
|
|
27260
|
+
continue;
|
|
27261
|
+
}
|
|
27262
|
+
const payload = typeof event2.payload === "object" && event2.payload !== null ? event2.payload : {};
|
|
27263
|
+
const hash = typeof payload.stash_ref === "string" ? payload.stash_ref : "";
|
|
27264
|
+
if (hash === "")
|
|
27265
|
+
continue;
|
|
27266
|
+
if (event2.type === FOREIGN_RESTORED_EVENT && payload.restored === true) {
|
|
27267
|
+
open.delete(hash);
|
|
27268
|
+
continue;
|
|
27269
|
+
}
|
|
27270
|
+
if (event2.type !== FOREIGN_ASIDE_EVENT)
|
|
27271
|
+
continue;
|
|
27272
|
+
const repo = typeof payload.repo === "string" ? payload.repo : "";
|
|
27273
|
+
const repoDir = repo === "" ? null : repoDirOf2(repo);
|
|
27274
|
+
if (repoDir === null)
|
|
27275
|
+
continue;
|
|
27276
|
+
open.set(hash, {
|
|
27277
|
+
repo,
|
|
27278
|
+
repoDir,
|
|
27279
|
+
paths: Array.isArray(payload.paths) ? payload.paths.filter((p) => typeof p === "string") : [],
|
|
27280
|
+
hash,
|
|
27281
|
+
message: ""
|
|
27282
|
+
});
|
|
27283
|
+
}
|
|
27284
|
+
return [...open.values()];
|
|
27285
|
+
}
|
|
27286
|
+
|
|
27287
|
+
// src/core/budget/wouldExceed.ts
|
|
27288
|
+
function totalSpent(budget) {
|
|
27289
|
+
return budget.phases.reduce((sum, p) => sum + p.spent_usd, 0);
|
|
27290
|
+
}
|
|
27291
|
+
function remaining(budget, phaseId) {
|
|
27292
|
+
if (phaseId !== undefined) {
|
|
27293
|
+
const phase = budget.phases.find((p) => p.id === phaseId);
|
|
27294
|
+
if (phase !== undefined)
|
|
27295
|
+
return round6(phase.ceiling_usd - phase.spent_usd);
|
|
27296
|
+
}
|
|
27297
|
+
return round6(budget.ceiling_usd - totalSpent(budget));
|
|
27298
|
+
}
|
|
27299
|
+
function wouldExceed(budget, phaseId, estimate) {
|
|
27300
|
+
const phase = phaseId === null ? undefined : budget.phases.find((p) => p.id === phaseId);
|
|
27301
|
+
const scope = phase === undefined ? "run" : "phase";
|
|
27302
|
+
const ceiling = phase === undefined ? budget.ceiling_usd : phase.ceiling_usd;
|
|
27303
|
+
const spent = phase === undefined ? totalSpent(budget) : phase.spent_usd;
|
|
27304
|
+
const left = round6(ceiling - spent);
|
|
27305
|
+
const exceeds = round6(spent + estimate) > ceiling;
|
|
27306
|
+
const pct = ceiling === 0 ? 100 : (spent + estimate) / ceiling * 100;
|
|
27307
|
+
return {
|
|
27308
|
+
exceeds,
|
|
27309
|
+
blocked: exceeds && budget.on_exceed === "block",
|
|
27310
|
+
scope,
|
|
27311
|
+
phaseId: phase === undefined ? null : phase.id,
|
|
27312
|
+
remaining: left,
|
|
27313
|
+
ceiling,
|
|
27314
|
+
estimate,
|
|
27315
|
+
warns: !exceeds && pct >= budget.warn_at_pct
|
|
27316
|
+
};
|
|
27317
|
+
}
|
|
27318
|
+
function round6(n) {
|
|
27319
|
+
return Math.round(n * 1e6) / 1e6;
|
|
27320
|
+
}
|
|
27321
|
+
function wouldExceedHostTokens(budget, phaseId, spentTokens) {
|
|
27322
|
+
const ceiling = hostTokenCeiling(budget, phaseId);
|
|
27323
|
+
if (ceiling === null)
|
|
27324
|
+
return null;
|
|
27325
|
+
const phase = phaseId === null ? undefined : budget.phases.find((p) => p.id === phaseId);
|
|
27326
|
+
const over = ceiling > 0 && spentTokens > ceiling;
|
|
27327
|
+
return {
|
|
27328
|
+
over,
|
|
27329
|
+
blocked: over && budget.on_host_tokens_exceed === "block",
|
|
27330
|
+
scope: phase === undefined ? "run" : "phase",
|
|
27331
|
+
spent: spentTokens,
|
|
27332
|
+
ceiling
|
|
27333
|
+
};
|
|
27334
|
+
}
|
|
27335
|
+
|
|
27336
|
+
// src/core/budget/remainingWork.ts
|
|
27337
|
+
import { existsSync as existsSync40, readFileSync as readFileSync36 } from "node:fs";
|
|
27338
|
+
import { join as join59 } from "node:path";
|
|
27339
|
+
|
|
26724
27340
|
// src/core/build/fixlist.ts
|
|
26725
|
-
import { existsSync as
|
|
26726
|
-
import { join as
|
|
27341
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync11, readdirSync as readdirSync20, readFileSync as readFileSync35, writeFileSync as writeFileSync10 } from "node:fs";
|
|
27342
|
+
import { join as join58 } from "node:path";
|
|
26727
27343
|
var FIXLIST_DIR = "fixlist";
|
|
26728
27344
|
var MAX_FIXLIST_ROUNDS = 1;
|
|
26729
27345
|
var DISPOSITIONS = ["fix-now", "defer-with-log", "refuted", "out-of-scope"];
|
|
@@ -26998,21 +27614,21 @@ function fixlistRel(phaseDir, storyId, round4) {
|
|
|
26998
27614
|
return `${phaseDir}/${FIXLIST_DIR}/${storyId}-${String(round4)}.md`;
|
|
26999
27615
|
}
|
|
27000
27616
|
function fixlistDir(runDir, phaseDir) {
|
|
27001
|
-
return
|
|
27617
|
+
return join58(runDir, phaseDir, FIXLIST_DIR);
|
|
27002
27618
|
}
|
|
27003
27619
|
function writeFixlist(runDir, phaseDir, parts) {
|
|
27004
27620
|
const dir = fixlistDir(runDir, phaseDir);
|
|
27005
27621
|
mkdirSync11(dir, { recursive: true });
|
|
27006
|
-
writeFileSync10(
|
|
27622
|
+
writeFileSync10(join58(dir, `${parts.storyId}-${String(parts.round)}.md`), renderFixlist(parts), "utf8");
|
|
27007
27623
|
return fixlistRel(phaseDir, parts.storyId, parts.round);
|
|
27008
27624
|
}
|
|
27009
27625
|
function fixlistRounds(runDir, phaseDir, storyId) {
|
|
27010
27626
|
const dir = fixlistDir(runDir, phaseDir);
|
|
27011
|
-
if (!
|
|
27627
|
+
if (!existsSync39(dir))
|
|
27012
27628
|
return [];
|
|
27013
27629
|
let entries;
|
|
27014
27630
|
try {
|
|
27015
|
-
entries =
|
|
27631
|
+
entries = readdirSync20(dir);
|
|
27016
27632
|
} catch {
|
|
27017
27633
|
return [];
|
|
27018
27634
|
}
|
|
@@ -27022,7 +27638,7 @@ function fixlistRounds(runDir, phaseDir, storyId) {
|
|
|
27022
27638
|
if (match === null)
|
|
27023
27639
|
continue;
|
|
27024
27640
|
const round4 = Number(match[1] ?? "0");
|
|
27025
|
-
const path =
|
|
27641
|
+
const path = join58(dir, entry);
|
|
27026
27642
|
rows.push({ round: round4, path, rel: fixlistRel(phaseDir, storyId, round4), findings: readFindings(path) });
|
|
27027
27643
|
}
|
|
27028
27644
|
return rows.sort((a, b) => a.round - b.round);
|
|
@@ -27032,11 +27648,11 @@ function latestFixlist(runDir, phaseDir, storyId) {
|
|
|
27032
27648
|
return rounds[rounds.length - 1] ?? null;
|
|
27033
27649
|
}
|
|
27034
27650
|
function readFixlistAt(path, rel) {
|
|
27035
|
-
if (!
|
|
27651
|
+
if (!existsSync39(path))
|
|
27036
27652
|
return null;
|
|
27037
27653
|
let text2;
|
|
27038
27654
|
try {
|
|
27039
|
-
text2 =
|
|
27655
|
+
text2 = readFileSync35(path, "utf8");
|
|
27040
27656
|
} catch {
|
|
27041
27657
|
return null;
|
|
27042
27658
|
}
|
|
@@ -27048,7 +27664,7 @@ function readFixlistAt(path, rel) {
|
|
|
27048
27664
|
}
|
|
27049
27665
|
function readFindings(path) {
|
|
27050
27666
|
try {
|
|
27051
|
-
return parseFixlistFile(
|
|
27667
|
+
return parseFixlistFile(readFileSync35(path, "utf8"));
|
|
27052
27668
|
} catch {
|
|
27053
27669
|
return [];
|
|
27054
27670
|
}
|
|
@@ -27412,7 +28028,7 @@ function measure(input) {
|
|
|
27412
28028
|
if (all.length === 0)
|
|
27413
28029
|
return fallback;
|
|
27414
28030
|
const ids = new Set(all.map((story) => story.id));
|
|
27415
|
-
const prices = progress.implicit ? new Map : loadPlanPrices(
|
|
28031
|
+
const prices = progress.implicit ? new Map : loadPlanPrices(join59(input.runDir, PLAN_DIR), ids).prices;
|
|
27416
28032
|
const verdicts = reviewVerdictsByStory(input.runDir);
|
|
27417
28033
|
const caps = new CapMath(input, prices, all.length);
|
|
27418
28034
|
const stories = [];
|
|
@@ -27561,12 +28177,12 @@ class CapMath {
|
|
|
27561
28177
|
}
|
|
27562
28178
|
function reviewVerdictsByStory(runDir) {
|
|
27563
28179
|
const counts = new Map;
|
|
27564
|
-
const path =
|
|
27565
|
-
if (!
|
|
28180
|
+
const path = join59(runDir, "events.jsonl");
|
|
28181
|
+
if (!existsSync40(path))
|
|
27566
28182
|
return counts;
|
|
27567
28183
|
let text3;
|
|
27568
28184
|
try {
|
|
27569
|
-
text3 =
|
|
28185
|
+
text3 = readFileSync36(path, "utf8");
|
|
27570
28186
|
} catch {
|
|
27571
28187
|
return counts;
|
|
27572
28188
|
}
|
|
@@ -27733,11 +28349,11 @@ function round7(n) {
|
|
|
27733
28349
|
}
|
|
27734
28350
|
|
|
27735
28351
|
// src/core/run/attempts.ts
|
|
27736
|
-
import { existsSync as
|
|
27737
|
-
import { join as
|
|
28352
|
+
import { existsSync as existsSync41, readFileSync as readFileSync37 } from "node:fs";
|
|
28353
|
+
import { join as join60 } from "node:path";
|
|
27738
28354
|
function stageAttempts(runDir, phase, stage2) {
|
|
27739
28355
|
const costs = [];
|
|
27740
|
-
for (const event2 of readEvents(
|
|
28356
|
+
for (const event2 of readEvents(join60(runDir, "events.jsonl"))) {
|
|
27741
28357
|
if (event2.type !== "agent.result" || event2.stage !== stage2)
|
|
27742
28358
|
continue;
|
|
27743
28359
|
const eventPhase = event2.payload.phase;
|
|
@@ -27754,12 +28370,12 @@ function renderAttempts(attempts) {
|
|
|
27754
28370
|
return `attempts: ${String(attempts.costs.length)} · ${each}`;
|
|
27755
28371
|
}
|
|
27756
28372
|
function readEvents(path) {
|
|
27757
|
-
if (!
|
|
28373
|
+
if (!existsSync41(path))
|
|
27758
28374
|
return [];
|
|
27759
28375
|
const events = [];
|
|
27760
28376
|
let text3;
|
|
27761
28377
|
try {
|
|
27762
|
-
text3 =
|
|
28378
|
+
text3 = readFileSync37(path, "utf8");
|
|
27763
28379
|
} catch {
|
|
27764
28380
|
return events;
|
|
27765
28381
|
}
|
|
@@ -27782,8 +28398,8 @@ import { existsSync as existsSync43, readFileSync as readFileSync39 } from "node
|
|
|
27782
28398
|
import { join as join62 } from "node:path";
|
|
27783
28399
|
|
|
27784
28400
|
// src/core/run/checks.ts
|
|
27785
|
-
import { existsSync as
|
|
27786
|
-
import { basename as basename9, join as
|
|
28401
|
+
import { existsSync as existsSync42, readFileSync as readFileSync38 } from "node:fs";
|
|
28402
|
+
import { basename as basename9, join as join61 } from "node:path";
|
|
27787
28403
|
var WRITE_TIME_ONLY = ["no-reask", "budget-gate", "dod"];
|
|
27788
28404
|
async function runChecks(checks, ctx) {
|
|
27789
28405
|
const outcomes = [];
|
|
@@ -27829,12 +28445,12 @@ function checkClaimSources(ctx) {
|
|
|
27829
28445
|
continue;
|
|
27830
28446
|
}
|
|
27831
28447
|
for (const hit of hits) {
|
|
27832
|
-
if (!
|
|
28448
|
+
if (!existsSync42(hit.absolute)) {
|
|
27833
28449
|
if (isHandoffOutput)
|
|
27834
28450
|
failures.push(`${hit.path} was declared but never written`);
|
|
27835
28451
|
continue;
|
|
27836
28452
|
}
|
|
27837
|
-
const text3 =
|
|
28453
|
+
const text3 = readFileSync38(hit.absolute, "utf8");
|
|
27838
28454
|
if (isHandoffOutput || isHandoff(text3)) {
|
|
27839
28455
|
handoffs++;
|
|
27840
28456
|
const validation = validateHandoff(text3, srcCtx);
|
|
@@ -27977,7 +28593,7 @@ function checkPlan(ctx) {
|
|
|
27977
28593
|
if (!writesPlanArtefacts(ctx.stage.outputs)) {
|
|
27978
28594
|
return { id: "plan", status: "skipped", detail: "the stage declares no waves.yml output" };
|
|
27979
28595
|
}
|
|
27980
|
-
const planDir =
|
|
28596
|
+
const planDir = join61(ctx.runDir, ctx.stage.phase);
|
|
27981
28597
|
const workspace = loadWorkspace(ctx.root);
|
|
27982
28598
|
const report2 = validatePlan(planDir, workspace.commands, workspace.iterationCommands);
|
|
27983
28599
|
if (!report2.ok) {
|
|
@@ -27996,10 +28612,10 @@ function checkSchema(ctx) {
|
|
|
27996
28612
|
["budget.yml", validateRunBudget]
|
|
27997
28613
|
];
|
|
27998
28614
|
for (const [name, validate2] of pairs) {
|
|
27999
|
-
const path =
|
|
28000
|
-
if (!
|
|
28615
|
+
const path = join61(ctx.runDir, name);
|
|
28616
|
+
if (!existsSync42(path))
|
|
28001
28617
|
return { id: "schema", status: "failed", detail: `${name} is missing` };
|
|
28002
|
-
const validation = validate2(parseYaml(
|
|
28618
|
+
const validation = validate2(parseYaml(readFileSync38(path, "utf8")));
|
|
28003
28619
|
if (!validation.ok) {
|
|
28004
28620
|
const first2 = validation.issues[0];
|
|
28005
28621
|
return { id: "schema", status: "failed", detail: `${name}: ${first2?.path ?? ""} ${first2?.message ?? ""}`.trim() };
|
|
@@ -28086,316 +28702,6 @@ ${stderr}`.split(`
|
|
|
28086
28702
|
return last.length > max ? `${last.slice(0, max - 1)}…` : last;
|
|
28087
28703
|
}
|
|
28088
28704
|
|
|
28089
|
-
// src/core/run/boundary.ts
|
|
28090
|
-
import { existsSync as existsSync42, readFileSync as readFileSync38, readdirSync as readdirSync20 } from "node:fs";
|
|
28091
|
-
import { join as join61 } from "node:path";
|
|
28092
|
-
var OUTSIDE_SURFACE = "work outside the declared surface is a boundary change — a human decides whether to widen the scope";
|
|
28093
|
-
var NAMED_PATHS = 8;
|
|
28094
|
-
var SURFACE_HANDOFFS = ["01-what/handoff.md", "02-how/handoff.md"];
|
|
28095
|
-
var STORIES_DIR2 = "stories";
|
|
28096
|
-
var EPICS_DIR2 = "epics";
|
|
28097
|
-
function normalisePath2(path) {
|
|
28098
|
-
let out = path.trim().split("\\").join("/");
|
|
28099
|
-
while (out.startsWith("./"))
|
|
28100
|
-
out = out.slice(2);
|
|
28101
|
-
while (out.endsWith("/"))
|
|
28102
|
-
out = out.slice(0, -1);
|
|
28103
|
-
return out;
|
|
28104
|
-
}
|
|
28105
|
-
function inSurface(changed, surface) {
|
|
28106
|
-
const path = normalisePath2(changed);
|
|
28107
|
-
for (const raw of surface) {
|
|
28108
|
-
const entry = normalisePath2(raw);
|
|
28109
|
-
if (entry === "")
|
|
28110
|
-
continue;
|
|
28111
|
-
if (path === entry || path.startsWith(`${entry}/`))
|
|
28112
|
-
return true;
|
|
28113
|
-
}
|
|
28114
|
-
return false;
|
|
28115
|
-
}
|
|
28116
|
-
function unqualifiedCitedPaths(handoffText, repos) {
|
|
28117
|
-
const out = [];
|
|
28118
|
-
const seen = new Set;
|
|
28119
|
-
if (handoffText === "")
|
|
28120
|
-
return out;
|
|
28121
|
-
for (const line of handoffText.split(`
|
|
28122
|
-
`)) {
|
|
28123
|
-
const token = parseSrcToken(line, repos);
|
|
28124
|
-
if (token === null)
|
|
28125
|
-
continue;
|
|
28126
|
-
for (const ref of token.refs) {
|
|
28127
|
-
if (ref.kind !== "file" || ref.repo !== null)
|
|
28128
|
-
continue;
|
|
28129
|
-
const path = normalisePath2(ref.path);
|
|
28130
|
-
if (path === "" || seen.has(path))
|
|
28131
|
-
continue;
|
|
28132
|
-
seen.add(path);
|
|
28133
|
-
out.push(path);
|
|
28134
|
-
}
|
|
28135
|
-
}
|
|
28136
|
-
return out;
|
|
28137
|
-
}
|
|
28138
|
-
function storyTouches(runDir) {
|
|
28139
|
-
const dir = join61(runDir, PLAN_DIR, STORIES_DIR2);
|
|
28140
|
-
if (!existsSync42(dir))
|
|
28141
|
-
return [];
|
|
28142
|
-
const out = [];
|
|
28143
|
-
let names;
|
|
28144
|
-
try {
|
|
28145
|
-
names = readdirSync20(dir).filter((name) => name.endsWith(".md")).sort();
|
|
28146
|
-
} catch {
|
|
28147
|
-
return [];
|
|
28148
|
-
}
|
|
28149
|
-
for (const name of names) {
|
|
28150
|
-
const front2 = readFront(join61(dir, name));
|
|
28151
|
-
if (front2 === null)
|
|
28152
|
-
continue;
|
|
28153
|
-
const repo = typeof front2.repo === "string" ? front2.repo : "";
|
|
28154
|
-
const id = typeof front2.id === "string" && front2.id !== "" ? front2.id : name.replace(/\.md$/, "");
|
|
28155
|
-
out.push({ story: id, repo, touches: stringList2(front2.touches) });
|
|
28156
|
-
}
|
|
28157
|
-
return out;
|
|
28158
|
-
}
|
|
28159
|
-
function declaredTouchesFor(runDir, storyId) {
|
|
28160
|
-
for (const row2 of storyTouches(runDir)) {
|
|
28161
|
-
if (row2.story === storyId)
|
|
28162
|
-
return row2.touches;
|
|
28163
|
-
}
|
|
28164
|
-
const implicit = implicitStorySurface(runDir);
|
|
28165
|
-
return implicit !== null && implicit.story === storyId ? implicit.touches : null;
|
|
28166
|
-
}
|
|
28167
|
-
function implicitStorySurface(runDir) {
|
|
28168
|
-
const doc = readYaml(join61(runDir, BUILD_PHASE2, IMPLICIT_PLAN_FILE));
|
|
28169
|
-
const story = doc?.story;
|
|
28170
|
-
if (story === null || typeof story !== "object")
|
|
28171
|
-
return null;
|
|
28172
|
-
const row2 = story;
|
|
28173
|
-
return {
|
|
28174
|
-
story: typeof row2.id === "string" && row2.id !== "" ? row2.id : IMPLICIT_STORY_ID,
|
|
28175
|
-
repo: typeof row2.repo === "string" ? row2.repo : "",
|
|
28176
|
-
touches: stringList2(row2.touches)
|
|
28177
|
-
};
|
|
28178
|
-
}
|
|
28179
|
-
function deriveSurface(runDir, workspace) {
|
|
28180
|
-
const byRepo = new Map;
|
|
28181
|
-
const unqualified = [];
|
|
28182
|
-
const excluded = [];
|
|
28183
|
-
let cited = 0;
|
|
28184
|
-
let declared = 0;
|
|
28185
|
-
const add = (repo, path, count) => {
|
|
28186
|
-
const entry = normalisePath2(path);
|
|
28187
|
-
if (entry === "")
|
|
28188
|
-
return;
|
|
28189
|
-
if (isStatePath(entry)) {
|
|
28190
|
-
if (!excluded.includes(entry))
|
|
28191
|
-
excluded.push(entry);
|
|
28192
|
-
return;
|
|
28193
|
-
}
|
|
28194
|
-
count();
|
|
28195
|
-
if (repo === "") {
|
|
28196
|
-
if (!unqualified.includes(entry))
|
|
28197
|
-
unqualified.push(entry);
|
|
28198
|
-
return;
|
|
28199
|
-
}
|
|
28200
|
-
const list = byRepo.get(repo) ?? [];
|
|
28201
|
-
if (!list.includes(entry))
|
|
28202
|
-
list.push(entry);
|
|
28203
|
-
byRepo.set(repo, list);
|
|
28204
|
-
};
|
|
28205
|
-
const repoNames = new Set(workspace.repos.keys());
|
|
28206
|
-
for (const rel of SURFACE_HANDOFFS) {
|
|
28207
|
-
const text3 = readText2(join61(runDir, rel));
|
|
28208
|
-
if (text3 === "")
|
|
28209
|
-
continue;
|
|
28210
|
-
for (const entry of citedRepoPaths(text3, workspace)) {
|
|
28211
|
-
add(entry.repo, entry.path, () => {
|
|
28212
|
-
cited += 1;
|
|
28213
|
-
});
|
|
28214
|
-
}
|
|
28215
|
-
for (const path of unqualifiedCitedPaths(text3, repoNames)) {
|
|
28216
|
-
add("", path, () => {
|
|
28217
|
-
cited += 1;
|
|
28218
|
-
});
|
|
28219
|
-
}
|
|
28220
|
-
}
|
|
28221
|
-
const stories = storyTouches(runDir);
|
|
28222
|
-
const sources = stories.length > 0 ? stories : [implicitStorySurface(runDir)].filter((s) => s !== null);
|
|
28223
|
-
for (const story of sources) {
|
|
28224
|
-
for (const path of story.touches) {
|
|
28225
|
-
add(story.repo, path, () => {
|
|
28226
|
-
declared += 1;
|
|
28227
|
-
});
|
|
28228
|
-
}
|
|
28229
|
-
}
|
|
28230
|
-
return { byRepo, unqualified, cited, declared, excluded };
|
|
28231
|
-
}
|
|
28232
|
-
function epicTargets2(runDir, workspace) {
|
|
28233
|
-
const out = [];
|
|
28234
|
-
const seen = new Set;
|
|
28235
|
-
const push2 = (epic2, repo, branch) => {
|
|
28236
|
-
if (repo === "" || branch === "")
|
|
28237
|
-
return;
|
|
28238
|
-
const key = `${repo}\x00${branch}`;
|
|
28239
|
-
if (seen.has(key))
|
|
28240
|
-
return;
|
|
28241
|
-
seen.add(key);
|
|
28242
|
-
const rel = repoPath(workspace, repo);
|
|
28243
|
-
out.push({
|
|
28244
|
-
epic: epic2,
|
|
28245
|
-
repo,
|
|
28246
|
-
branch,
|
|
28247
|
-
base: workspace.defaultBranches.get(repo) ?? "main",
|
|
28248
|
-
dir: rel
|
|
28249
|
-
});
|
|
28250
|
-
};
|
|
28251
|
-
const dir = join61(runDir, PLAN_DIR, EPICS_DIR2);
|
|
28252
|
-
if (existsSync42(dir)) {
|
|
28253
|
-
let names = [];
|
|
28254
|
-
try {
|
|
28255
|
-
names = readdirSync20(dir).filter((name) => name.endsWith(".md")).sort();
|
|
28256
|
-
} catch {
|
|
28257
|
-
names = [];
|
|
28258
|
-
}
|
|
28259
|
-
for (const name of names) {
|
|
28260
|
-
const front2 = readFront(join61(dir, name));
|
|
28261
|
-
if (front2 === null)
|
|
28262
|
-
continue;
|
|
28263
|
-
const id = typeof front2.id === "string" ? front2.id : name.replace(/\.md$/, "");
|
|
28264
|
-
const branch = typeof front2.branch === "string" ? front2.branch : "";
|
|
28265
|
-
const repos = stringList2(front2.repos);
|
|
28266
|
-
const fallback = storyTouches(runDir).map((story) => story.repo).filter((repo) => repo !== "");
|
|
28267
|
-
for (const repo of repos.length > 0 ? repos : [...new Set(fallback)])
|
|
28268
|
-
push2(id, repo, branch);
|
|
28269
|
-
}
|
|
28270
|
-
if (out.length > 0)
|
|
28271
|
-
return out;
|
|
28272
|
-
}
|
|
28273
|
-
const doc = readYaml(join61(runDir, BUILD_PHASE2, IMPLICIT_PLAN_FILE));
|
|
28274
|
-
const epic = doc?.epic;
|
|
28275
|
-
if (epic !== null && typeof epic === "object") {
|
|
28276
|
-
const row2 = epic;
|
|
28277
|
-
const id = typeof row2.id === "string" ? row2.id : "E1";
|
|
28278
|
-
const branch = typeof row2.branch === "string" ? row2.branch : "";
|
|
28279
|
-
for (const repo of stringList2(row2.repos))
|
|
28280
|
-
push2(id, repo, branch);
|
|
28281
|
-
}
|
|
28282
|
-
return out;
|
|
28283
|
-
}
|
|
28284
|
-
async function diffRepo(target) {
|
|
28285
|
-
if (target.dir === null)
|
|
28286
|
-
return { changed: null, reason: `${target.repo} is not in ${PROJECT_WORKSPACE_FILE}` };
|
|
28287
|
-
if (!existsSync42(target.dir))
|
|
28288
|
-
return { changed: null, reason: `${target.repo} is not on disk` };
|
|
28289
|
-
const baseCheck = await git(["rev-parse", "--verify", "--quiet", `${target.base}^{commit}`], target.dir);
|
|
28290
|
-
if (!baseCheck.ok) {
|
|
28291
|
-
return {
|
|
28292
|
-
changed: null,
|
|
28293
|
-
reason: `\`${target.base}\`, the \`default_branch\` ${PROJECT_WORKSPACE_FILE} records for ${target.repo}, does not ` + "resolve there — the record is stale, so nothing in this repo has a base to be diffed against " + "(run `tldrx doctor`)"
|
|
28294
|
-
};
|
|
28295
|
-
}
|
|
28296
|
-
const branchCheck = await git(["rev-parse", "--verify", "--quiet", `${target.branch}^{commit}`], target.dir);
|
|
28297
|
-
if (!branchCheck.ok) {
|
|
28298
|
-
return { changed: null, reason: `\`${target.branch}\` does not resolve in ${target.repo}` };
|
|
28299
|
-
}
|
|
28300
|
-
const range = `${target.base}...${target.branch}`;
|
|
28301
|
-
const diff = await git(["diff", "--name-only", range], target.dir);
|
|
28302
|
-
if (!diff.ok)
|
|
28303
|
-
return { changed: null, reason: `\`git diff ${range}\` failed in ${target.repo}` };
|
|
28304
|
-
const changed = diff.stdout.split(`
|
|
28305
|
-
`).map(normalisePath2).filter((path) => path !== "");
|
|
28306
|
-
return { changed, reason: null };
|
|
28307
|
-
}
|
|
28308
|
-
async function evaluateBoundary(input) {
|
|
28309
|
-
if (input.phaseId !== BUILD_PHASE2)
|
|
28310
|
-
return { ok: true, detail: "n/a (not a build stage)" };
|
|
28311
|
-
const workspace = loadWorkspace(input.root);
|
|
28312
|
-
const targets = epicTargets2(input.runDir, workspace);
|
|
28313
|
-
if (targets.length === 0)
|
|
28314
|
-
return { ok: true, detail: "n/a (no plan naming an epic branch)" };
|
|
28315
|
-
const surface = deriveSurface(input.runDir, workspace);
|
|
28316
|
-
const excludedPart = surface.excluded.length === 0 ? "" : `; ${String(surface.excluded.length)} state path(s) excluded from the surface: ` + surface.excluded.slice(0, NAMED_PATHS).join(", ") + (surface.excluded.length > NAMED_PATHS ? `, +${String(surface.excluded.length - NAMED_PATHS)} more` : "");
|
|
28317
|
-
if (surface.cited + surface.declared === 0) {
|
|
28318
|
-
return {
|
|
28319
|
-
ok: true,
|
|
28320
|
-
detail: `n/a (the run declares no surface: no cited path, no \`touches:\`)${excludedPart}`
|
|
28321
|
-
};
|
|
28322
|
-
}
|
|
28323
|
-
const repos = [];
|
|
28324
|
-
for (const target of targets) {
|
|
28325
|
-
const { changed, reason } = await diffRepo(target);
|
|
28326
|
-
if (changed === null) {
|
|
28327
|
-
repos.push({ target, changed: null, reason, outside: [], state: [] });
|
|
28328
|
-
continue;
|
|
28329
|
-
}
|
|
28330
|
-
const allowed = [...surface.byRepo.get(target.repo) ?? [], ...surface.unqualified];
|
|
28331
|
-
const state = changed.filter(isStatePath);
|
|
28332
|
-
const product = changed.filter((path) => !isStatePath(path));
|
|
28333
|
-
repos.push({
|
|
28334
|
-
target,
|
|
28335
|
-
changed: product,
|
|
28336
|
-
reason: null,
|
|
28337
|
-
outside: product.filter((path) => !inSurface(path, allowed)),
|
|
28338
|
-
state
|
|
28339
|
-
});
|
|
28340
|
-
}
|
|
28341
|
-
const measured = repos.filter((repo) => repo.changed !== null);
|
|
28342
|
-
if (measured.length === 0) {
|
|
28343
|
-
const why = repos.map((repo) => repo.reason ?? "unavailable").join("; ");
|
|
28344
|
-
return { ok: true, detail: `n/a (nothing could be diffed: ${why})${excludedPart}` };
|
|
28345
|
-
}
|
|
28346
|
-
const audited = measured.reduce((sum, repo) => sum + (repo.changed?.length ?? 0), 0);
|
|
28347
|
-
const outside = measured.flatMap((repo) => repo.outside.map((path) => `${repo.target.repo}:${path}`));
|
|
28348
|
-
const unread = repos.filter((repo) => repo.changed === null);
|
|
28349
|
-
const unreadPart = unread.length === 0 ? "" : `; not diffed: ${unread.map((repo) => repo.reason ?? "unavailable").join(", ")}`;
|
|
28350
|
-
const range = measured.map((repo) => `${repo.target.repo} ${repo.target.base}...${repo.target.branch}`).join(", ");
|
|
28351
|
-
if (outside.length === 0) {
|
|
28352
|
-
return {
|
|
28353
|
-
ok: true,
|
|
28354
|
-
detail: `${String(audited)} changed path(s), 0 outside the surface (${range})${unreadPart}${excludedPart}`
|
|
28355
|
-
};
|
|
28356
|
-
}
|
|
28357
|
-
const named = outside.slice(0, NAMED_PATHS);
|
|
28358
|
-
const rest = outside.length - named.length;
|
|
28359
|
-
return {
|
|
28360
|
-
ok: false,
|
|
28361
|
-
detail: `${String(audited)} changed path(s), ${String(outside.length)} outside the surface: ` + `${named.join(", ")}${rest > 0 ? `, +${String(rest)} more` : ""}` + `${unreadPart}${excludedPart}; ${OUTSIDE_SURFACE}`
|
|
28362
|
-
};
|
|
28363
|
-
}
|
|
28364
|
-
function readText2(path) {
|
|
28365
|
-
if (!existsSync42(path))
|
|
28366
|
-
return "";
|
|
28367
|
-
try {
|
|
28368
|
-
return readFileSync38(path, "utf8");
|
|
28369
|
-
} catch {
|
|
28370
|
-
return "";
|
|
28371
|
-
}
|
|
28372
|
-
}
|
|
28373
|
-
function readYaml(path) {
|
|
28374
|
-
const text3 = readText2(path);
|
|
28375
|
-
if (text3 === "")
|
|
28376
|
-
return null;
|
|
28377
|
-
try {
|
|
28378
|
-
return parseYaml(text3);
|
|
28379
|
-
} catch {
|
|
28380
|
-
return null;
|
|
28381
|
-
}
|
|
28382
|
-
}
|
|
28383
|
-
function readFront(path) {
|
|
28384
|
-
const text3 = readText2(path);
|
|
28385
|
-
if (text3 === "")
|
|
28386
|
-
return null;
|
|
28387
|
-
const parsed = parseFrontMatter(text3);
|
|
28388
|
-
const doc = parsed.doc;
|
|
28389
|
-
if (doc === null || typeof doc !== "object" || Array.isArray(doc))
|
|
28390
|
-
return null;
|
|
28391
|
-
return doc;
|
|
28392
|
-
}
|
|
28393
|
-
function stringList2(value) {
|
|
28394
|
-
if (!Array.isArray(value))
|
|
28395
|
-
return [];
|
|
28396
|
-
return value.filter((item) => typeof item === "string");
|
|
28397
|
-
}
|
|
28398
|
-
|
|
28399
28705
|
// src/core/run/autoGate.ts
|
|
28400
28706
|
var AUTO_GATE_ACTOR = "auto";
|
|
28401
28707
|
async function evaluateAutoGate(input) {
|
|
@@ -29696,11 +30002,11 @@ function gateTimeoutNotification(ctx, costUsd, waitedMs, policy = null) {
|
|
|
29696
30002
|
}
|
|
29697
30003
|
};
|
|
29698
30004
|
}
|
|
29699
|
-
function stageDoneNotification(ctx, costUsd, unmetered = 0) {
|
|
30005
|
+
function stageDoneNotification(ctx, costUsd, unmetered = 0, note = null) {
|
|
29700
30006
|
const figure = spentFigure({ usd: costUsd, unmetered, metered: unmetered > 0 && costUsd === 0 ? 0 : 1 });
|
|
29701
30007
|
return {
|
|
29702
30008
|
...base(ctx, "stage.done"),
|
|
29703
|
-
summary: `${ctx.runId} finished ${ctx.stage ?? "a stage"} for ${figure} and moved on. ` + "No decision is waiting on you.",
|
|
30009
|
+
summary: `${ctx.runId} finished ${ctx.stage ?? "a stage"} for ${figure} and moved on. ` + (note === null ? "No decision is waiting on you." : note),
|
|
29704
30010
|
command: null,
|
|
29705
30011
|
detail: { cost_usd: costUsd, ...unmetered === 0 ? {} : { unmetered_tasks: unmetered } }
|
|
29706
30012
|
};
|
|
@@ -29718,12 +30024,12 @@ function budgetNotification(ctx, spentUsd, ceilingUsd, tally = { usd: spentUsd,
|
|
|
29718
30024
|
}
|
|
29719
30025
|
};
|
|
29720
30026
|
}
|
|
29721
|
-
function runEndNotification(ctx, exitCode, spentUsd, lastLine2, tally = { usd: spentUsd, unmetered: 0, metered: 1 }) {
|
|
30027
|
+
function runEndNotification(ctx, exitCode, spentUsd, lastLine2, tally = { usd: spentUsd, unmetered: 0, metered: 1 }, note = null) {
|
|
29722
30028
|
const kind = exitCode === 0 ? "run.finished" : "run.failed";
|
|
29723
30029
|
const verb = exitCode === 0 ? "finished" : "stopped";
|
|
29724
30030
|
return {
|
|
29725
30031
|
...base(ctx, kind),
|
|
29726
|
-
summary: `${ctx.runId}: the loop ${verb} with exit ${String(exitCode)} ` + `(${exitFamily(exitCode)}), ${spentFigure(tally)} spent by this loop. ${lastLine2}`,
|
|
30032
|
+
summary: `${ctx.runId}: the loop ${verb} with exit ${String(exitCode)} ` + `(${exitFamily(exitCode)}), ${spentFigure(tally)} spent by this loop. ${lastLine2}` + `${note === null ? "" : ` ${note}`}`,
|
|
29727
30033
|
command: exitCode === 0 ? null : `tldrx run status ${ctx.runId}`,
|
|
29728
30034
|
detail: {
|
|
29729
30035
|
exit_code: exitCode,
|
|
@@ -32762,10 +33068,11 @@ function renderBuildHandoff(parts) {
|
|
|
32762
33068
|
"",
|
|
32763
33069
|
"## Unknowns",
|
|
32764
33070
|
"",
|
|
32765
|
-
...notDone.length === 0 && (parts.carried ?? []).length === 0 && (parts.unreadableStories ?? []).length === 0 ? [`- none — every scheduled story reached \`done\` and no carried finding is unowned ` + `[src: absent:04-build/log]`] : [],
|
|
33071
|
+
...notDone.length === 0 && (parts.carried ?? []).length === 0 && (parts.unreadableStories ?? []).length === 0 && (parts.foreignWork ?? []).length === 0 ? [`- none — every scheduled story reached \`done\` and no carried finding is unowned ` + `[src: absent:04-build/log]`] : [],
|
|
32766
33072
|
...notDone.map((o) => `- ${o.id} is \`${o.status}\` and needs a human: ${o.reason ?? "see the review"} ` + `[src: ${o.reviewRel}:1]`),
|
|
32767
33073
|
...carriedBullets(parts.carried ?? []),
|
|
32768
33074
|
...(parts.unreadableStories ?? []).map((row2) => `- a story file could not be read, so its carried findings were not checked: ` + `\`${row2.rel}\` — ${row2.reason} [src: absent:${row2.rel}]`),
|
|
33075
|
+
...(parts.foreignWork ?? []).map((row2) => `- uncommitted work this stage set aside in \`${row2.repo}\` was NOT restored and needs a human: ` + `stash \`${row2.stashRef.slice(0, 12)}\` still holds ${row2.paths.join(", ")} — ${row2.detail}. ` + `Take it back with \`${row2.command === "" ? `git -C <${row2.repo}> stash list` : row2.command}\` ` + `[src: absent:04-build/log]`),
|
|
32769
33076
|
"",
|
|
32770
33077
|
"## Evidence ledger",
|
|
32771
33078
|
"",
|
|
@@ -33324,36 +33631,65 @@ async function foreignEpicRefusal(state, parts, stories) {
|
|
|
33324
33631
|
}
|
|
33325
33632
|
return null;
|
|
33326
33633
|
}
|
|
33634
|
+
function relaunchCommand(mode, runId) {
|
|
33635
|
+
return mode === "headless" ? `tldrx run auto ${runId}` : "tldrx next";
|
|
33636
|
+
}
|
|
33327
33637
|
async function dirtyRepoRefusal(parts, stories) {
|
|
33328
33638
|
const seen = new Set;
|
|
33639
|
+
const aside = [];
|
|
33329
33640
|
let ignored = 0;
|
|
33641
|
+
const refuse2 = (refusal) => ({ refusal, ignored, aside: [] });
|
|
33330
33642
|
for (const planned of stories) {
|
|
33331
33643
|
const name = planned.story.repo;
|
|
33332
33644
|
if (seen.has(name))
|
|
33333
33645
|
continue;
|
|
33334
33646
|
seen.add(name);
|
|
33335
33647
|
const dir = repoDirOf(parts.workspace, name);
|
|
33336
|
-
const
|
|
33337
|
-
|
|
33338
|
-
const
|
|
33339
|
-
|
|
33648
|
+
const entries = await dirtyEntries(dir);
|
|
33649
|
+
const touches = stories.filter((row2) => row2.story.repo === name).flatMap((row2) => [...row2.story.touches]);
|
|
33650
|
+
const verdict2 = classifyDirty({
|
|
33651
|
+
entries,
|
|
33652
|
+
statePrefixes: stateDirPrefixes(parts.workspace.root, dir),
|
|
33653
|
+
touches,
|
|
33654
|
+
submodules: await submodulePaths(dir)
|
|
33655
|
+
});
|
|
33656
|
+
ignored += verdict2.own.length;
|
|
33657
|
+
if (verdict2.foreign.length === 0 && verdict2.overlapping.length === 0)
|
|
33340
33658
|
continue;
|
|
33341
33659
|
const branch = await currentBranch(dir);
|
|
33342
|
-
|
|
33343
|
-
|
|
33344
|
-
|
|
33345
|
-
|
|
33346
|
-
|
|
33347
|
-
|
|
33348
|
-
|
|
33349
|
-
|
|
33350
|
-
|
|
33351
|
-
|
|
33352
|
-
|
|
33353
|
-
|
|
33354
|
-
|
|
33660
|
+
const busy = await operationInProgress(dir);
|
|
33661
|
+
if (busy !== null) {
|
|
33662
|
+
return refuse2({
|
|
33663
|
+
lines: [
|
|
33664
|
+
`[tldrx] build: repo \`${name}\` is in the middle of ${busy} on \`${branch}\` — refusing to cut ` + "an epic branch, and refusing to stash anything into that state.",
|
|
33665
|
+
` ${namePaths(entries.map((entry) => entry.entry))}`,
|
|
33666
|
+
` Finish or abort ${busy} first, then run \`${relaunchCommand(parts.mode, parts.runId)}\`.`
|
|
33667
|
+
],
|
|
33668
|
+
error: `repo \`${name}\` is in the middle of ${busy}`
|
|
33669
|
+
});
|
|
33670
|
+
}
|
|
33671
|
+
if (verdict2.overlapping.length > 0) {
|
|
33672
|
+
const paths = verdict2.overlapping.map((row2) => row2.entry.path);
|
|
33673
|
+
const setAside = verdict2.foreign.map((entry) => entry.path);
|
|
33674
|
+
return refuse2({
|
|
33675
|
+
lines: [
|
|
33676
|
+
`[tldrx] build: repo \`${name}\` has ${String(paths.length)} uncommitted change(s) on ` + `\`${branch}\` that a pending story is about to write — refusing to cut an epic branch ` + "from a dirty tree.",
|
|
33677
|
+
` ${namePaths(verdict2.overlapping.map((row2) => row2.entry.entry))}`,
|
|
33678
|
+
...verdict2.overlapping.slice(0, NAMED_PATHS2).map((row2) => ` · ${row2.entry.path}: ${row2.why}`),
|
|
33679
|
+
" Why: the base pre-flight runs in this checkout, so uncommitted product changes here",
|
|
33680
|
+
" land inside the measurement that decides whether a red DoD is the story's fault.",
|
|
33681
|
+
" Commit them, or set exactly these paths aside and take them back afterwards:",
|
|
33682
|
+
` ${stashCommand(dir, parts.runId, paths)}`,
|
|
33683
|
+
` ${relaunchCommand(parts.mode, parts.runId)}`,
|
|
33684
|
+
` git -C ${shellQuote2(dir)} stash pop`,
|
|
33685
|
+
...setAside.length === 0 ? [] : [` The other ${String(setAside.length)} change(s) here are nobody's story — ` + "the engine would have set those aside and given them back itself."]
|
|
33686
|
+
],
|
|
33687
|
+
error: `repo \`${name}\` has uncommitted changes a pending story declares`
|
|
33688
|
+
});
|
|
33689
|
+
}
|
|
33690
|
+
aside.push({ repo: name, repoDir: dir, paths: verdict2.foreign.map((entry) => entry.path) });
|
|
33355
33691
|
}
|
|
33356
|
-
return { refusal: null, ignored };
|
|
33692
|
+
return { refusal: null, ignored, aside };
|
|
33357
33693
|
}
|
|
33358
33694
|
function epicRows(state, parts, outcomes) {
|
|
33359
33695
|
const rows = [];
|
|
@@ -35659,20 +35995,28 @@ async function buildExecutor(ctx) {
|
|
|
35659
35995
|
epicBranches: [...session.claimedEpics],
|
|
35660
35996
|
branchModel: session.branchModel.kind
|
|
35661
35997
|
};
|
|
35998
|
+
const withRestore = async (outcome) => {
|
|
35999
|
+
const lines = await session.restoreForeignWorkAside();
|
|
36000
|
+
return withClaims(lines.length === 0 ? outcome : { ...outcome, lines: [...outcome.lines, ...lines] });
|
|
36001
|
+
};
|
|
35662
36002
|
try {
|
|
35663
36003
|
if (ctx.mode === "prepare") {
|
|
35664
|
-
return
|
|
36004
|
+
return await withRestore(ctx.review ? await session.prepareReviewOnly() : await session.prepare());
|
|
35665
36005
|
}
|
|
35666
36006
|
if (ctx.mode === "commit") {
|
|
35667
|
-
return
|
|
36007
|
+
return await withRestore(ctx.review ? await session.commitReview() : await session.commit());
|
|
35668
36008
|
}
|
|
35669
|
-
return
|
|
36009
|
+
return await withRestore(await session.runAll());
|
|
35670
36010
|
} catch (error) {
|
|
35671
36011
|
if (error instanceof BaseGateFailure)
|
|
35672
|
-
return
|
|
36012
|
+
return await withRestore(refusedOnBase(session, error));
|
|
35673
36013
|
if (error instanceof GitError || error instanceof PlanLoadError) {
|
|
35674
|
-
|
|
36014
|
+
const outcome = failed3(ctx, error.message, session.tasks);
|
|
36015
|
+
return await withRestore({ ...outcome, lines: [...session.reportLines, ...outcome.lines] });
|
|
35675
36016
|
}
|
|
36017
|
+
try {
|
|
36018
|
+
await session.restoreForeignWorkAside();
|
|
36019
|
+
} catch {}
|
|
35676
36020
|
throw error;
|
|
35677
36021
|
}
|
|
35678
36022
|
}
|
|
@@ -35686,6 +36030,7 @@ function refusedOnBase(session, error) {
|
|
|
35686
36030
|
costUsd: session.tasks.reduce((sum, task2) => sum + task2.costUsd, 0),
|
|
35687
36031
|
outputs: [],
|
|
35688
36032
|
lines: [
|
|
36033
|
+
...session.reportLines,
|
|
35689
36034
|
`[tldrx] build: ${who} was not blocked — its Definition of Done failed for a reason the base tree shares.`,
|
|
35690
36035
|
...baseRefusalLines([error.result])
|
|
35691
36036
|
],
|
|
@@ -35774,6 +36119,9 @@ class BuildSession {
|
|
|
35774
36119
|
branchModel;
|
|
35775
36120
|
writes = new SerialQueue;
|
|
35776
36121
|
advisories = [];
|
|
36122
|
+
restores = [];
|
|
36123
|
+
restoreAttempted = new Set;
|
|
36124
|
+
aside = [];
|
|
35777
36125
|
preflight;
|
|
35778
36126
|
lanes;
|
|
35779
36127
|
constructor(ctx, workspace, plan, opening = []) {
|
|
@@ -35791,7 +36139,7 @@ class BuildSession {
|
|
|
35791
36139
|
this.advisories.push(plan.priceIssue);
|
|
35792
36140
|
}
|
|
35793
36141
|
async runAll() {
|
|
35794
|
-
const refusal = await this.refuseOnDirtyRepos() ?? await this.refuseOnForeignEpic() ?? await this.refuseOnRedBase();
|
|
36142
|
+
const refusal = await this.refuseOnDirtyRepos() ?? await this.refuseOnForeignEpic() ?? await this.setAsideForeign() ?? await this.refuseOnRedBase();
|
|
35795
36143
|
if (refusal !== null)
|
|
35796
36144
|
return refusal;
|
|
35797
36145
|
this.recordGateFeedback();
|
|
@@ -35832,7 +36180,7 @@ class BuildSession {
|
|
|
35832
36180
|
const planned = this.nextPending();
|
|
35833
36181
|
if (planned === null)
|
|
35834
36182
|
return await this.finish();
|
|
35835
|
-
const refusal = await this.refuseOnDirtyRepos() ?? await this.refuseOnForeignEpic() ?? await this.refuseOnRedBase();
|
|
36183
|
+
const refusal = await this.refuseOnDirtyRepos() ?? await this.refuseOnForeignEpic() ?? await this.setAsideForeign() ?? await this.refuseOnRedBase();
|
|
35836
36184
|
if (refusal !== null)
|
|
35837
36185
|
return refusal;
|
|
35838
36186
|
const review = this.reviewWorkFor(planned);
|
|
@@ -35914,7 +36262,7 @@ class BuildSession {
|
|
|
35914
36262
|
const planned = this.nextPending();
|
|
35915
36263
|
if (planned === null)
|
|
35916
36264
|
return await this.finish();
|
|
35917
|
-
const refusal = await this.refuseOnDirtyRepos() ?? await this.refuseOnForeignEpic();
|
|
36265
|
+
const refusal = await this.refuseOnDirtyRepos() ?? await this.refuseOnForeignEpic() ?? await this.setAsideForeign();
|
|
35918
36266
|
if (refusal !== null)
|
|
35919
36267
|
return refusal;
|
|
35920
36268
|
const work = this.reviewWorkFor(planned) ?? this.reviewWorkFromLedger(planned);
|
|
@@ -36878,6 +37226,7 @@ class BuildSession {
|
|
|
36878
37226
|
return existsSync74(buildRetroPath(this.ctx.runDir)) ? [RETRO_REL] : [];
|
|
36879
37227
|
}
|
|
36880
37228
|
async finish() {
|
|
37229
|
+
const restored = await this.restoreForeignWorkAside();
|
|
36881
37230
|
const outcomes = this.orderedOutcomes();
|
|
36882
37231
|
const done = outcomes.filter((o) => o.status === "done").length;
|
|
36883
37232
|
this.writeHandoff(outcomes);
|
|
@@ -36891,7 +37240,8 @@ class BuildSession {
|
|
|
36891
37240
|
lines: [
|
|
36892
37241
|
`${this.ctx.phaseId}/${this.ctx.stageId}: ${String(done)} of ${String(outcomes.length)} story(ies) done ` + `across ${String(this.plan.waves.length)} wave(s)`,
|
|
36893
37242
|
...this.lines,
|
|
36894
|
-
`wrote ${HANDOFF_REL2}
|
|
37243
|
+
`wrote ${HANDOFF_REL2}`,
|
|
37244
|
+
...restored
|
|
36895
37245
|
],
|
|
36896
37246
|
stderr: [...this.advisories],
|
|
36897
37247
|
error: null
|
|
@@ -36918,7 +37268,14 @@ class BuildSession {
|
|
|
36918
37268
|
storiesRel: this.plan.implicit ? IMPLICIT_PLAN_REL : null,
|
|
36919
37269
|
carried: carried.rows,
|
|
36920
37270
|
unreadableStories: carried.unreadable,
|
|
36921
|
-
widenings: this.wideningRows()
|
|
37271
|
+
widenings: this.wideningRows(),
|
|
37272
|
+
foreignWork: unrestored(this.restores).map((outcome) => ({
|
|
37273
|
+
repo: outcome.stash.repo,
|
|
37274
|
+
paths: outcome.conflicts.length > 0 ? outcome.conflicts : outcome.stash.paths,
|
|
37275
|
+
stashRef: outcome.stash.hash,
|
|
37276
|
+
command: outcome.command,
|
|
37277
|
+
detail: outcome.detail
|
|
37278
|
+
}))
|
|
36922
37279
|
}), "utf8");
|
|
36923
37280
|
}
|
|
36924
37281
|
carriedRows() {
|
|
@@ -37066,13 +37423,17 @@ class BuildSession {
|
|
|
37066
37423
|
tasks: [],
|
|
37067
37424
|
costUsd: 0,
|
|
37068
37425
|
outputs: [],
|
|
37069
|
-
lines: refusal.lines,
|
|
37426
|
+
lines: [...this.lines, ...refusal.lines],
|
|
37070
37427
|
error: refusal.error
|
|
37071
37428
|
};
|
|
37072
37429
|
}
|
|
37430
|
+
get reportLines() {
|
|
37431
|
+
return this.lines;
|
|
37432
|
+
}
|
|
37073
37433
|
async refuseOnDirtyRepos() {
|
|
37074
|
-
const
|
|
37075
|
-
|
|
37434
|
+
const plan = await dirtyRepoRefusal({ ...this.claimParts, mode: this.ctx.mode }, this.pendingStories());
|
|
37435
|
+
this.aside = plan.aside;
|
|
37436
|
+
if (plan.refusal !== null) {
|
|
37076
37437
|
return {
|
|
37077
37438
|
ok: false,
|
|
37078
37439
|
refused: true,
|
|
@@ -37080,15 +37441,65 @@ class BuildSession {
|
|
|
37080
37441
|
tasks: [],
|
|
37081
37442
|
costUsd: 0,
|
|
37082
37443
|
outputs: [],
|
|
37083
|
-
lines: refusal.lines,
|
|
37084
|
-
error: refusal.error
|
|
37444
|
+
lines: plan.refusal.lines,
|
|
37445
|
+
error: plan.refusal.error
|
|
37085
37446
|
};
|
|
37086
37447
|
}
|
|
37087
|
-
if (ignored > 0) {
|
|
37088
|
-
this.lines.push(` · ignoring ${String(ignored)} tldrx state file(s) in the dirty-tree check`);
|
|
37448
|
+
if (plan.ignored > 0) {
|
|
37449
|
+
this.lines.push(` · ignoring ${String(plan.ignored)} tldrx state file(s) in the dirty-tree check`);
|
|
37450
|
+
}
|
|
37451
|
+
return null;
|
|
37452
|
+
}
|
|
37453
|
+
async setAsideForeign() {
|
|
37454
|
+
for (const row2 of this.aside) {
|
|
37455
|
+
const reason = "uncommitted work no pending story declares in its `touches:` — set aside so the " + "base pre-flight measures the tree the stories are cut from";
|
|
37456
|
+
const outcome = await setAsideForeignWork(row2.repo, row2.repoDir, this.ctx.runId, row2.paths);
|
|
37457
|
+
if (outcome.stash === null) {
|
|
37458
|
+
return {
|
|
37459
|
+
ok: false,
|
|
37460
|
+
refused: true,
|
|
37461
|
+
awaiting: false,
|
|
37462
|
+
tasks: [],
|
|
37463
|
+
costUsd: 0,
|
|
37464
|
+
outputs: [],
|
|
37465
|
+
lines: [
|
|
37466
|
+
`[tldrx] build: repo \`${row2.repo}\` has ${String(row2.paths.length)} uncommitted change(s) that ` + `are nobody's story, and they could not be set aside: ${outcome.reason ?? "no reason given"}.`,
|
|
37467
|
+
` ${namePaths(row2.paths)}`,
|
|
37468
|
+
" Commit them, or set them aside by hand, then run " + `\`${relaunchCommand(this.ctx.mode, this.ctx.runId)}\`.`
|
|
37469
|
+
],
|
|
37470
|
+
error: `repo \`${row2.repo}\` has uncommitted changes that could not be set aside`
|
|
37471
|
+
};
|
|
37472
|
+
}
|
|
37473
|
+
this.ctx.emit(FOREIGN_ASIDE_EVENT, asidePayload(outcome.stash, reason));
|
|
37474
|
+
this.lines.push(` · ${row2.repo}: ${String(row2.paths.length)} uncommitted change(s) nobody's story declares — ` + `set aside in stash ${outcome.stash.hash.slice(0, 12)} (${namePaths(row2.paths)}) and ` + "given back when this stage ends");
|
|
37089
37475
|
}
|
|
37090
37476
|
return null;
|
|
37091
37477
|
}
|
|
37478
|
+
async restoreForeignWorkAside() {
|
|
37479
|
+
let open;
|
|
37480
|
+
try {
|
|
37481
|
+
open = pendingAsides(readFileSync68(join91(this.ctx.runDir, EVENTS_FILE2), "utf8"), (repo) => {
|
|
37482
|
+
try {
|
|
37483
|
+
return repoDirOf(this.workspace, repo);
|
|
37484
|
+
} catch {
|
|
37485
|
+
return null;
|
|
37486
|
+
}
|
|
37487
|
+
});
|
|
37488
|
+
} catch {
|
|
37489
|
+
return [];
|
|
37490
|
+
}
|
|
37491
|
+
const lines = [];
|
|
37492
|
+
for (const stash of open) {
|
|
37493
|
+
if (this.restoreAttempted.has(stash.hash))
|
|
37494
|
+
continue;
|
|
37495
|
+
this.restoreAttempted.add(stash.hash);
|
|
37496
|
+
const outcome = await restoreForeignWork(stash);
|
|
37497
|
+
this.restores.push(outcome);
|
|
37498
|
+
this.ctx.emit(FOREIGN_RESTORED_EVENT, restoredPayload(outcome));
|
|
37499
|
+
lines.push(outcome.restored ? ` · ${restoredLine(outcome)}` : `[tldrx] build: ${notRestoredLine(outcome)}`);
|
|
37500
|
+
}
|
|
37501
|
+
return lines;
|
|
37502
|
+
}
|
|
37092
37503
|
pendingStories() {
|
|
37093
37504
|
const rows = [];
|
|
37094
37505
|
for (const wave of this.plan.waves) {
|
|
@@ -39004,7 +39415,9 @@ function tokenSplit(inputTokens, outputTokens) {
|
|
|
39004
39415
|
return {};
|
|
39005
39416
|
}
|
|
39006
39417
|
function out(code, lines, stderr = []) {
|
|
39007
|
-
|
|
39418
|
+
const held2 = lines.filter((line) => line.includes(NOT_RESTORED_MARK));
|
|
39419
|
+
const ordered = held2.length === 0 ? lines : [...lines.filter((line) => !line.includes(NOT_RESTORED_MARK)), ...held2];
|
|
39420
|
+
return stderr.length === 0 ? { code, lines: ordered } : { code, lines: ordered, stderr };
|
|
39008
39421
|
}
|
|
39009
39422
|
|
|
39010
39423
|
// src/core/facilitator/runAuto.ts
|
|
@@ -39081,11 +39494,18 @@ async function runAuto(options) {
|
|
|
39081
39494
|
if (heartbeat !== null)
|
|
39082
39495
|
clearInterval(heartbeat);
|
|
39083
39496
|
if (notifier !== null) {
|
|
39084
|
-
await notifier.send(runEndNotification(notifyCtx(), code, spentUsd, lines[lines.length - 1] ?? "", loopTally(RunStore.open(runDir2).run, spentUsd)), stageIdOf());
|
|
39497
|
+
await notifier.send(runEndNotification(notifyCtx(), code, spentUsd, lines[lines.length - 1] ?? "", loopTally(RunStore.open(runDir2).run, spentUsd), heldForeignWork()), stageIdOf());
|
|
39085
39498
|
await notifier.drain();
|
|
39086
39499
|
}
|
|
39087
39500
|
return { code, lines };
|
|
39088
39501
|
};
|
|
39502
|
+
const heldForeignWork = () => {
|
|
39503
|
+
try {
|
|
39504
|
+
return notRestoredSummary(readFileSync70(join93(runDir2, "events.jsonl"), "utf8"));
|
|
39505
|
+
} catch {
|
|
39506
|
+
return null;
|
|
39507
|
+
}
|
|
39508
|
+
};
|
|
39089
39509
|
const openQuestions2 = () => {
|
|
39090
39510
|
const ctx = cursorContext(runDir2, runId);
|
|
39091
39511
|
return ctx === null ? null : questionsCard(ctx);
|
|
@@ -39126,7 +39546,7 @@ async function runAuto(options) {
|
|
|
39126
39546
|
if (event4.type === "stage.done") {
|
|
39127
39547
|
const stage2 = flatten(RunStore.open(runDir2).run).find((entry) => entry.stage.id === event4.stage);
|
|
39128
39548
|
const unmetered = stage2 === undefined ? 0 : stage2.stage.tasks.filter((task2) => task2.metered === false).length;
|
|
39129
|
-
await notifier.send(stageDoneNotification(notifyCtx(), number2(payload(event4, "cost_usd")), unmetered), event4.stage);
|
|
39549
|
+
await notifier.send(stageDoneNotification(notifyCtx(), number2(payload(event4, "cost_usd")), unmetered, heldForeignWork()), event4.stage);
|
|
39130
39550
|
}
|
|
39131
39551
|
}
|
|
39132
39552
|
return { costUsd: null, deferredGate: null };
|
|
@@ -39537,7 +39957,7 @@ function event4(options, runId, payload2) {
|
|
|
39537
39957
|
return { ts: options.at, run: runId, stage: null, type: "run.attended", actor: options.actor, cost_usd: 0, payload: payload2 };
|
|
39538
39958
|
}
|
|
39539
39959
|
// src/core/status/initQuestionsItem.ts
|
|
39540
|
-
import { existsSync as existsSync75, readFileSync as
|
|
39960
|
+
import { existsSync as existsSync75, readFileSync as readFileSync71 } from "node:fs";
|
|
39541
39961
|
import { join as join94 } from "node:path";
|
|
39542
39962
|
var MAX_LISTED = 5;
|
|
39543
39963
|
function initQuestionsItem(root2) {
|
|
@@ -39546,7 +39966,7 @@ function initQuestionsItem(root2) {
|
|
|
39546
39966
|
return null;
|
|
39547
39967
|
let open;
|
|
39548
39968
|
try {
|
|
39549
|
-
open = openBlocks(parseQuestions(
|
|
39969
|
+
open = openBlocks(parseQuestions(readFileSync71(path, "utf8")).blocks);
|
|
39550
39970
|
} catch {
|
|
39551
39971
|
return null;
|
|
39552
39972
|
}
|
|
@@ -39566,7 +39986,7 @@ function initQuestionsItem(root2) {
|
|
|
39566
39986
|
}
|
|
39567
39987
|
|
|
39568
39988
|
// src/core/status/seedSplitItems.ts
|
|
39569
|
-
import { closeSync as closeSync2, existsSync as existsSync77, openSync as openSync2, readSync, readdirSync as readdirSync27, readFileSync as
|
|
39989
|
+
import { closeSync as closeSync2, existsSync as existsSync77, openSync as openSync2, readSync, readdirSync as readdirSync27, readFileSync as readFileSync72, statSync as statSync21 } from "node:fs";
|
|
39570
39990
|
import { basename as basename12, join as join96, relative as relative18 } from "node:path";
|
|
39571
39991
|
|
|
39572
39992
|
// src/core/seed/triageInventory.ts
|
|
@@ -39766,7 +40186,7 @@ function proposedSplits(root2) {
|
|
|
39766
40186
|
try {
|
|
39767
40187
|
if (!statSync21(dir).isDirectory() || !existsSync77(path))
|
|
39768
40188
|
continue;
|
|
39769
|
-
const file = readSplitFile(parseYaml(
|
|
40189
|
+
const file = readSplitFile(parseYaml(readFileSync72(path, "utf8")));
|
|
39770
40190
|
if (file.status !== "proposed" && file.status !== "applying")
|
|
39771
40191
|
continue;
|
|
39772
40192
|
found.push({ path, rel: toRel(root2, path), dir, file });
|
|
@@ -39834,7 +40254,7 @@ function seedDocuments2(dir, file) {
|
|
|
39834
40254
|
const inventory = join96(dir, "inventory.json");
|
|
39835
40255
|
if (existsSync77(inventory)) {
|
|
39836
40256
|
try {
|
|
39837
|
-
const doc = JSON.parse(
|
|
40257
|
+
const doc = JSON.parse(readFileSync72(inventory, "utf8"));
|
|
39838
40258
|
const rels2 = (doc.documents ?? []).map((row2) => row2.rel).filter((rel) => typeof rel === "string");
|
|
39839
40259
|
if (rels2.length > 0)
|
|
39840
40260
|
return rels2;
|
|
@@ -39870,7 +40290,7 @@ function cachedStatuses(dir) {
|
|
|
39870
40290
|
if (!existsSync77(inventory))
|
|
39871
40291
|
return out2;
|
|
39872
40292
|
try {
|
|
39873
|
-
const doc = JSON.parse(
|
|
40293
|
+
const doc = JSON.parse(readFileSync72(inventory, "utf8"));
|
|
39874
40294
|
for (const row2 of doc.documents ?? []) {
|
|
39875
40295
|
if (typeof row2.rel === "string" && typeof row2.adrStatus === "string")
|
|
39876
40296
|
out2.set(row2.rel, row2.adrStatus);
|
|
@@ -40186,7 +40606,7 @@ function resolveWorkspaceRoot(explicit, start = process.cwd()) {
|
|
|
40186
40606
|
return findWorkspaceRoot2(start) ?? resolve11(start);
|
|
40187
40607
|
}
|
|
40188
40608
|
// src/core/experts/stageCoverage.ts
|
|
40189
|
-
import { existsSync as existsSync79, readdirSync as readdirSync28, readFileSync as
|
|
40609
|
+
import { existsSync as existsSync79, readdirSync as readdirSync28, readFileSync as readFileSync73, statSync as statSync22 } from "node:fs";
|
|
40190
40610
|
import { join as join98 } from "node:path";
|
|
40191
40611
|
function stagesLoadingExperts(root2) {
|
|
40192
40612
|
const repos = workspaceRepos(root2);
|
|
@@ -40246,7 +40666,7 @@ function readStage(root2, id) {
|
|
|
40246
40666
|
if (!existsSync79(path))
|
|
40247
40667
|
return null;
|
|
40248
40668
|
try {
|
|
40249
|
-
const doc = parseYaml(
|
|
40669
|
+
const doc = parseYaml(readFileSync73(path, "utf8"));
|
|
40250
40670
|
return isRecord(doc) ? doc : null;
|
|
40251
40671
|
} catch {
|
|
40252
40672
|
return null;
|
|
@@ -40257,7 +40677,7 @@ function workspaceRepos(root2) {
|
|
|
40257
40677
|
if (!existsSync79(path))
|
|
40258
40678
|
return [];
|
|
40259
40679
|
try {
|
|
40260
|
-
const doc = parseYaml(
|
|
40680
|
+
const doc = parseYaml(readFileSync73(path, "utf8"));
|
|
40261
40681
|
const list = isRecord(doc) && Array.isArray(doc.repos) ? doc.repos : [];
|
|
40262
40682
|
return list.map((row2) => isRecord(row2) && typeof row2.name === "string" ? row2.name : "").filter((name) => name !== "");
|
|
40263
40683
|
} catch {
|
|
@@ -40607,7 +41027,7 @@ function sharedCitationWarnings(shared) {
|
|
|
40607
41027
|
return shared.map((row2) => `warning: shared citation ${row2.src} by ${row2.experts.join(",")} — check for contradiction`);
|
|
40608
41028
|
}
|
|
40609
41029
|
// src/core/training/mineRuns.ts
|
|
40610
|
-
import { existsSync as existsSync81, readFileSync as
|
|
41030
|
+
import { existsSync as existsSync81, readFileSync as readFileSync74, readdirSync as readdirSync29, statSync as statSync23 } from "node:fs";
|
|
40611
41031
|
import { join as join101, relative as relative19 } from "node:path";
|
|
40612
41032
|
var MAX_RUN_FILES = 24;
|
|
40613
41033
|
var MAX_RUN_BYTES = 64 * 1024;
|
|
@@ -40644,7 +41064,7 @@ function mineRuns(options) {
|
|
|
40644
41064
|
}
|
|
40645
41065
|
let text3;
|
|
40646
41066
|
try {
|
|
40647
|
-
text3 =
|
|
41067
|
+
text3 = readFileSync74(candidate.abs, "utf8");
|
|
40648
41068
|
} catch {
|
|
40649
41069
|
notRead.push(candidate.path);
|
|
40650
41070
|
continue;
|
|
@@ -40708,7 +41128,7 @@ function runRepos(path) {
|
|
|
40708
41128
|
if (!existsSync81(path))
|
|
40709
41129
|
return [];
|
|
40710
41130
|
try {
|
|
40711
|
-
const doc = parseYaml(
|
|
41131
|
+
const doc = parseYaml(readFileSync74(path, "utf8"));
|
|
40712
41132
|
const repos = doc?.repos;
|
|
40713
41133
|
if (!Array.isArray(repos))
|
|
40714
41134
|
return [];
|
|
@@ -41823,7 +42243,7 @@ var statusCommand = {
|
|
|
41823
42243
|
};
|
|
41824
42244
|
|
|
41825
42245
|
// src/core/seed/runTriage.ts
|
|
41826
|
-
import { mkdirSync as mkdirSync20, readFileSync as
|
|
42246
|
+
import { mkdirSync as mkdirSync20, readFileSync as readFileSync75, writeFileSync as writeFileSync18, existsSync as existsSync83 } from "node:fs";
|
|
41827
42247
|
import { isAbsolute as isAbsolute8, join as join102, relative as relative20, resolve as resolve14 } from "node:path";
|
|
41828
42248
|
|
|
41829
42249
|
// src/core/seed/renderInventory.ts
|
|
@@ -42266,7 +42686,7 @@ function readTriageResult(outDir) {
|
|
|
42266
42686
|
}
|
|
42267
42687
|
let doc;
|
|
42268
42688
|
try {
|
|
42269
|
-
doc = JSON.parse(
|
|
42689
|
+
doc = JSON.parse(readFileSync75(path, "utf8"));
|
|
42270
42690
|
} catch (error) {
|
|
42271
42691
|
throw new PendingError(`${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
42272
42692
|
}
|
|
@@ -42307,7 +42727,7 @@ function round28(n) {
|
|
|
42307
42727
|
}
|
|
42308
42728
|
|
|
42309
42729
|
// src/core/seed/applySplit.ts
|
|
42310
|
-
import { existsSync as existsSync84, readFileSync as
|
|
42730
|
+
import { existsSync as existsSync84, readFileSync as readFileSync76, writeFileSync as writeFileSync19 } from "node:fs";
|
|
42311
42731
|
import { dirname as dirname14, isAbsolute as isAbsolute9, join as join103, relative as relative21, resolve as resolve15 } from "node:path";
|
|
42312
42732
|
var EXIT_OK10 = 0;
|
|
42313
42733
|
var EXIT_USAGE7 = 1;
|
|
@@ -42319,7 +42739,7 @@ function applySplit(options) {
|
|
|
42319
42739
|
}
|
|
42320
42740
|
let file;
|
|
42321
42741
|
try {
|
|
42322
|
-
file = readSplitFile(parseYaml(
|
|
42742
|
+
file = readSplitFile(parseYaml(readFileSync76(path, "utf8")));
|
|
42323
42743
|
} catch (error) {
|
|
42324
42744
|
return fail2(EXIT_USAGE7, [error instanceof SplitError ? error.message : message5(error)]);
|
|
42325
42745
|
}
|
|
@@ -42444,7 +42864,7 @@ function splitUniverse(splitPath, file) {
|
|
|
42444
42864
|
const inventory = join103(dirname14(splitPath), "inventory.json");
|
|
42445
42865
|
if (existsSync84(inventory)) {
|
|
42446
42866
|
try {
|
|
42447
|
-
const doc = JSON.parse(
|
|
42867
|
+
const doc = JSON.parse(readFileSync76(inventory, "utf8"));
|
|
42448
42868
|
const rels2 = (doc.documents ?? []).map((row2) => row2.rel).filter((rel) => typeof rel === "string");
|
|
42449
42869
|
if (rels2.length > 0)
|
|
42450
42870
|
return new Set(rels2);
|
|
@@ -42487,7 +42907,7 @@ function message5(error) {
|
|
|
42487
42907
|
}
|
|
42488
42908
|
|
|
42489
42909
|
// src/core/seed/answerSplitQuestion.ts
|
|
42490
|
-
import { existsSync as existsSync85, readFileSync as
|
|
42910
|
+
import { existsSync as existsSync85, readFileSync as readFileSync77, writeFileSync as writeFileSync20 } from "node:fs";
|
|
42491
42911
|
import { dirname as dirname15, isAbsolute as isAbsolute10, join as join104, relative as relative22, resolve as resolve16 } from "node:path";
|
|
42492
42912
|
var EXIT_OK11 = 0;
|
|
42493
42913
|
var EXIT_USAGE8 = 1;
|
|
@@ -42501,7 +42921,7 @@ function answerSplitQuestion(options) {
|
|
|
42501
42921
|
return { code: EXIT_NOT_FOUND9, lines: [`no such file: ${options.splitPath}`] };
|
|
42502
42922
|
let file;
|
|
42503
42923
|
try {
|
|
42504
|
-
file = readSplitFile(parseYaml(
|
|
42924
|
+
file = readSplitFile(parseYaml(readFileSync77(path, "utf8")));
|
|
42505
42925
|
} catch (error) {
|
|
42506
42926
|
return { code: EXIT_USAGE8, lines: [error instanceof SplitError ? error.message : message6(error)] };
|
|
42507
42927
|
}
|
|
@@ -42728,10 +43148,10 @@ import { existsSync as existsSync89 } from "node:fs";
|
|
|
42728
43148
|
import { join as join107 } from "node:path";
|
|
42729
43149
|
|
|
42730
43150
|
// src/core/answers/captureAnswers.ts
|
|
42731
|
-
import { existsSync as existsSync88, readFileSync as
|
|
43151
|
+
import { existsSync as existsSync88, readFileSync as readFileSync80, writeFileSync as writeFileSync21 } from "node:fs";
|
|
42732
43152
|
|
|
42733
43153
|
// src/core/answers/stampSuperseded.ts
|
|
42734
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync86, readFileSync as
|
|
43154
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync86, readFileSync as readFileSync78, statSync as statSync24 } from "node:fs";
|
|
42735
43155
|
import { isAbsolute as isAbsolute11, join as join105, relative as relative23 } from "node:path";
|
|
42736
43156
|
var STAMP_MARKER = "tldrx:superseded";
|
|
42737
43157
|
var AFFECTS_KEY = "affects";
|
|
@@ -42771,7 +43191,7 @@ function stampSuperseded(runDir2, questionsPath, block2, fact, at) {
|
|
|
42771
43191
|
const questionsRel = runRelative(runDir2, questionsPath) ?? "questions.md";
|
|
42772
43192
|
const stamped = [];
|
|
42773
43193
|
for (const doc of affectedDocs(runDir2, questionsPath, block2)) {
|
|
42774
|
-
const text3 =
|
|
43194
|
+
const text3 = readFileSync78(doc.abs, "utf8");
|
|
42775
43195
|
if (isStamped(text3, fact))
|
|
42776
43196
|
continue;
|
|
42777
43197
|
const lead = text3 === "" ? "" : text3.endsWith(`
|
|
@@ -42845,7 +43265,7 @@ function reposFromAffects(affects, repoNames) {
|
|
|
42845
43265
|
}
|
|
42846
43266
|
|
|
42847
43267
|
// src/core/answers/raiseConflict.ts
|
|
42848
|
-
import { appendFileSync as appendFileSync3, existsSync as existsSync87, readFileSync as
|
|
43268
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync87, readFileSync as readFileSync79 } from "node:fs";
|
|
42849
43269
|
import { join as join106 } from "node:path";
|
|
42850
43270
|
var RAISED_BY = "tldrx";
|
|
42851
43271
|
function nextQuestionId(runDir2) {
|
|
@@ -42856,7 +43276,7 @@ function nextQuestionId(runDir2) {
|
|
|
42856
43276
|
continue;
|
|
42857
43277
|
let blocks;
|
|
42858
43278
|
try {
|
|
42859
|
-
blocks = parseQuestions(
|
|
43279
|
+
blocks = parseQuestions(readFileSync79(path, "utf8")).blocks;
|
|
42860
43280
|
} catch {
|
|
42861
43281
|
continue;
|
|
42862
43282
|
}
|
|
@@ -42934,7 +43354,7 @@ function factWasTruncated(title, answer2) {
|
|
|
42934
43354
|
function captureAnswers(questionsPath, ctx) {
|
|
42935
43355
|
if (!existsSync88(questionsPath))
|
|
42936
43356
|
return [];
|
|
42937
|
-
let doc = parseQuestions(
|
|
43357
|
+
let doc = parseQuestions(readFileSync80(questionsPath, "utf8"));
|
|
42938
43358
|
const answered = detectAnswered(doc.blocks);
|
|
42939
43359
|
if (answered.length === 0)
|
|
42940
43360
|
return [];
|
|
@@ -43068,7 +43488,7 @@ function supersedeAnswer(questionsPath, qid, text3, ctx) {
|
|
|
43068
43488
|
const answer2 = text3.trim();
|
|
43069
43489
|
if (answer2 === "")
|
|
43070
43490
|
throw new AnswerError("a superseding answer cannot be empty");
|
|
43071
|
-
const doc = parseQuestions(
|
|
43491
|
+
const doc = parseQuestions(readFileSync80(questionsPath, "utf8"));
|
|
43072
43492
|
const block2 = doc.blocks.find((b) => b.id === qid);
|
|
43073
43493
|
if (block2 === undefined)
|
|
43074
43494
|
throw new AnswerError(`${qid} is not in ${questionsPath}`);
|
|
@@ -43149,7 +43569,7 @@ function writeAnswerSlot(questionsPath, qid, text3) {
|
|
|
43149
43569
|
throw new AnswerError(`no questions file at ${questionsPath}`);
|
|
43150
43570
|
if (text3.trim() === "")
|
|
43151
43571
|
throw new AnswerError("an answer cannot be empty");
|
|
43152
|
-
const doc = parseQuestions(
|
|
43572
|
+
const doc = parseQuestions(readFileSync80(questionsPath, "utf8"));
|
|
43153
43573
|
const block2 = doc.blocks.find((b) => b.id === qid);
|
|
43154
43574
|
if (block2 === undefined)
|
|
43155
43575
|
throw new AnswerError(`${qid} is not in ${questionsPath}`);
|
|
@@ -43180,7 +43600,7 @@ function scopeRepos(names, repoNames) {
|
|
|
43180
43600
|
}
|
|
43181
43601
|
|
|
43182
43602
|
// src/cli/commands/answer.ts
|
|
43183
|
-
import { readFileSync as
|
|
43603
|
+
import { readFileSync as readFileSync81 } from "node:fs";
|
|
43184
43604
|
var QUESTION_ID_RE = /^Q\d{1,6}$/;
|
|
43185
43605
|
var answerCommand2 = {
|
|
43186
43606
|
name: "answer",
|
|
@@ -43295,7 +43715,7 @@ function locateQuestion(store, qid, status) {
|
|
|
43295
43715
|
const path = join107(store.runDir, phase.id, "questions.md");
|
|
43296
43716
|
if (!existsSync89(path))
|
|
43297
43717
|
continue;
|
|
43298
|
-
const block2 = parseQuestions(
|
|
43718
|
+
const block2 = parseQuestions(readFileSync81(path, "utf8")).blocks.find((b) => b.id === qid);
|
|
43299
43719
|
if (block2 !== undefined && block2.metadata?.status === status)
|
|
43300
43720
|
return { path, block: block2 };
|
|
43301
43721
|
}
|
|
@@ -43303,7 +43723,7 @@ function locateQuestion(store, qid, status) {
|
|
|
43303
43723
|
}
|
|
43304
43724
|
|
|
43305
43725
|
// src/cli/commands/approve.ts
|
|
43306
|
-
import { existsSync as existsSync90, readFileSync as
|
|
43726
|
+
import { existsSync as existsSync90, readFileSync as readFileSync82 } from "node:fs";
|
|
43307
43727
|
import { relative as relative24 } from "node:path";
|
|
43308
43728
|
var approveCommand2 = {
|
|
43309
43729
|
name: "approve",
|
|
@@ -43388,7 +43808,7 @@ function readAgentEvidence(root2, store, override) {
|
|
|
43388
43808
|
process.stderr.write(`tldrx approve: --as-agent refused — no evidence note at ${relative24(root2, path)}. ` + "Write one (`tldrx gate template` puts the blank form there), then sign with it.\n");
|
|
43389
43809
|
return { kind: "stop", exit: EXIT_GATE_REFUSED };
|
|
43390
43810
|
}
|
|
43391
|
-
const text3 =
|
|
43811
|
+
const text3 = readFileSync82(path, "utf8");
|
|
43392
43812
|
const gate2 = `${entry.phase.id}/${entry.stage.id}`;
|
|
43393
43813
|
const validation = validateEvidence(text3, toSrcContext(loadWorkspace(root2), store.runDir, { epicRefs: true }), { gate: gate2 });
|
|
43394
43814
|
const refusals = validation.issues.filter((issue) => issue.kind === "verdict");
|
|
@@ -43964,7 +44384,7 @@ function template(argv) {
|
|
|
43964
44384
|
}
|
|
43965
44385
|
|
|
43966
44386
|
// src/core/run/reopenStory.ts
|
|
43967
|
-
import { existsSync as existsSync92, readFileSync as
|
|
44387
|
+
import { existsSync as existsSync92, readFileSync as readFileSync83, writeFileSync as writeFileSync23 } from "node:fs";
|
|
43968
44388
|
import { join as join108 } from "node:path";
|
|
43969
44389
|
var EXIT_OK12 = 0;
|
|
43970
44390
|
var EXIT_REFUSED8 = 2;
|
|
@@ -44064,7 +44484,7 @@ function reopenStory(options) {
|
|
|
44064
44484
|
}
|
|
44065
44485
|
let patched;
|
|
44066
44486
|
try {
|
|
44067
|
-
const text3 =
|
|
44487
|
+
const text3 = readFileSync83(path, "utf8");
|
|
44068
44488
|
patched = progress.implicit ? updateImplicitPlan(text3, { status: REOPENED_TO }) : updateStoryFront(text3, { status: REOPENED_TO });
|
|
44069
44489
|
} catch (error) {
|
|
44070
44490
|
if (error instanceof StoryWriteError) {
|
|
@@ -44152,7 +44572,7 @@ function refuse3(lines) {
|
|
|
44152
44572
|
}
|
|
44153
44573
|
|
|
44154
44574
|
// src/core/run/widenStory.ts
|
|
44155
|
-
import { existsSync as existsSync93, readFileSync as
|
|
44575
|
+
import { existsSync as existsSync93, readFileSync as readFileSync84, writeFileSync as writeFileSync24 } from "node:fs";
|
|
44156
44576
|
import { join as join109 } from "node:path";
|
|
44157
44577
|
var EXIT_OK13 = 0;
|
|
44158
44578
|
var EXIT_REFUSED9 = 2;
|
|
@@ -44232,7 +44652,7 @@ function widenStory(options) {
|
|
|
44232
44652
|
if (!existsSync93(path)) {
|
|
44233
44653
|
return refuse4([`${id} is \`${row2.status}\` but ${path} is not on disk — refusing to write a file that is not there`]);
|
|
44234
44654
|
}
|
|
44235
|
-
const text3 =
|
|
44655
|
+
const text3 = readFileSync84(path, "utf8");
|
|
44236
44656
|
const before = touchesIn(text3, progress.implicit);
|
|
44237
44657
|
if (before === null) {
|
|
44238
44658
|
return refuse4([
|
|
@@ -44557,7 +44977,7 @@ function firstProblem(text3, area, kind, confidence, decidedBy) {
|
|
|
44557
44977
|
}
|
|
44558
44978
|
|
|
44559
44979
|
// src/core/run/ship.ts
|
|
44560
|
-
import { existsSync as existsSync94, mkdtempSync as mkdtempSync2, readFileSync as
|
|
44980
|
+
import { existsSync as existsSync94, mkdtempSync as mkdtempSync2, readFileSync as readFileSync85, rmSync as rmSync11, writeFileSync as writeFileSync25 } from "node:fs";
|
|
44561
44981
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
44562
44982
|
import { dirname as dirname17, join as join110, resolve as resolve17 } from "node:path";
|
|
44563
44983
|
|
|
@@ -45048,7 +45468,7 @@ function lastHandoff(runDir2, phases) {
|
|
|
45048
45468
|
continue;
|
|
45049
45469
|
let text3;
|
|
45050
45470
|
try {
|
|
45051
|
-
text3 =
|
|
45471
|
+
text3 = readFileSync85(path, "utf8");
|
|
45052
45472
|
} catch {
|
|
45053
45473
|
continue;
|
|
45054
45474
|
}
|
|
@@ -45171,11 +45591,11 @@ var shipCommand = {
|
|
|
45171
45591
|
import { join as join113 } from "node:path";
|
|
45172
45592
|
|
|
45173
45593
|
// src/core/plan/syncDod.ts
|
|
45174
|
-
import { existsSync as existsSync96, readdirSync as readdirSync30, readFileSync as
|
|
45594
|
+
import { existsSync as existsSync96, readdirSync as readdirSync30, readFileSync as readFileSync87 } from "node:fs";
|
|
45175
45595
|
import { join as join112 } from "node:path";
|
|
45176
45596
|
|
|
45177
45597
|
// src/core/plan/workspaceHistory.ts
|
|
45178
|
-
import { existsSync as existsSync95, readFileSync as
|
|
45598
|
+
import { existsSync as existsSync95, readFileSync as readFileSync86, realpathSync } from "node:fs";
|
|
45179
45599
|
import { join as join111, relative as relative26, sep as sep10 } from "node:path";
|
|
45180
45600
|
var WORKSPACE_REL = `${PROJECT_FRAMEWORK_DIR}/workspace.yml`;
|
|
45181
45601
|
var MAX_VERSIONS = 50;
|
|
@@ -45183,7 +45603,7 @@ async function readWorkspaceHistory(root2) {
|
|
|
45183
45603
|
const documents = [];
|
|
45184
45604
|
const live2 = join111(root2, WORKSPACE_REL);
|
|
45185
45605
|
if (existsSync95(live2)) {
|
|
45186
|
-
const parsed = parse(
|
|
45606
|
+
const parsed = parse(readFileSync86(live2, "utf8"));
|
|
45187
45607
|
if (parsed !== null)
|
|
45188
45608
|
documents.push(parsed);
|
|
45189
45609
|
}
|
|
@@ -45273,7 +45693,7 @@ function planSyncDod(input) {
|
|
|
45273
45693
|
};
|
|
45274
45694
|
}
|
|
45275
45695
|
function syncOne(path, name, input) {
|
|
45276
|
-
const text3 =
|
|
45696
|
+
const text3 = readFileSync87(path, "utf8");
|
|
45277
45697
|
const id = name.replace(/\.md$/, "");
|
|
45278
45698
|
const rel = `${STORIES_DIR}/${name}`;
|
|
45279
45699
|
const repo = readStory(text3).repo ?? "";
|
|
@@ -45868,7 +46288,7 @@ function refusal(input, model2, share, expected) {
|
|
|
45868
46288
|
];
|
|
45869
46289
|
}
|
|
45870
46290
|
// src/core/training/ambientModel.ts
|
|
45871
|
-
import { readFileSync as
|
|
46291
|
+
import { readFileSync as readFileSync88 } from "node:fs";
|
|
45872
46292
|
import { join as join115 } from "node:path";
|
|
45873
46293
|
function ambientModelFiles(root2, home) {
|
|
45874
46294
|
return [
|
|
@@ -45891,7 +46311,7 @@ function resolveAmbientModel(input) {
|
|
|
45891
46311
|
function modelIn(path) {
|
|
45892
46312
|
let parsed;
|
|
45893
46313
|
try {
|
|
45894
|
-
parsed = JSON.parse(
|
|
46314
|
+
parsed = JSON.parse(readFileSync88(path, "utf8"));
|
|
45895
46315
|
} catch {
|
|
45896
46316
|
return null;
|
|
45897
46317
|
}
|
|
@@ -45963,11 +46383,11 @@ function noEvidenceNote(costUsd, added, warnings) {
|
|
|
45963
46383
|
return lines;
|
|
45964
46384
|
}
|
|
45965
46385
|
// src/core/training/rescoreExperts.ts
|
|
45966
|
-
import { readFileSync as
|
|
46386
|
+
import { readFileSync as readFileSync91 } from "node:fs";
|
|
45967
46387
|
import { relative as relative27 } from "node:path";
|
|
45968
46388
|
|
|
45969
46389
|
// src/core/training/competenciesWrite.ts
|
|
45970
|
-
import { existsSync as existsSync98, mkdirSync as mkdirSync22, readFileSync as
|
|
46390
|
+
import { existsSync as existsSync98, mkdirSync as mkdirSync22, readFileSync as readFileSync89, writeFileSync as writeFileSync26 } from "node:fs";
|
|
45971
46391
|
import { dirname as dirname18, join as join117 } from "node:path";
|
|
45972
46392
|
var HEADER = "# Written by `tldrx expert train` (spec §2.6). `level` is computed, never hand-set.\n";
|
|
45973
46393
|
|
|
@@ -45977,7 +46397,7 @@ function readDocument(root2, expert) {
|
|
|
45977
46397
|
const path = join117(expertDir(root2, expert), COMPETENCIES_FILE);
|
|
45978
46398
|
if (!existsSync98(path))
|
|
45979
46399
|
throw new CompetenciesError(`${path} is missing — \`tldrx expert create\` writes it`);
|
|
45980
|
-
const text3 =
|
|
46400
|
+
const text3 = readFileSync89(path, "utf8");
|
|
45981
46401
|
const parsed = parseYaml(text3);
|
|
45982
46402
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
45983
46403
|
throw new CompetenciesError(`${path} is not a mapping`);
|
|
@@ -46090,7 +46510,7 @@ function str11(value) {
|
|
|
46090
46510
|
}
|
|
46091
46511
|
|
|
46092
46512
|
// src/core/training/trainingLog.ts
|
|
46093
|
-
import { appendFileSync as appendFileSync4, existsSync as existsSync99, mkdirSync as mkdirSync23, readFileSync as
|
|
46513
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync99, mkdirSync as mkdirSync23, readFileSync as readFileSync90, statSync as statSync26 } from "node:fs";
|
|
46094
46514
|
import { dirname as dirname19, join as join118 } from "node:path";
|
|
46095
46515
|
var TRAINING_EVENT_TYPES = [
|
|
46096
46516
|
"agent.spawned",
|
|
@@ -46137,7 +46557,7 @@ class TrainingLog {
|
|
|
46137
46557
|
if (!existsSync99(this.path))
|
|
46138
46558
|
return [];
|
|
46139
46559
|
const out2 = [];
|
|
46140
|
-
for (const line of
|
|
46560
|
+
for (const line of readFileSync90(this.path, "utf8").split(`
|
|
46141
46561
|
`)) {
|
|
46142
46562
|
if (line.trim() === "")
|
|
46143
46563
|
continue;
|
|
@@ -46287,7 +46707,7 @@ function rescoreExperts(options) {
|
|
|
46287
46707
|
}
|
|
46288
46708
|
const shape = minesRunRecord ? RUNS_SHAPE : LIGHT_SHAPE;
|
|
46289
46709
|
const scope = knowledgeScopeFor(options.root, expert, area);
|
|
46290
|
-
const parsed = parseKnowledgeFile(
|
|
46710
|
+
const parsed = parseKnowledgeFile(readFileSync91(file.path, "utf8"), srcCtx, shape, scope);
|
|
46291
46711
|
if (!parsed.ok) {
|
|
46292
46712
|
const errors2 = knowledgeErrors(parsed).length;
|
|
46293
46713
|
skip(`the file does not validate under today's rules — ${String(errors2)} problem(s), so it is not evidence`);
|
|
@@ -46374,7 +46794,7 @@ function rescoreJson(rows) {
|
|
|
46374
46794
|
return JSON.stringify(rows, null, 2);
|
|
46375
46795
|
}
|
|
46376
46796
|
// src/core/training/selectFiles.ts
|
|
46377
|
-
import { existsSync as existsSync101, readFileSync as
|
|
46797
|
+
import { existsSync as existsSync101, readFileSync as readFileSync92, statSync as statSync28 } from "node:fs";
|
|
46378
46798
|
import { join as join119 } from "node:path";
|
|
46379
46799
|
var MAX_SCANNED_FILES2 = 4000;
|
|
46380
46800
|
var MAX_SCAN_BYTES2 = 512 * 1024;
|
|
@@ -46514,7 +46934,7 @@ function readDomains(root2, repos) {
|
|
|
46514
46934
|
const path = join119(root2, PROJECT_FRAMEWORK_DIR, "map", repo, "domains.md");
|
|
46515
46935
|
if (!existsSync101(path))
|
|
46516
46936
|
continue;
|
|
46517
|
-
for (const line of
|
|
46937
|
+
for (const line of readFileSync92(path, "utf8").split(`
|
|
46518
46938
|
`)) {
|
|
46519
46939
|
if (!line.startsWith("- "))
|
|
46520
46940
|
continue;
|
|
@@ -46543,7 +46963,7 @@ function readCommunities(root2, repos, keywords) {
|
|
|
46543
46963
|
}
|
|
46544
46964
|
let text3;
|
|
46545
46965
|
try {
|
|
46546
|
-
text3 =
|
|
46966
|
+
text3 = readFileSync92(path, "utf8");
|
|
46547
46967
|
} catch {
|
|
46548
46968
|
notes.push(`${repo}: graph.json could not be read`);
|
|
46549
46969
|
continue;
|
|
@@ -46622,7 +47042,7 @@ function matches2(label2, keywords) {
|
|
|
46622
47042
|
function contentHits(abs, keywords) {
|
|
46623
47043
|
let text3;
|
|
46624
47044
|
try {
|
|
46625
|
-
text3 =
|
|
47045
|
+
text3 = readFileSync92(abs, "utf8").toLowerCase();
|
|
46626
47046
|
} catch {
|
|
46627
47047
|
return [];
|
|
46628
47048
|
}
|
|
@@ -46633,7 +47053,7 @@ function readCapped2(abs, remaining2) {
|
|
|
46633
47053
|
try {
|
|
46634
47054
|
if (statSync28(abs).size > MAX_SCAN_BYTES2)
|
|
46635
47055
|
return null;
|
|
46636
|
-
text3 =
|
|
47056
|
+
text3 = readFileSync92(abs, "utf8");
|
|
46637
47057
|
} catch {
|
|
46638
47058
|
return null;
|
|
46639
47059
|
}
|
|
@@ -47206,7 +47626,7 @@ function skipNoteLines(expert, area, reason) {
|
|
|
47206
47626
|
return [`${expert}/${area}: skipped — ${first2 ?? ""}`, ...rest.map((line) => ` ${line}`)];
|
|
47207
47627
|
}
|
|
47208
47628
|
// src/core/training/strayWrite.ts
|
|
47209
|
-
import { existsSync as existsSync102, mkdirSync as mkdirSync24, readdirSync as readdirSync33, readFileSync as
|
|
47629
|
+
import { existsSync as existsSync102, mkdirSync as mkdirSync24, readdirSync as readdirSync33, readFileSync as readFileSync93, renameSync as renameSync4, rmdirSync as rmdirSync2, unlinkSync, writeFileSync as writeFileSync27 } from "node:fs";
|
|
47210
47630
|
import { dirname as dirname20, join as join121, relative as relative28, resolve as resolve19 } from "node:path";
|
|
47211
47631
|
function findStrayWrite(probe2) {
|
|
47212
47632
|
const root2 = resolve19(probe2.root);
|
|
@@ -47229,7 +47649,7 @@ function recoverStrayWrite(stray, dest) {
|
|
|
47229
47649
|
renameSync4(stray.path, dest);
|
|
47230
47650
|
} catch {
|
|
47231
47651
|
try {
|
|
47232
|
-
writeFileSync27(dest,
|
|
47652
|
+
writeFileSync27(dest, readFileSync93(stray.path, "utf8"), "utf8");
|
|
47233
47653
|
unlinkSync(stray.path);
|
|
47234
47654
|
} catch {
|
|
47235
47655
|
return { stray, recovered: existsSync102(dest), leftBehind: stray.path };
|
|
@@ -47278,7 +47698,7 @@ function describeStrayRecovery(root2, recovery, rel) {
|
|
|
47278
47698
|
return lines;
|
|
47279
47699
|
}
|
|
47280
47700
|
// src/core/training/runTraining.ts
|
|
47281
|
-
import { existsSync as existsSync103, mkdirSync as mkdirSync25, readFileSync as
|
|
47701
|
+
import { existsSync as existsSync103, mkdirSync as mkdirSync25, readFileSync as readFileSync94, renameSync as renameSync5, rmSync as rmSync12, writeFileSync as writeFileSync28 } from "node:fs";
|
|
47282
47702
|
import { homedir as homedir3 } from "node:os";
|
|
47283
47703
|
import { join as join122, relative as relative29 } from "node:path";
|
|
47284
47704
|
var EXIT_OK14 = 0;
|
|
@@ -47460,7 +47880,7 @@ async function trainWithPreflight(options, said2) {
|
|
|
47460
47880
|
const previous = prompts.map((task2) => ({
|
|
47461
47881
|
path: join122(dir, task2.final),
|
|
47462
47882
|
partial: join122(dir, task2.output),
|
|
47463
|
-
content: existsSync103(join122(dir, task2.final)) ?
|
|
47883
|
+
content: existsSync103(join122(dir, task2.final)) ? readFileSync94(join122(dir, task2.final), "utf8") : null
|
|
47464
47884
|
}));
|
|
47465
47885
|
const tasks = [];
|
|
47466
47886
|
for (const task2 of prompts) {
|
|
@@ -47574,7 +47994,7 @@ async function trainWithPreflight(options, said2) {
|
|
|
47574
47994
|
" nothing was written to competencies.yml and the status is unchanged"
|
|
47575
47995
|
]);
|
|
47576
47996
|
}
|
|
47577
|
-
const text3 =
|
|
47997
|
+
const text3 = readFileSync94(abs, "utf8");
|
|
47578
47998
|
const shape = task2.key === CODE_TASK ? LIGHT_SHAPE : RUNS_SHAPE;
|
|
47579
47999
|
let parsed = parseKnowledgeFile(text3, srcCtx, shape, scope);
|
|
47580
48000
|
if (!parsed.ok && options.run === "headless") {
|
|
@@ -47752,7 +48172,7 @@ async function repairRound(input) {
|
|
|
47752
48172
|
parsed: null
|
|
47753
48173
|
};
|
|
47754
48174
|
}
|
|
47755
|
-
const parsed = parseKnowledgeFile(
|
|
48175
|
+
const parsed = parseKnowledgeFile(readFileSync94(abs, "utf8"), input.srcCtx, input.shape, input.scope);
|
|
47756
48176
|
const left = knowledgeErrors(parsed).length;
|
|
47757
48177
|
return {
|
|
47758
48178
|
lines: [
|
|
@@ -47782,7 +48202,7 @@ function quarantine(abs, header2 = []) {
|
|
|
47782
48202
|
try {
|
|
47783
48203
|
const body = header2.length === 0 ? null : `${header2.join(`
|
|
47784
48204
|
`)}
|
|
47785
|
-
${
|
|
48205
|
+
${readFileSync94(abs, "utf8")}`;
|
|
47786
48206
|
rmSync12(kept, { force: true });
|
|
47787
48207
|
renameSync5(abs, kept);
|
|
47788
48208
|
if (body !== null)
|
|
@@ -47826,7 +48246,7 @@ function rollback(previous) {
|
|
|
47826
48246
|
function setExpertStatus(path, status) {
|
|
47827
48247
|
if (!existsSync103(path))
|
|
47828
48248
|
return;
|
|
47829
|
-
const lines =
|
|
48249
|
+
const lines = readFileSync94(path, "utf8").split(`
|
|
47830
48250
|
`);
|
|
47831
48251
|
if ((lines[0] ?? "").trimEnd() !== "---")
|
|
47832
48252
|
return;
|
|
@@ -48306,7 +48726,7 @@ function message8(error) {
|
|
|
48306
48726
|
import { isAbsolute as isAbsolute12, join as join128 } from "node:path";
|
|
48307
48727
|
|
|
48308
48728
|
// src/core/dashboard/model.ts
|
|
48309
|
-
import { existsSync as existsSync105, readFileSync as
|
|
48729
|
+
import { existsSync as existsSync105, readFileSync as readFileSync95, readdirSync as readdirSync34 } from "node:fs";
|
|
48310
48730
|
import { basename as basename15, join as join124 } from "node:path";
|
|
48311
48731
|
// src/core/replay/renderReplay.ts
|
|
48312
48732
|
function renderReplay(loaded) {
|
|
@@ -49192,7 +49612,7 @@ function readWaves(path, unreadable) {
|
|
|
49192
49612
|
}
|
|
49193
49613
|
function read2(path, unreadable, label2) {
|
|
49194
49614
|
try {
|
|
49195
|
-
return
|
|
49615
|
+
return readFileSync95(path, "utf8");
|
|
49196
49616
|
} catch {
|
|
49197
49617
|
unreadable.push(label2);
|
|
49198
49618
|
return null;
|
|
@@ -51184,6 +51604,7 @@ var LOOPBACK = "127.0.0.1";
|
|
|
51184
51604
|
var LOOPBACK_HOSTNAMES = ["127.0.0.1", "localhost", "::1"];
|
|
51185
51605
|
var DEBOUNCE_MS = 300;
|
|
51186
51606
|
var POLL_MS = 500;
|
|
51607
|
+
var SWEEP_MS = 2000;
|
|
51187
51608
|
var AGE_TICK_MS = 25000;
|
|
51188
51609
|
var PROBE_NOW = new Date(0);
|
|
51189
51610
|
var PROBE_STAMP = "1970-01-01T00:00:00Z";
|
|
@@ -51207,7 +51628,7 @@ async function startDashboardServer(options) {
|
|
|
51207
51628
|
const tail2 = createLedgerTail(root2);
|
|
51208
51629
|
tail2.poll();
|
|
51209
51630
|
let shown = probe2(root2);
|
|
51210
|
-
const watcher = watchWorkspace(root2, debounceMs, options.watch ?? "auto", () => {
|
|
51631
|
+
const watcher = watchWorkspace(root2, debounceMs, options.watch ?? "auto", options.sweepMs ?? SWEEP_MS, () => {
|
|
51211
51632
|
const change = tail2.poll();
|
|
51212
51633
|
const next = probe2(root2);
|
|
51213
51634
|
if (next !== null && shown !== null && next === shown)
|
|
@@ -51233,6 +51654,9 @@ async function startDashboardServer(options) {
|
|
|
51233
51654
|
port,
|
|
51234
51655
|
url: `http://${host}:${String(port)}`,
|
|
51235
51656
|
watchMode: watcher.mode,
|
|
51657
|
+
simulateWatcherLoss: () => {
|
|
51658
|
+
watcher.dropWatchers();
|
|
51659
|
+
},
|
|
51236
51660
|
close: async () => {
|
|
51237
51661
|
clearInterval(tick);
|
|
51238
51662
|
watcher.close();
|
|
@@ -51348,12 +51772,13 @@ function probe2(root2) {
|
|
|
51348
51772
|
return null;
|
|
51349
51773
|
}
|
|
51350
51774
|
}
|
|
51351
|
-
function watchWorkspace(root2, debounceMs, requested, onChange) {
|
|
51775
|
+
function watchWorkspace(root2, debounceMs, requested, sweepMs, onChange) {
|
|
51352
51776
|
const targets = [join127(root2, PROJECT_FRAMEWORK_DIR), join127(root2, PROJECT_WORK_DIR)];
|
|
51353
51777
|
const watchers = new Map;
|
|
51354
51778
|
let timer = null;
|
|
51355
51779
|
let closed = false;
|
|
51356
51780
|
let mode = "watch";
|
|
51781
|
+
let swept = "";
|
|
51357
51782
|
const fire = () => {
|
|
51358
51783
|
if (closed)
|
|
51359
51784
|
return;
|
|
@@ -51362,6 +51787,7 @@ function watchWorkspace(root2, debounceMs, requested, onChange) {
|
|
|
51362
51787
|
timer = setTimeout(() => {
|
|
51363
51788
|
timer = null;
|
|
51364
51789
|
arm();
|
|
51790
|
+
swept = fingerprint(root2, targets);
|
|
51365
51791
|
onChange();
|
|
51366
51792
|
}, debounceMs);
|
|
51367
51793
|
timer.unref();
|
|
@@ -51392,26 +51818,27 @@ function watchWorkspace(root2, debounceMs, requested, onChange) {
|
|
|
51392
51818
|
mode = "poll";
|
|
51393
51819
|
else
|
|
51394
51820
|
arm();
|
|
51395
|
-
|
|
51396
|
-
|
|
51397
|
-
|
|
51398
|
-
|
|
51399
|
-
|
|
51400
|
-
|
|
51401
|
-
|
|
51402
|
-
|
|
51403
|
-
|
|
51404
|
-
}, POLL_MS);
|
|
51405
|
-
poller.unref();
|
|
51406
|
-
}
|
|
51821
|
+
swept = fingerprint(root2, targets);
|
|
51822
|
+
const poller = setInterval(() => {
|
|
51823
|
+
const next = fingerprint(root2, targets);
|
|
51824
|
+
if (next === swept)
|
|
51825
|
+
return;
|
|
51826
|
+
swept = next;
|
|
51827
|
+
fire();
|
|
51828
|
+
}, mode === "poll" ? POLL_MS : sweepMs);
|
|
51829
|
+
poller.unref();
|
|
51407
51830
|
return {
|
|
51408
51831
|
mode,
|
|
51832
|
+
dropWatchers: () => {
|
|
51833
|
+
for (const watcher of watchers.values())
|
|
51834
|
+
watcher.close();
|
|
51835
|
+
watchers.clear();
|
|
51836
|
+
},
|
|
51409
51837
|
close: () => {
|
|
51410
51838
|
closed = true;
|
|
51411
51839
|
if (timer !== null)
|
|
51412
51840
|
clearTimeout(timer);
|
|
51413
|
-
|
|
51414
|
-
clearInterval(poller);
|
|
51841
|
+
clearInterval(poller);
|
|
51415
51842
|
for (const watcher of watchers.values())
|
|
51416
51843
|
watcher.close();
|
|
51417
51844
|
watchers.clear();
|
|
@@ -51621,7 +52048,7 @@ function option2(argv, name) {
|
|
|
51621
52048
|
import { writeFileSync as writeFileSync31 } from "node:fs";
|
|
51622
52049
|
import { join as join130 } from "node:path";
|
|
51623
52050
|
// src/core/retro/applyPractices.ts
|
|
51624
|
-
import { existsSync as existsSync107, mkdirSync as mkdirSync27, readFileSync as
|
|
52051
|
+
import { existsSync as existsSync107, mkdirSync as mkdirSync27, readFileSync as readFileSync96, writeFileSync as writeFileSync30 } from "node:fs";
|
|
51625
52052
|
import { dirname as dirname21, join as join129 } from "node:path";
|
|
51626
52053
|
var PRACTICES_FILE = "practices.md";
|
|
51627
52054
|
var HEADER2 = [
|
|
@@ -51640,7 +52067,7 @@ function applyPractices(root2, run2, proposals, now = new Date) {
|
|
|
51640
52067
|
if (proposals.length === 0) {
|
|
51641
52068
|
return { path, appended: false, reason: "there are no practice proposals to apply" };
|
|
51642
52069
|
}
|
|
51643
|
-
const existing = existsSync107(path) ?
|
|
52070
|
+
const existing = existsSync107(path) ? readFileSync96(path, "utf8") : "";
|
|
51644
52071
|
const stamp4 = `— run ${run2}`;
|
|
51645
52072
|
if (existing.includes(stamp4)) {
|
|
51646
52073
|
return { path, appended: false, reason: `practices.md already carries a block for run ${run2}` };
|
|
@@ -52189,7 +52616,7 @@ function theOneOpenRun() {
|
|
|
52189
52616
|
}
|
|
52190
52617
|
}
|
|
52191
52618
|
// src/core/watch/watchViews.ts
|
|
52192
|
-
import { existsSync as existsSync108, readdirSync as readdirSync36, readFileSync as
|
|
52619
|
+
import { existsSync as existsSync108, readdirSync as readdirSync36, readFileSync as readFileSync97 } from "node:fs";
|
|
52193
52620
|
import { join as join131 } from "node:path";
|
|
52194
52621
|
function watchersDir(runDir2) {
|
|
52195
52622
|
return join131(runDir2, WATCH_PHASE, WATCHERS_DIR);
|
|
@@ -52201,7 +52628,7 @@ function loadCards(runDir2, ctx) {
|
|
|
52201
52628
|
const cards = [];
|
|
52202
52629
|
for (const name of readdirSync36(dir).filter((n) => n.endsWith(".md")).sort()) {
|
|
52203
52630
|
const id = name.slice(0, -".md".length);
|
|
52204
|
-
const text5 =
|
|
52631
|
+
const text5 = readFileSync97(join131(dir, name), "utf8");
|
|
52205
52632
|
cards.push({
|
|
52206
52633
|
id,
|
|
52207
52634
|
text: text5,
|
|
@@ -52972,7 +53399,7 @@ function quote4(value) {
|
|
|
52972
53399
|
return JSON.stringify(value);
|
|
52973
53400
|
}
|
|
52974
53401
|
// src/core/adapters/processConfig.ts
|
|
52975
|
-
import { existsSync as existsSync110, readFileSync as
|
|
53402
|
+
import { existsSync as existsSync110, readFileSync as readFileSync98 } from "node:fs";
|
|
52976
53403
|
import { join as join133 } from "node:path";
|
|
52977
53404
|
var PROCESS_FILE2 = "process.yml";
|
|
52978
53405
|
var DISABLED = {
|
|
@@ -52991,7 +53418,7 @@ function readTicketToolConfig(root2) {
|
|
|
52991
53418
|
return DISABLED;
|
|
52992
53419
|
let doc;
|
|
52993
53420
|
try {
|
|
52994
|
-
doc = parseYaml(
|
|
53421
|
+
doc = parseYaml(readFileSync98(path, "utf8"));
|
|
52995
53422
|
} catch {
|
|
52996
53423
|
return { ...DISABLED, path };
|
|
52997
53424
|
}
|
|
@@ -53153,7 +53580,7 @@ function strip(url) {
|
|
|
53153
53580
|
return at === -1 ? url : url.slice(at);
|
|
53154
53581
|
}
|
|
53155
53582
|
// src/core/adapters/collect.ts
|
|
53156
|
-
import { existsSync as existsSync111, readdirSync as readdirSync37, readFileSync as
|
|
53583
|
+
import { existsSync as existsSync111, readdirSync as readdirSync37, readFileSync as readFileSync99 } from "node:fs";
|
|
53157
53584
|
import { join as join134 } from "node:path";
|
|
53158
53585
|
var PLAN_PHASE3 = "03-plan";
|
|
53159
53586
|
function collectMirrorItems(planDir, runId) {
|
|
@@ -53161,7 +53588,7 @@ function collectMirrorItems(planDir, runId) {
|
|
|
53161
53588
|
const skipped = [];
|
|
53162
53589
|
for (const name of markdownIn4(join134(planDir, EPICS_DIR))) {
|
|
53163
53590
|
const path = join134(planDir, EPICS_DIR, name);
|
|
53164
|
-
const text6 =
|
|
53591
|
+
const text6 = readFileSync99(path, "utf8");
|
|
53165
53592
|
const parsed = validateEpicFile(text6);
|
|
53166
53593
|
const epic = parsed.epic;
|
|
53167
53594
|
if (epic === null) {
|
|
@@ -53194,7 +53621,7 @@ function collectMirrorItems(planDir, runId) {
|
|
|
53194
53621
|
}
|
|
53195
53622
|
for (const name of markdownIn4(join134(planDir, STORIES_DIR))) {
|
|
53196
53623
|
const path = join134(planDir, STORIES_DIR, name);
|
|
53197
|
-
const text6 =
|
|
53624
|
+
const text6 = readFileSync99(path, "utf8");
|
|
53198
53625
|
const parsed = validateStoryFile(text6);
|
|
53199
53626
|
const story = parsed.story;
|
|
53200
53627
|
if (story === null) {
|
|
@@ -53239,7 +53666,7 @@ function reason(issues) {
|
|
|
53239
53666
|
return first2.path === "" ? first2.message : `${first2.path}: ${first2.message}`;
|
|
53240
53667
|
}
|
|
53241
53668
|
// src/core/adapters/sync.ts
|
|
53242
|
-
import { readFileSync as
|
|
53669
|
+
import { readFileSync as readFileSync100, writeFileSync as writeFileSync32 } from "node:fs";
|
|
53243
53670
|
function planSync(options) {
|
|
53244
53671
|
const collected = collectMirrorItems(options.planDir, options.runId);
|
|
53245
53672
|
const actions = new Map;
|
|
@@ -53283,7 +53710,7 @@ async function syncTickets(options) {
|
|
|
53283
53710
|
return { results, skipped: collected.skipped, created, updated, dryRun: options.dryRun };
|
|
53284
53711
|
}
|
|
53285
53712
|
function writeBack(item, ref, externalStatus, sync) {
|
|
53286
|
-
const before2 =
|
|
53713
|
+
const before2 = readFileSync100(item.path, "utf8");
|
|
53287
53714
|
const after = applyExternal(before2, {
|
|
53288
53715
|
external: ref,
|
|
53289
53716
|
...sync === "two-way" ? { externalStatus } : {}
|
|
@@ -53535,7 +53962,7 @@ function renderSync(runId, provider, config, outcome) {
|
|
|
53535
53962
|
}
|
|
53536
53963
|
|
|
53537
53964
|
// src/cli/commands/interview.ts
|
|
53538
|
-
import { existsSync as existsSync114, readFileSync as
|
|
53965
|
+
import { existsSync as existsSync114, readFileSync as readFileSync103 } from "node:fs";
|
|
53539
53966
|
import { join as join137 } from "node:path";
|
|
53540
53967
|
|
|
53541
53968
|
// src/core/interview/lineReader.ts
|
|
@@ -53649,7 +54076,7 @@ function renderNextSteps(hasRun) {
|
|
|
53649
54076
|
}
|
|
53650
54077
|
|
|
53651
54078
|
// src/core/interview/runInterview.ts
|
|
53652
|
-
import { existsSync as existsSync112, readFileSync as
|
|
54079
|
+
import { existsSync as existsSync112, readFileSync as readFileSync101 } from "node:fs";
|
|
53653
54080
|
|
|
53654
54081
|
// src/core/interview/reply.ts
|
|
53655
54082
|
var LETTER_RE = /^[A-Ea-e]$/;
|
|
@@ -53690,7 +54117,7 @@ async function interview(options) {
|
|
|
53690
54117
|
const { questionsPath, out: out2, ctx } = options;
|
|
53691
54118
|
if (!existsSync112(questionsPath))
|
|
53692
54119
|
throw new InterviewError(`no questions file at ${questionsPath}`);
|
|
53693
|
-
const blocks = openBlocks(parseQuestions(
|
|
54120
|
+
const blocks = openBlocks(parseQuestions(readFileSync101(questionsPath, "utf8")).blocks);
|
|
53694
54121
|
const answered = [];
|
|
53695
54122
|
let skipped = 0;
|
|
53696
54123
|
let quit = false;
|
|
@@ -53751,7 +54178,7 @@ function renderInterviewSummary(result2) {
|
|
|
53751
54178
|
}
|
|
53752
54179
|
|
|
53753
54180
|
// src/core/init/processAnswers.ts
|
|
53754
|
-
import { existsSync as existsSync113, readFileSync as
|
|
54181
|
+
import { existsSync as existsSync113, readFileSync as readFileSync102 } from "node:fs";
|
|
53755
54182
|
|
|
53756
54183
|
// src/core/init/githubProject.ts
|
|
53757
54184
|
import { join as join136 } from "node:path";
|
|
@@ -53801,7 +54228,7 @@ function hasProcessAnswer(answers) {
|
|
|
53801
54228
|
function collectProcessAnswers(questionsPath, answered) {
|
|
53802
54229
|
if (answered.length === 0 || !existsSync113(questionsPath))
|
|
53803
54230
|
return NO_PROCESS_ANSWERS;
|
|
53804
|
-
const titles = new Map(parseQuestions(
|
|
54231
|
+
const titles = new Map(parseQuestions(readFileSync102(questionsPath, "utf8")).blocks.map((block2) => [block2.id, block2.title]));
|
|
53805
54232
|
const find = (title) => answered.find((answer2) => titles.get(answer2.q) === title);
|
|
53806
54233
|
const methodology = find(METHODOLOGY_QUESTION);
|
|
53807
54234
|
const ticket = find(TICKET_QUESTION);
|
|
@@ -53839,7 +54266,7 @@ async function applyProcessAnswers(input) {
|
|
|
53839
54266
|
}
|
|
53840
54267
|
assertEnum(methodology, METHODOLOGIES, "methodology");
|
|
53841
54268
|
assertEnum(ticketTool, TICKET_TOOLS, "ticket_tool.kind");
|
|
53842
|
-
const before2 = existsSync113(path) ?
|
|
54269
|
+
const before2 = existsSync113(path) ? readFileSync102(path, "utf8") : null;
|
|
53843
54270
|
const seed = before2 ?? PROCESS_HEADER + stringifyYaml(buildProcessDocument({
|
|
53844
54271
|
methodology: null,
|
|
53845
54272
|
approver: await gitUserName(input.runner, input.root),
|
|
@@ -54076,11 +54503,11 @@ No open questions.
|
|
|
54076
54503
|
return "no-open-questions";
|
|
54077
54504
|
}
|
|
54078
54505
|
function countOpen(path) {
|
|
54079
|
-
return openBlocks(parseQuestions(
|
|
54506
|
+
return openBlocks(parseQuestions(readFileSync103(path, "utf8")).blocks).length;
|
|
54080
54507
|
}
|
|
54081
54508
|
|
|
54082
54509
|
// src/cli/commands/questions.ts
|
|
54083
|
-
import { existsSync as existsSync115, readFileSync as
|
|
54510
|
+
import { existsSync as existsSync115, readFileSync as readFileSync104, writeFileSync as writeFileSync33 } from "node:fs";
|
|
54084
54511
|
import { join as join138 } from "node:path";
|
|
54085
54512
|
var VALUE_FLAGS15 = ["run", "root", "area"];
|
|
54086
54513
|
var questionsCommand = {
|
|
@@ -54137,7 +54564,7 @@ function lint(argv) {
|
|
|
54137
54564
|
const needSource = [];
|
|
54138
54565
|
for (const path of files) {
|
|
54139
54566
|
const rel = path.slice(store.runDir.length + 1);
|
|
54140
|
-
const text6 =
|
|
54567
|
+
const text6 = readFileSync104(path, "utf8");
|
|
54141
54568
|
const readable = parseQuestions(text6).blocks.length;
|
|
54142
54569
|
const lost = unreadableQuestionHeadings(text6);
|
|
54143
54570
|
if (lost.length === 0) {
|
|
@@ -54302,7 +54729,7 @@ function renderFinale(ink) {
|
|
|
54302
54729
|
}
|
|
54303
54730
|
|
|
54304
54731
|
// src/core/learn/engine.ts
|
|
54305
|
-
import { existsSync as existsSync118, readdirSync as readdirSync38, readFileSync as
|
|
54732
|
+
import { existsSync as existsSync118, readdirSync as readdirSync38, readFileSync as readFileSync105 } from "node:fs";
|
|
54306
54733
|
import { join as join141 } from "node:path";
|
|
54307
54734
|
|
|
54308
54735
|
// src/core/learn/sandbox.ts
|
|
@@ -54370,9 +54797,9 @@ function claudeShimScript(selfCmd, scriptPath) {
|
|
|
54370
54797
|
"#!/bin/sh",
|
|
54371
54798
|
"# The tutorial's stand-in for `claude`, written by `tldrx learn`.",
|
|
54372
54799
|
"# It is not the real CLI, it never reaches a network, and it never costs anything.",
|
|
54373
|
-
`${SCRIPT_ENV}=${
|
|
54800
|
+
`${SCRIPT_ENV}=${shellQuote3(scriptPath)}`,
|
|
54374
54801
|
`export ${SCRIPT_ENV}`,
|
|
54375
|
-
`exec ${
|
|
54802
|
+
`exec ${shellQuote3(interpreter)} ${shellQuote3(entry)} ${LEARN_AGENT_ARGV0} "$@"`,
|
|
54376
54803
|
""
|
|
54377
54804
|
].join(`
|
|
54378
54805
|
`);
|
|
@@ -54381,7 +54808,7 @@ function writeClaudeShim(sandbox, selfCmd) {
|
|
|
54381
54808
|
writeFileSync34(sandbox.claudeBin, claudeShimScript(selfCmd, sandbox.scriptPath), "utf8");
|
|
54382
54809
|
chmodSync(sandbox.claudeBin, 493);
|
|
54383
54810
|
}
|
|
54384
|
-
function
|
|
54811
|
+
function shellQuote3(text6) {
|
|
54385
54812
|
return `'${text6.replaceAll("'", `'\\''`)}'`;
|
|
54386
54813
|
}
|
|
54387
54814
|
function writeScript(sandbox, script) {
|
|
@@ -54576,7 +55003,7 @@ function newestRunId(sandbox) {
|
|
|
54576
55003
|
}
|
|
54577
55004
|
function runIsFinished(runDir2) {
|
|
54578
55005
|
try {
|
|
54579
|
-
const status = /^status:\s*(\S+)/m.exec(
|
|
55006
|
+
const status = /^status:\s*(\S+)/m.exec(readFileSync105(join141(runDir2, "run.yml"), "utf8"))?.[1] ?? "";
|
|
54580
55007
|
return isFinished(status);
|
|
54581
55008
|
} catch {
|
|
54582
55009
|
return false;
|
|
@@ -54617,7 +55044,7 @@ function sandboxHas(sandbox, rel) {
|
|
|
54617
55044
|
}
|
|
54618
55045
|
|
|
54619
55046
|
// src/core/learn/chapters.ts
|
|
54620
|
-
import { existsSync as existsSync119, mkdirSync as mkdirSync29, readdirSync as readdirSync39, readFileSync as
|
|
55047
|
+
import { existsSync as existsSync119, mkdirSync as mkdirSync29, readdirSync as readdirSync39, readFileSync as readFileSync106, writeFileSync as writeFileSync35 } from "node:fs";
|
|
54621
55048
|
import { dirname as dirname23, join as join142 } from "node:path";
|
|
54622
55049
|
var LEARN_RUN_SLUG = "bulk-pricing";
|
|
54623
55050
|
var WATCH_PHASE_DIR = "05-watch";
|
|
@@ -55862,7 +56289,7 @@ function writeInto(path, content) {
|
|
|
55862
56289
|
}
|
|
55863
56290
|
function read3(sandbox, rel) {
|
|
55864
56291
|
try {
|
|
55865
|
-
return
|
|
56292
|
+
return readFileSync106(join142(sandbox.workspace, rel), "utf8");
|
|
55866
56293
|
} catch {
|
|
55867
56294
|
return "";
|
|
55868
56295
|
}
|
|
@@ -55893,14 +56320,14 @@ function runIdBySlug(sandbox, slug) {
|
|
|
55893
56320
|
}
|
|
55894
56321
|
|
|
55895
56322
|
// src/core/learn/progress.ts
|
|
55896
|
-
import { existsSync as existsSync120, mkdirSync as mkdirSync30, readFileSync as
|
|
56323
|
+
import { existsSync as existsSync120, mkdirSync as mkdirSync30, readFileSync as readFileSync107, writeFileSync as writeFileSync36 } from "node:fs";
|
|
55897
56324
|
import { dirname as dirname24 } from "node:path";
|
|
55898
56325
|
var NO_PROGRESS = { version: 1, completed: [], updatedAt: null };
|
|
55899
56326
|
function readProgress(path) {
|
|
55900
56327
|
if (!existsSync120(path))
|
|
55901
56328
|
return NO_PROGRESS;
|
|
55902
56329
|
try {
|
|
55903
|
-
const doc = JSON.parse(
|
|
56330
|
+
const doc = JSON.parse(readFileSync107(path, "utf8"));
|
|
55904
56331
|
const completed = doc.completed;
|
|
55905
56332
|
if (!Array.isArray(completed))
|
|
55906
56333
|
return NO_PROGRESS;
|