tldr-experts 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +182 -0
- package/README.md +2 -0
- package/dist/hooks/budget-gate.js +1 -1
- package/dist/hooks/{chunk-1p5027fn.js → chunk-2jb272zj.js} +10 -4
- package/dist/hooks/{chunk-ah1pdax3.js → chunk-4frmcwp3.js} +11 -1
- package/dist/hooks/session-start.js +2 -2
- package/dist/hooks/statusline.js +2 -2
- package/dist/tldrx.js +1141 -819
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/dist/tldrx.js
CHANGED
|
@@ -16033,7 +16033,7 @@ var ENTRIES = [
|
|
|
16033
16033
|
{
|
|
16034
16034
|
name: "until-done",
|
|
16035
16035
|
arg: "[<n>]",
|
|
16036
|
-
meaning: "Relaunch the loop IN-PROCESS after an exit it can do nothing else with, at most n times — bare means 5, the cap. `--retry-failed` bounds one stage's exit 5s INSIDE the loop; this bounds the loop ENDING: a stage failure past that bound (5), a thrown error that used to reach the shell as a bare 1 with nothing on the ledger, a refusal whose remedy is the same command typed again (2). Measured on one 28 h unattended run: five such exits, each a person reading it and typing `tldrx run auto` again, 18 h before a story ran. Every relaunch is a `run.relaunched` event with the exit it recovered from, the attempt and the bound, and `run.finished` / `run.failed` go out ONCE, from the last attempt. What it NEVER relaunches over: exit 4 (a person's — `--wait-answers` and `--wait-gates` own it), a `budget.blocked` or this loop's own `--max-usd` (nothing in-process moves a ceiling, and `--max-usd` spans every relaunch rather than resetting), and an attempt
|
|
16036
|
+
meaning: "Relaunch the loop IN-PROCESS after an exit it can do nothing else with, at most n times — bare means 5, the cap. `--retry-failed` bounds one stage's exit 5s INSIDE the loop; this bounds the loop ENDING: a stage failure past that bound (5), a thrown error that used to reach the shell as a bare 1 with nothing on the ledger, a refusal whose remedy is the same command typed again (2). Measured on one 28 h unattended run: five such exits, each a person reading it and typing `tldrx run auto` again, 18 h before a story ran. Every relaunch is a `run.relaunched` event with the exit it recovered from, the attempt and the bound, and `run.finished` / `run.failed` go out ONCE, from the last attempt. What it NEVER relaunches over: exit 4 (a person's — `--wait-answers` and `--wait-gates` own it), a `budget.blocked` or this loop's own `--max-usd` (nothing in-process moves a ceiling, and `--max-usd` spans every relaunch rather than resetting), and an attempt REFUSED BY THE SAME WORDS as the one before it — a refusal that repeats is not one a relaunch moves. The words compared are the ones the stop NAMES, never the last line printed: every stage death ends with the same advice about cost, and comparing that stopped runs that were making progress (#297). `0` is the default and is one launch, exactly as before. Put the run id before the flag, or write `--until-done=3`.",
|
|
16037
16037
|
sub: "auto"
|
|
16038
16038
|
},
|
|
16039
16039
|
{
|
|
@@ -16100,7 +16100,7 @@ var ENTRIES = [
|
|
|
16100
16100
|
exits: [EXIT_OK, EXIT_USAGE, EXIT_GATE_REFUSED, EXIT_NOT_FOUND, EXIT_AWAITING_HUMAN, EXIT_AGENT_FAILED],
|
|
16101
16101
|
notes: [
|
|
16102
16102
|
"`run attend host` is a LOCK, not an engine. It sets one field, spends nothing, runs no stage and touches no branch — and from then on THE FRAMEWORK WILL NOT SPAWN on that run: every turn is a `tldrx next --prepare` / `tldrx next --commit` handshake with the session driving it, the Build reviewer included. `run attend --none` hands it back.",
|
|
16103
|
-
"`run auto` is an ENGINE, not a lock. It calls `next` HEADLESS over and over, so THE FRAMEWORK spawns a metered sub-agent stage after stage, and it stops at the first thing it may not decide: a human gate or an open question (4), a stage failure (5) — unless `--retry-failed <n>` lets it run that stage again, bounded, up to n times in a row — a phase ceiling or this loop's own --max-usd (2). `--until-done [<n>]` puts a bounded relauncher OVER those stops: after a 5 past the retry bound, a throw, or a 2 with no money behind it, the same process runs the loop again and writes `run.relaunched` — never over a person's 4, never over a `budget.blocked`, never twice over the same
|
|
16103
|
+
"`run auto` is an ENGINE, not a lock. It calls `next` HEADLESS over and over, so THE FRAMEWORK spawns a metered sub-agent stage after stage, and it stops at the first thing it may not decide: a human gate or an open question (4), a stage failure (5) — unless `--retry-failed <n>` lets it run that stage again, bounded, up to n times in a row — a phase ceiling or this loop's own --max-usd (2). `--until-done [<n>]` puts a bounded relauncher OVER those stops: after a 5 past the retry bound, a throw, or a 2 with no money behind it, the same process runs the loop again and writes `run.relaunched` — never over a person's 4, never over a `budget.blocked`, never twice over the same refusal. It is REFUSED ON AN ATTENDED RUN (exit 1, before the event log is opened) — a lock and an engine are alternatives, never layers.",
|
|
16104
16104
|
"`run auto` can also TELL SOMEBODY. When `.tldrx/workspace.yml` declares a `notify:` command (§2.18), the loop hands that command one `version: 1` JSON object on stdin at every moment a person is needed — an open question with its options and the literal `tldrx answer` line, a gate with the literal approve line, a finished or failed run with its exit code and family — plus a periodic `status` under `--notify-every`. The framework names no chat tool: the command is the owner's own, run as argv with no shell, and its exit code is recorded as `notify.sent` / `notify.failed` and NEVER changes the run's outcome.",
|
|
16105
16105
|
"Under `run auto`, a stage whose `gates_policy` is `agent` gets one bounded GATE-SIGNER turn of its own. When the stage's checks have passed, the engine spawns a single sub-agent at the stage's model and effort, on a quarter of the stage's per-agent ceiling, allowed to read and to write exactly one file: `.agent/<stage>/evidence.md`. The note then goes through the UNCHANGED `approve --as-agent` path — the same validator a person's note goes through — so `verdict: sign` plus every condition holding closes the gate under the note's own `by:`, and anything else leaves it pending for a person with the reasons named. The turn is recorded like any other (`agent.spawned` / `agent.result`, `role: gate-signer`) and shows up in `tldrx cost`. There is no flag for it: `gates_policy: agent` is already the owner's recorded decision that an agent may close this gate, and `human` gates are never touched.",
|
|
16106
16106
|
"`run auto` walks the LAST MILE when the run was opened with `--ship` (#253): the moment the run reads `done` under it, it runs `tldrx ship` — push, PR, and under `merge` the auto-merge — and `run.finished` carries `pr_url` and `merge`. A ship that is refused is the loop's exit code (2), because the loop was asked for a PR and did not deliver one; the run's own work is still `done` on disk. A run closed by a hand `tldrx approve` outside the loop is shipped by typing `tldrx ship`, which reads the same block.",
|
|
@@ -16449,7 +16449,7 @@ var ENTRIES = [
|
|
|
16449
16449
|
{
|
|
16450
16450
|
name: "as-is",
|
|
16451
16451
|
arg: null,
|
|
16452
|
-
meaning: "Settle the story from its BRANCH AS IT STANDS: the next Build turn runs the dod, the review and the merge over the commits already on `story/<run>/<id>`, and spawns NO developer. For a story a person finished by hand — a rebase after a conflict, a fix typed directly on the branch — which the plain verb cannot land, because a developer handed finished work has nothing to do and the story blocks on the no-work rule. It is not a shortcut past a gate: a red dod blocks the story, and so does a branch carrying no commit its epic has not already got. Requires --note; refused together with --for-fix.",
|
|
16452
|
+
meaning: "Settle the story from its BRANCH AS IT STANDS: the next Build turn runs the dod, the review and the merge over the commits already on `story/<run>/<id>`, and spawns NO developer. For a story a person finished by hand — a rebase after a conflict, a fix typed directly on the branch — which the plain verb cannot land, because a developer handed finished work has nothing to do and the story blocks on the no-work rule. It is not a shortcut past a gate: a red dod blocks the story, and so does a branch carrying no commit its epic has not already got — with one named exception: a story whose work is ALREADY on the epic from an earlier turn and whose review never completed (the reviewer died, or the attempt after the merge died with no work) is settled review-only: nothing is merged, the dod runs on the epic head and the reviewer judges the range the story was merged as; a review that stands (`approve`, `changes`, `fixlist`) still refuses. Requires --note; refused together with --for-fix.",
|
|
16453
16453
|
sub: "reopen"
|
|
16454
16454
|
},
|
|
16455
16455
|
runFlag(),
|
|
@@ -20652,10 +20652,17 @@ function describe4(exitCode, doc, stderr, timedOut, stdout, provider) {
|
|
|
20652
20652
|
return `${name} exited ${exitCode} without a parseable result event: ${tail}`;
|
|
20653
20653
|
}
|
|
20654
20654
|
const errors2 = Array.isArray(doc.errors) ? doc.errors.filter((e) => typeof e === "string") : [];
|
|
20655
|
-
const
|
|
20656
|
-
const
|
|
20657
|
-
|
|
20658
|
-
}
|
|
20655
|
+
const verdict = `${name} exited ${exitCode} with is_error=${String(doc.is_error === true)}`;
|
|
20656
|
+
const named = typeof errors2[0] === "string" && errors2[0] !== "" ? errors2[0] : "";
|
|
20657
|
+
if (named !== "")
|
|
20658
|
+
return `${verdict}: ${named}`;
|
|
20659
|
+
const subtype = typeof doc.subtype === "string" ? doc.subtype : "";
|
|
20660
|
+
if (subtype !== "" && !SUCCESS_SUBTYPES.has(subtype))
|
|
20661
|
+
return `${verdict}: ${subtype}`;
|
|
20662
|
+
const contradiction = subtype === "" ? "" : ` (the provider's own subtype said "${subtype}")`;
|
|
20663
|
+
return `${verdict}: no reason named${contradiction}`;
|
|
20664
|
+
}
|
|
20665
|
+
var SUCCESS_SUBTYPES = new Set(["success"]);
|
|
20659
20666
|
function firstLine3(text2) {
|
|
20660
20667
|
const line = text2.split(`
|
|
20661
20668
|
`).map((l) => l.trim()).find((l) => l !== "");
|
|
@@ -24906,7 +24913,7 @@ function effortFlag(args, name = "effort") {
|
|
|
24906
24913
|
}
|
|
24907
24914
|
|
|
24908
24915
|
// src/core/facilitator/runAuto.ts
|
|
24909
|
-
import { readFileSync as
|
|
24916
|
+
import { readFileSync as readFileSync77 } from "node:fs";
|
|
24910
24917
|
import { join as join102 } from "node:path";
|
|
24911
24918
|
|
|
24912
24919
|
// src/core/build/implicitPlan.ts
|
|
@@ -25937,6 +25944,7 @@ function carriedFindings(findings2) {
|
|
|
25937
25944
|
return findings2.filter((f) => f.disposition === "defer-with-log" && !(f.resolved && f.resolvedSha !== null));
|
|
25938
25945
|
}
|
|
25939
25946
|
var CLAIMED_UNVERIFIED = "claimed-unverified";
|
|
25947
|
+
var FIXLIST_SETTLED_MARK = "every finding is routed away from `fix-now`, so the story is done without a fix round";
|
|
25940
25948
|
function parseFixFindings(value) {
|
|
25941
25949
|
const problems = [];
|
|
25942
25950
|
const format = [];
|
|
@@ -26452,8 +26460,16 @@ function reviewStillOwed(outcome) {
|
|
|
26452
26460
|
}
|
|
26453
26461
|
var AS_IS_MARK = "the branch was taken AS IT STANDS — no developer was spawned for it";
|
|
26454
26462
|
var AS_IS_NOT_AHEAD_MARK = "nothing to take as it stands";
|
|
26455
|
-
|
|
26456
|
-
|
|
26463
|
+
var AS_IS_JUDGED_MARK = "the last review of that work stands";
|
|
26464
|
+
var AS_IS_REVIEW_ONLY_MARK = "the review was run AS IT STANDS — nothing was merged and no developer was spawned";
|
|
26465
|
+
function asIsNotAheadReason(branch, epicBranch, ahead, judged) {
|
|
26466
|
+
if (ahead === null) {
|
|
26467
|
+
return `${AS_IS_NOT_AHEAD_MARK}: git could not count what \`${branch}\` carries beyond ` + `\`${epicBranch}\`, and a settlement that spawns no developer will not merge on a ` + "measurement it does not have. Nothing was merged";
|
|
26468
|
+
}
|
|
26469
|
+
return `${AS_IS_NOT_AHEAD_MARK}: \`${branch}\` carries no commit \`${epicBranch}\` has not already ` + "got, so there is no work to settle" + (judged === undefined ? "" : `, and ${AS_IS_JUDGED_MARK} — \`${judged.commit.slice(0, 7)}\` is on \`${epicBranch}\` and its ` + `review said \`${judged.verdict}\`, so the review is not what is owed`) + ". Commit the fix to the story branch first, then reopen it --as-is. Nothing was merged";
|
|
26470
|
+
}
|
|
26471
|
+
function reviewNeverCompleted(verdict) {
|
|
26472
|
+
return verdict === "n-a" || verdict === "error";
|
|
26457
26473
|
}
|
|
26458
26474
|
function scopedNote(result2) {
|
|
26459
26475
|
if (result2.scope !== "paths" || result2.rendered === undefined)
|
|
@@ -28997,6 +29013,7 @@ function dodLines(r) {
|
|
|
28997
29013
|
` \`${r.outputPath}\` [src: ${r.outputPath}:${String(r.outputLine ?? 1)}]. Read that before re-running anything.`
|
|
28998
29014
|
];
|
|
28999
29015
|
}
|
|
29016
|
+
var WHY_NOT_DONE_HEADING = "## Why it is not done";
|
|
29000
29017
|
function renderReviewLog(outcome) {
|
|
29001
29018
|
const lines = [
|
|
29002
29019
|
`# Review — ${outcome.id} · ${outcome.title}`,
|
|
@@ -29008,7 +29025,7 @@ function renderReviewLog(outcome) {
|
|
|
29008
29025
|
`- Repo: \`${outcome.repo}\` · wave ${outcome.wave} · epic ${outcome.epic}`,
|
|
29009
29026
|
`- Branch: \`${outcome.branch}\` → \`${outcome.epicBranch}\` (${mergeWord(outcome)})`,
|
|
29010
29027
|
`- Commit: ${outcome.commit ?? "(none)"}`,
|
|
29011
|
-
...outcome.asIs == null ? [] : [`- Developer: **none** — ${AS_IS_MARK}. \`${outcome.branch}\` was settled as it stands, ` + `signed by ${outcome.asIs.actor}: ${outcome.asIs.note}. The Definition of Done below and ` + "the reviewer above judged that branch, unchanged."],
|
|
29028
|
+
...outcome.asIs == null ? [] : outcome.asIs.reason === "review-only" ? [`- Developer: **none** — ${AS_IS_REVIEW_ONLY_MARK}. \`${outcome.asIs.reviewed?.commit ?? outcome.commit ?? "(none)"}\` ` + `was already on \`${outcome.epicBranch}\` from an earlier turn and nothing had judged it; ` + `signed by ${outcome.asIs.actor}: ${outcome.asIs.note}. The Definition of Done below ran on the ` + "epic head, and the reviewer above judged the range that merge was recorded as."] : [`- Developer: **none** — ${AS_IS_MARK}. \`${outcome.branch}\` was settled as it stands, ` + `signed by ${outcome.asIs.actor}: ${outcome.asIs.note}. The Definition of Done below and ` + "the reviewer above judged that branch, unchanged."],
|
|
29012
29029
|
...outcome.developerError === null ? [] : [`- Developer: **FAILED** — ${outcome.developerError}`],
|
|
29013
29030
|
...outcome.permissionRefused == null ? [] : [withCure(`- Developer: \`${outcome.permissionRefused}\` was refused for approval by the agent's own ` + "permission layer; the tree held committed work, so the Definition of Done below decided", outcome.permissionRefused, outcome.declaredCommands)],
|
|
29014
29031
|
...outcome.budgetDeath == null ? [] : [`- Developer: died on its per-story cap — ${outcome.budgetDeath}; the tree held work, ` + "so the Definition of Done below decided"],
|
|
@@ -29033,7 +29050,7 @@ function renderReviewLog(outcome) {
|
|
|
29033
29050
|
lines.push("## Uncommitted work rescued", "", ...rescueLines(outcome), "");
|
|
29034
29051
|
}
|
|
29035
29052
|
if (outcome.reason !== null) {
|
|
29036
|
-
lines.push(
|
|
29053
|
+
lines.push(WHY_NOT_DONE_HEADING, "", outcome.reason, "");
|
|
29037
29054
|
}
|
|
29038
29055
|
return lines.join(`
|
|
29039
29056
|
`);
|
|
@@ -29369,6 +29386,161 @@ function round22(n) {
|
|
|
29369
29386
|
return Math.round(n * 100) / 100;
|
|
29370
29387
|
}
|
|
29371
29388
|
|
|
29389
|
+
// src/core/build/caps.ts
|
|
29390
|
+
var MAX_ATTEMPTS2 = STAGE_TUNING_DEFAULTS.attempts;
|
|
29391
|
+
var REVIEWER_SHARE2 = STAGE_TUNING_DEFAULTS.reviewerShare;
|
|
29392
|
+
var REVIEWER_FLOOR_USD2 = 1;
|
|
29393
|
+
var STORY_CAP_MULTIPLIER2 = STAGE_TUNING_DEFAULTS.storyCapMultiplier;
|
|
29394
|
+
var STORY_CAP_FLOOR_USD2 = STAGE_TUNING_DEFAULTS.storyCapFloorUsd;
|
|
29395
|
+
function developerAttemptDivisor2(attempt, attempts = MAX_ATTEMPTS2) {
|
|
29396
|
+
return attempt <= 1 ? 1 : attempts;
|
|
29397
|
+
}
|
|
29398
|
+
var DEFAULT_PARALLEL = 1;
|
|
29399
|
+
function clampParallel(requested) {
|
|
29400
|
+
if (requested === undefined || !Number.isFinite(requested))
|
|
29401
|
+
return DEFAULT_PARALLEL;
|
|
29402
|
+
return Math.max(1, Math.min(MAX_STORIES_PER_WAVE, Math.trunc(requested)));
|
|
29403
|
+
}
|
|
29404
|
+
function attemptsOf(parts) {
|
|
29405
|
+
return parts.attempts ?? MAX_ATTEMPTS2;
|
|
29406
|
+
}
|
|
29407
|
+
function storyCapMultiplierOf(parts) {
|
|
29408
|
+
return parts.storyCapMultiplier ?? STORY_CAP_MULTIPLIER2;
|
|
29409
|
+
}
|
|
29410
|
+
function storyCapFloorOf(parts) {
|
|
29411
|
+
return parts.storyCapFloorUsd ?? STORY_CAP_FLOOR_USD2;
|
|
29412
|
+
}
|
|
29413
|
+
function reviewerShareOf(parts) {
|
|
29414
|
+
return parts.reviewerShare ?? REVIEWER_SHARE2;
|
|
29415
|
+
}
|
|
29416
|
+
function round23(n) {
|
|
29417
|
+
return Math.round(n * 100) / 100;
|
|
29418
|
+
}
|
|
29419
|
+
function developerCap(parts, storyId, attempt = 1) {
|
|
29420
|
+
const price = priceOf(parts, storyId);
|
|
29421
|
+
if (price === null)
|
|
29422
|
+
return parts.agentCap(1 / worstCaseShares(parts));
|
|
29423
|
+
return parts.agentCap(shareOf(parts, storyCeilingUsd(parts, price) / developerAttemptDivisor2(attempt, attemptsOf(parts))));
|
|
29424
|
+
}
|
|
29425
|
+
function storyCeilingUsd(parts, price) {
|
|
29426
|
+
const floor = parts.budgetUsd > 0 ? Math.min(storyCapFloorOf(parts), parts.budgetUsd) : storyCapFloorOf(parts);
|
|
29427
|
+
return Math.max(price * storyCapMultiplierOf(parts), floor);
|
|
29428
|
+
}
|
|
29429
|
+
function reviewerCap(parts, spentUsd, storyId) {
|
|
29430
|
+
const price = priceOf(parts, storyId);
|
|
29431
|
+
const share = reviewerShareOf(parts);
|
|
29432
|
+
const derived = price === null ? parts.agentCap(share / worstCaseShares(parts)) : parts.agentCap(shareOf(parts, price * share / (attemptsOf(parts) * (1 + share))));
|
|
29433
|
+
const floor = Math.min(REVIEWER_FLOOR_USD2, Math.max(parts.budgetUsd - spentUsd, 0));
|
|
29434
|
+
return round23(Math.min(Math.max(derived, floor), parts.maxBudgetUsd));
|
|
29435
|
+
}
|
|
29436
|
+
function stageRemainderUsd(parts, spentUsd) {
|
|
29437
|
+
if (!(parts.budgetUsd > 0))
|
|
29438
|
+
return null;
|
|
29439
|
+
return Math.max(round23(parts.budgetUsd - spentUsd), 0);
|
|
29440
|
+
}
|
|
29441
|
+
function reviewerUnderfunded(parts, spentUsd) {
|
|
29442
|
+
const left = stageRemainderUsd(parts, spentUsd);
|
|
29443
|
+
return left !== null && left < REVIEWER_FLOOR_USD2;
|
|
29444
|
+
}
|
|
29445
|
+
function priceOf(parts, storyId) {
|
|
29446
|
+
if (storyId === undefined)
|
|
29447
|
+
return null;
|
|
29448
|
+
const price = parts.prices.get(storyId);
|
|
29449
|
+
if (price === undefined || !Number.isFinite(price) || price <= 0)
|
|
29450
|
+
return null;
|
|
29451
|
+
return price * priceScale(parts);
|
|
29452
|
+
}
|
|
29453
|
+
function priceScale(parts) {
|
|
29454
|
+
if (parts.budgetUsd <= 0)
|
|
29455
|
+
return 1;
|
|
29456
|
+
const sum = plannedSumUsd(parts);
|
|
29457
|
+
return sum <= parts.budgetUsd ? 1 : parts.budgetUsd / sum;
|
|
29458
|
+
}
|
|
29459
|
+
function plannedSumUsd(parts) {
|
|
29460
|
+
let sum = 0;
|
|
29461
|
+
for (const price of parts.prices.values()) {
|
|
29462
|
+
if (Number.isFinite(price) && price > 0)
|
|
29463
|
+
sum += price;
|
|
29464
|
+
}
|
|
29465
|
+
return sum;
|
|
29466
|
+
}
|
|
29467
|
+
function shareOf(parts, usd) {
|
|
29468
|
+
return parts.budgetUsd <= 0 ? 1 : usd / parts.budgetUsd;
|
|
29469
|
+
}
|
|
29470
|
+
function worstCaseShares(parts) {
|
|
29471
|
+
return Math.max(parts.storyCount, 1) * attemptsOf(parts) * (1 + reviewerShareOf(parts));
|
|
29472
|
+
}
|
|
29473
|
+
function storyCapDerivation(parts, storyId, attempt = 1) {
|
|
29474
|
+
const scaled = priceOf(parts, storyId);
|
|
29475
|
+
if (scaled === null)
|
|
29476
|
+
return null;
|
|
29477
|
+
const planPriceUsd = parts.prices.get(storyId);
|
|
29478
|
+
const floorUsd = parts.budgetUsd > 0 ? Math.min(storyCapFloorOf(parts), parts.budgetUsd) : storyCapFloorOf(parts);
|
|
29479
|
+
return {
|
|
29480
|
+
storyId,
|
|
29481
|
+
attempt,
|
|
29482
|
+
planPriceUsd,
|
|
29483
|
+
plannedSumUsd: plannedSumUsd(parts),
|
|
29484
|
+
stageBudgetUsd: parts.budgetUsd,
|
|
29485
|
+
scale: priceScale(parts),
|
|
29486
|
+
scaledPriceUsd: scaled,
|
|
29487
|
+
multiplier: storyCapMultiplierOf(parts),
|
|
29488
|
+
floorUsd,
|
|
29489
|
+
ceilingUsd: storyCeilingUsd(parts, scaled),
|
|
29490
|
+
divisor: developerAttemptDivisor2(attempt, attemptsOf(parts)),
|
|
29491
|
+
capUsd: developerCap(parts, storyId, attempt)
|
|
29492
|
+
};
|
|
29493
|
+
}
|
|
29494
|
+
function usd(n) {
|
|
29495
|
+
return `$${n.toFixed(2)}`;
|
|
29496
|
+
}
|
|
29497
|
+
function scaleOf(scale) {
|
|
29498
|
+
return scale === 1 ? "1" : scale.toFixed(4);
|
|
29499
|
+
}
|
|
29500
|
+
function describeStoryCap(d) {
|
|
29501
|
+
const multiplied = d.scaledPriceUsd * d.multiplier;
|
|
29502
|
+
const scaleNote = d.scale === 1 ? ` (stage scale 1: ${usd(d.plannedSumUsd)} of plan prices fit the stage's ${usd(d.stageBudgetUsd)})` : ` (stage budget_usd ${usd(d.stageBudgetUsd)} over ${usd(d.plannedSumUsd)} of plan prices)`;
|
|
29503
|
+
const scaleTerm = d.scale === 1 ? "" : ` × stage scale ${scaleOf(d.scale)}${scaleNote}`;
|
|
29504
|
+
const ceiling = multiplied >= d.floorUsd ? `plan price ${usd(d.planPriceUsd)}${scaleTerm} × story_cap_multiplier ${String(d.multiplier)}` + ` = ${usd(multiplied)}${d.scale === 1 ? scaleNote : ""}, above the floor ${usd(d.floorUsd)}` : `story_cap_floor_usd ${usd(d.floorUsd)} (plan price ${usd(d.planPriceUsd)} × stage scale ${scaleOf(d.scale)}` + ` × story_cap_multiplier ${String(d.multiplier)} = ${usd(multiplied)} is below it)`;
|
|
29505
|
+
const perAttempt = round23(d.ceilingUsd / d.divisor);
|
|
29506
|
+
const contingency = d.divisor === 1 ? "" : `; attempt ${String(d.attempt)} is the contingency and gets that ÷ ${String(d.divisor)} = ${usd(perAttempt)}`;
|
|
29507
|
+
const clamp = d.capUsd < perAttempt ? `; clamped to ${usd(d.capUsd)} by per_agent_max_usd / --max-usd` : "";
|
|
29508
|
+
return `cap ${usd(d.capUsd)} = ${ceiling}${contingency}${clamp}`;
|
|
29509
|
+
}
|
|
29510
|
+
function capDeathReason(error, parts, storyId, attempt, lever) {
|
|
29511
|
+
const head2 = `the developer died on its per-story cap — ${error}: `;
|
|
29512
|
+
const d = storyCapDerivation(parts, storyId, attempt);
|
|
29513
|
+
if (d === null) {
|
|
29514
|
+
const cap2 = developerCap(parts, storyId, attempt);
|
|
29515
|
+
return head2 + `this story has no price in \`03-plan/budget.yml\`, so its cap ${usd(cap2)} is the uniform share of the ` + `stage's budget_usd ${usd(parts.budgetUsd)} over ${String(Math.max(parts.storyCount, 1))} story(ies)` + ` × ${String(attemptsOf(parts))} attempt(s) × (1 + reviewer share ${String(reviewerShareOf(parts))})` + ` — the stage's own \`budget_usd\` is what moves it (\`${lever.raiseCommand(parts.budgetUsd)}\` doubles it)`;
|
|
29516
|
+
}
|
|
29517
|
+
const formula = describeStoryCap(d);
|
|
29518
|
+
if (d.scale === 1) {
|
|
29519
|
+
return head2 + formula + "; a story that really costs more than the plan guessed wants a higher price in `03-plan/budget.yml` " + "or a higher `story_cap_multiplier:` on the stage";
|
|
29520
|
+
}
|
|
29521
|
+
const short2 = shortBy(d.plannedSumUsd, d.stageBudgetUsd);
|
|
29522
|
+
return head2 + formula + "; the plan's price cannot move this cap while the plan is scaled — raising every price keeps the " + "ratio and the sum still exceeds the stage — so raise the stage's own `budget_usd` " + `(\`${lever.raiseCommand(short2)}\` lifts the scale to 1) or \`story_cap_multiplier:\` on the stage`;
|
|
29523
|
+
}
|
|
29524
|
+
function planOverStageAdvisory(parts, lever) {
|
|
29525
|
+
const scale = priceScale(parts);
|
|
29526
|
+
if (scale >= 1)
|
|
29527
|
+
return null;
|
|
29528
|
+
const sum = plannedSumUsd(parts);
|
|
29529
|
+
const factor = (sum / parts.budgetUsd).toFixed(1);
|
|
29530
|
+
let largest = null;
|
|
29531
|
+
for (const [id, price] of parts.prices) {
|
|
29532
|
+
if (Number.isFinite(price) && price > 0 && (largest === null || price > (parts.prices.get(largest) ?? 0))) {
|
|
29533
|
+
largest = id;
|
|
29534
|
+
}
|
|
29535
|
+
}
|
|
29536
|
+
const example = largest === null ? null : storyCapDerivation(parts, largest);
|
|
29537
|
+
const worked = example === null ? "" : `: ${example.storyId} priced ${usd(example.planPriceUsd)} gets max(${usd(example.planPriceUsd)} × ${scaleOf(scale)}` + ` × ${String(example.multiplier)}, ${usd(example.floorUsd)}) = ${usd(example.capUsd)}`;
|
|
29538
|
+
return `advisory: 03-plan/budget.yml prices ${usd(sum)} of stories into a stage whose budget_usd is ` + `${usd(parts.budgetUsd)} — ${factor}× what the stage holds — so every per-story cap is derived from ` + `the plan price × ${scaleOf(scale)}, not the price as written${worked}. Raising the prices cannot lift a ` + `cap while their sum exceeds the stage; \`${lever.raiseCommand(shortBy(sum, parts.budgetUsd))}\` ` + "makes the stage hold the plan as priced, or raise `story_cap_multiplier:` on the stage";
|
|
29539
|
+
}
|
|
29540
|
+
function shortBy(estimate, remaining2) {
|
|
29541
|
+
return Math.max(0.01, Math.ceil((estimate - remaining2) * 100) / 100);
|
|
29542
|
+
}
|
|
29543
|
+
|
|
29372
29544
|
// src/core/budget/budgetView.ts
|
|
29373
29545
|
function buildBudgetView(run, budget, runDir) {
|
|
29374
29546
|
const tally = runTally(run);
|
|
@@ -29428,8 +29600,8 @@ function grantLine(view) {
|
|
|
29428
29600
|
if (by === null)
|
|
29429
29601
|
return [];
|
|
29430
29602
|
const scopes = [
|
|
29431
|
-
...view.authorized_usd === null ? [] : [`the run ${
|
|
29432
|
-
...view.phases.filter((phase) => phase.authorized_usd !== null).map((phase) => `${phase.id} ${
|
|
29603
|
+
...view.authorized_usd === null ? [] : [`the run ${usd2(view.authorized_usd)}`],
|
|
29604
|
+
...view.phases.filter((phase) => phase.authorized_usd !== null).map((phase) => `${phase.id} ${usd2(phase.authorized_usd ?? 0)}`)
|
|
29433
29605
|
];
|
|
29434
29606
|
if (scopes.length === 0)
|
|
29435
29607
|
return [];
|
|
@@ -29447,9 +29619,6 @@ function raiseCommand(runId, phaseId, amountUsd) {
|
|
|
29447
29619
|
function stageRaiseCommand(runId, phaseId, stageId, amountUsd) {
|
|
29448
29620
|
return `${raiseCommand(runId, phaseId, amountUsd)} --stage ${stageId}`;
|
|
29449
29621
|
}
|
|
29450
|
-
function shortBy(estimate, remaining2) {
|
|
29451
|
-
return Math.max(0.01, Math.ceil((estimate - remaining2) * 100) / 100);
|
|
29452
|
-
}
|
|
29453
29622
|
function nextStageOf(stages) {
|
|
29454
29623
|
return stages.find((stage2) => !isTerminal(stage2.status)) ?? null;
|
|
29455
29624
|
}
|
|
@@ -29458,29 +29627,29 @@ function renderBudget(view) {
|
|
|
29458
29627
|
const stageWidth = Math.max(...view.phases.map((p) => (p.next_stage ?? "—").length), 10);
|
|
29459
29628
|
const lines = [
|
|
29460
29629
|
`${view.run} · ${view.title}`,
|
|
29461
|
-
`ceiling ${
|
|
29630
|
+
`ceiling ${usd2(view.ceiling_usd)} · spent ${spentFigure({
|
|
29462
29631
|
usd: view.spent_usd,
|
|
29463
29632
|
unmetered: view.unmetered_tasks,
|
|
29464
29633
|
metered: view.metered_tasks
|
|
29465
|
-
})}` + ` · left ${
|
|
29634
|
+
})}` + ` · left ${usd2(view.remaining_usd)} · ` + `per-agent max ${usd2(view.per_agent_max_usd)} · on_exceed ${view.on_exceed}`,
|
|
29466
29635
|
...view.unmetered_tasks === 0 ? [] : [unmeteredNote(view.unmetered_tasks)],
|
|
29467
29636
|
...grantLine(view),
|
|
29468
29637
|
"",
|
|
29469
29638
|
` ${"phase".padEnd(width)} ${pad2("ceiling")} ${pad2("spent")} ${pad2("left")} ` + `${"next stage".padEnd(stageWidth)} ${pad2("est.")} next`
|
|
29470
29639
|
];
|
|
29471
29640
|
for (const phase of view.phases) {
|
|
29472
|
-
lines.push(`${phase.is_cursor ? ">" : " "} ${phase.id.padEnd(width)} ${pad2(
|
|
29641
|
+
lines.push(`${phase.is_cursor ? ">" : " "} ${phase.id.padEnd(width)} ${pad2(usd2(phase.ceiling_usd))} ` + `${pad2(usd2(phase.spent_usd))} ${pad2(usd2(phase.remaining_usd))} ` + `${(phase.next_stage ?? "—").padEnd(stageWidth)} ` + `${pad2(phase.next_stage === null ? "—" : usd2(phase.next_estimate_usd))} ` + `${phase.next_stage === null ? "—" : phase.blocked ? "BLOCKED" : "ok"}`);
|
|
29473
29642
|
}
|
|
29474
29643
|
for (const phase of view.phases) {
|
|
29475
29644
|
if (phase.next_estimate_detail !== null) {
|
|
29476
|
-
lines.push(` ${phase.id}: ${phase.next_estimate_detail} ` + `(stage estimate ${
|
|
29645
|
+
lines.push(` ${phase.id}: ${phase.next_estimate_detail} ` + `(stage estimate ${usd2(phase.next_estimate_static_usd)})`);
|
|
29477
29646
|
}
|
|
29478
29647
|
}
|
|
29479
29648
|
const blocked = view.blocked;
|
|
29480
29649
|
if (blocked === null) {
|
|
29481
29650
|
lines.push("", "`tldrx next` is affordable in every phase that still has a stage to run.");
|
|
29482
29651
|
} else {
|
|
29483
|
-
lines.push("", `\`tldrx next\` is BLOCKED: phase ${blocked.id} has ${
|
|
29652
|
+
lines.push("", `\`tldrx next\` is BLOCKED: phase ${blocked.id} has ${usd2(blocked.remaining_usd)} left and stage ` + `\`${blocked.next_stage ?? "?"}\` estimates ${usd2(blocked.next_estimate_usd)} ` + `(short by ${usd2(blocked.short_by_usd)}).`, `Fix it with: ${view.fix_command ?? ""}`, `Or move the money instead of adding it: ${view.fix_command ?? ""} --take-from <phase>`);
|
|
29484
29653
|
}
|
|
29485
29654
|
return lines.join(`
|
|
29486
29655
|
`);
|
|
@@ -29488,7 +29657,7 @@ function renderBudget(view) {
|
|
|
29488
29657
|
function pad2(text3) {
|
|
29489
29658
|
return text3.padStart(9);
|
|
29490
29659
|
}
|
|
29491
|
-
function
|
|
29660
|
+
function usd2(amount) {
|
|
29492
29661
|
return `$${amount.toFixed(2)}`;
|
|
29493
29662
|
}
|
|
29494
29663
|
function round7(n) {
|
|
@@ -29598,6 +29767,26 @@ import { join as join61 } from "node:path";
|
|
|
29598
29767
|
|
|
29599
29768
|
// src/core/build/handoff.ts
|
|
29600
29769
|
var MAX_CARRIED_BULLETS = 25;
|
|
29770
|
+
var LINE_BREAK_MARK = "⏎";
|
|
29771
|
+
var LINE_BREAK_NOTE = `\`${LINE_BREAK_MARK}\` marks a line break inside a quoted command; the review log beside this file ` + "keeps the command verbatim.";
|
|
29772
|
+
function ensureLineBreakNote(document) {
|
|
29773
|
+
if (!document.includes(LINE_BREAK_MARK) || document.includes(LINE_BREAK_NOTE))
|
|
29774
|
+
return document;
|
|
29775
|
+
const lines = document.split(`
|
|
29776
|
+
`);
|
|
29777
|
+
const at = lines.findIndex((line) => line.startsWith("## "));
|
|
29778
|
+
if (at === -1)
|
|
29779
|
+
return `${document.trimEnd()}
|
|
29780
|
+
|
|
29781
|
+
${LINE_BREAK_NOTE}
|
|
29782
|
+
`;
|
|
29783
|
+
lines.splice(at, 0, LINE_BREAK_NOTE, "");
|
|
29784
|
+
return lines.join(`
|
|
29785
|
+
`);
|
|
29786
|
+
}
|
|
29787
|
+
function asOneLine(line) {
|
|
29788
|
+
return line.replace(/[ \t]*\r?\n[ \t]*/g, ` ${LINE_BREAK_MARK} `);
|
|
29789
|
+
}
|
|
29601
29790
|
function renderBuildHandoff(parts) {
|
|
29602
29791
|
const done = parts.outcomes.filter((o) => o.status === "done");
|
|
29603
29792
|
const notDone = parts.outcomes.filter((o) => o.status !== "done");
|
|
@@ -29648,8 +29837,8 @@ function renderBuildHandoff(parts) {
|
|
|
29648
29837
|
...parts.epics.length === 0 ? ["- (no epic branch was written)"] : gateRows(parts.epics).map((e) => `- \`${e.branch}\` in ${e.repos.join(", ")} → \`${e.defaultBranches.join(", ")}\` ` + `(${mergeSummary(e)})`),
|
|
29649
29838
|
""
|
|
29650
29839
|
];
|
|
29651
|
-
return lines.join(`
|
|
29652
|
-
`);
|
|
29840
|
+
return ensureLineBreakNote(lines.map(asOneLine).join(`
|
|
29841
|
+
`));
|
|
29653
29842
|
}
|
|
29654
29843
|
function gateRows(epics) {
|
|
29655
29844
|
const byBranch = new Map;
|
|
@@ -29913,12 +30102,412 @@ function statusWithOutcome(status, outcome) {
|
|
|
29913
30102
|
}
|
|
29914
30103
|
|
|
29915
30104
|
// src/core/run/autoGate.ts
|
|
29916
|
-
import { existsSync as
|
|
30105
|
+
import { existsSync as existsSync46, readFileSync as readFileSync42 } from "node:fs";
|
|
29917
30106
|
import { join as join63 } from "node:path";
|
|
29918
30107
|
|
|
29919
30108
|
// src/core/run/checks.ts
|
|
29920
|
-
import { existsSync as
|
|
30109
|
+
import { existsSync as existsSync45, readFileSync as readFileSync41 } from "node:fs";
|
|
29921
30110
|
import { basename as basename10, join as join62 } from "node:path";
|
|
30111
|
+
|
|
30112
|
+
// src/core/facilitator/stageSpec.ts
|
|
30113
|
+
import { existsSync as existsSync44, readFileSync as readFileSync40 } from "node:fs";
|
|
30114
|
+
|
|
30115
|
+
// src/core/facilitator/seedInputs.ts
|
|
30116
|
+
import { readFileSync as readFileSync39, statSync as statSync16 } from "node:fs";
|
|
30117
|
+
var DEFAULT_INPUTS_MAX_BYTES = 256 * 1024;
|
|
30118
|
+
var MAX_SEED_INLINE_BYTES = 64 * 1024;
|
|
30119
|
+
var MIN_SLICE_BYTES = 2 * 1024;
|
|
30120
|
+
function inlineInputs(declared, options) {
|
|
30121
|
+
const budget = options.budgetBytes ?? DEFAULT_INPUTS_MAX_BYTES;
|
|
30122
|
+
const exempt = options.exempt ?? new Set;
|
|
30123
|
+
const inputs = [];
|
|
30124
|
+
const truncated = [];
|
|
30125
|
+
let spent = 0;
|
|
30126
|
+
let seedOverflowed = false;
|
|
30127
|
+
for (const path of declared) {
|
|
30128
|
+
const abs = resolveDeclared(path, options.ctx);
|
|
30129
|
+
if (exempt.has(path)) {
|
|
30130
|
+
inputs.push({ path, content: readOrEmpty3(abs) });
|
|
30131
|
+
continue;
|
|
30132
|
+
}
|
|
30133
|
+
const size = sizeOf4(abs);
|
|
30134
|
+
const room = budget - spent;
|
|
30135
|
+
if (size <= room) {
|
|
30136
|
+
spent += size;
|
|
30137
|
+
inputs.push({ path, content: readOrEmpty3(abs) });
|
|
30138
|
+
continue;
|
|
30139
|
+
}
|
|
30140
|
+
if (options.seed.has(path))
|
|
30141
|
+
seedOverflowed = true;
|
|
30142
|
+
if (room >= MIN_SLICE_BYTES) {
|
|
30143
|
+
spent += room;
|
|
30144
|
+
truncated.push({ path, totalBytes: size, inlinedBytes: room });
|
|
30145
|
+
inputs.push({ path, content: slice(abs, room), inlinedBytes: room, totalBytes: size });
|
|
30146
|
+
continue;
|
|
30147
|
+
}
|
|
30148
|
+
truncated.push({ path, totalBytes: size, inlinedBytes: 0 });
|
|
30149
|
+
inputs.push({ path, content: "", inlinedBytes: 0, totalBytes: size });
|
|
30150
|
+
}
|
|
30151
|
+
return {
|
|
30152
|
+
inputs,
|
|
30153
|
+
note: describe5(budget, truncated, seedOverflowed),
|
|
30154
|
+
truncated,
|
|
30155
|
+
inlinedBytes: spent,
|
|
30156
|
+
budgetBytes: budget
|
|
30157
|
+
};
|
|
30158
|
+
}
|
|
30159
|
+
function describe5(budget, truncated, seedOverflowed) {
|
|
30160
|
+
if (truncated.length === 0)
|
|
30161
|
+
return null;
|
|
30162
|
+
const listed = truncated.map((entry) => `${entry.path} (${entry.totalBytes.toLocaleString("en-US")} B` + `${entry.inlinedBytes === 0 ? ", none inlined" : `, first ${entry.inlinedBytes.toLocaleString("en-US")} B only`})`).join("; ");
|
|
30163
|
+
const fix = seedOverflowed ? "raise `inputs_max_bytes` in the stage file or split the seed (`tldrx seed triage`)" : "raise `inputs_max_bytes` in the stage file";
|
|
30164
|
+
return `truncated inputs: ${listed} — the ${budget.toLocaleString("en-US")}-byte ` + `\`inputs_max_bytes\` budget ran out. ${fix}. ` + "Work from what IS inlined and say in your handoff what you could not read.";
|
|
30165
|
+
}
|
|
30166
|
+
function describeTruncatedInputs(result2) {
|
|
30167
|
+
return result2.truncated.map((entry) => `truncated input: ${entry.path} (${entry.totalBytes.toLocaleString("en-US")} B` + `${entry.inlinedBytes === 0 ? ", none inlined" : `, ${entry.inlinedBytes.toLocaleString("en-US")} B inlined`})` + ` — raise inputs_max_bytes (now ${result2.budgetBytes.toLocaleString("en-US")}) or split the seed`);
|
|
30168
|
+
}
|
|
30169
|
+
function capInputs(declared, max = MAX_STAGE_INPUTS) {
|
|
30170
|
+
return declared.length <= max ? declared : declared.slice(0, max);
|
|
30171
|
+
}
|
|
30172
|
+
function sizeOf4(abs) {
|
|
30173
|
+
try {
|
|
30174
|
+
return statSync16(abs).size;
|
|
30175
|
+
} catch {
|
|
30176
|
+
return 0;
|
|
30177
|
+
}
|
|
30178
|
+
}
|
|
30179
|
+
function slice(abs, bytes) {
|
|
30180
|
+
try {
|
|
30181
|
+
return readFileSync39(abs).subarray(0, bytes).toString("utf8");
|
|
30182
|
+
} catch {
|
|
30183
|
+
return "";
|
|
30184
|
+
}
|
|
30185
|
+
}
|
|
30186
|
+
function readOrEmpty3(abs) {
|
|
30187
|
+
try {
|
|
30188
|
+
return readFileSync39(abs, "utf8");
|
|
30189
|
+
} catch {
|
|
30190
|
+
return "";
|
|
30191
|
+
}
|
|
30192
|
+
}
|
|
30193
|
+
|
|
30194
|
+
// src/core/budget/modelPrices.ts
|
|
30195
|
+
var CACHE_WRITE_MULTIPLIER = 1.25;
|
|
30196
|
+
var CACHE_READ_MULTIPLIER = 0.1;
|
|
30197
|
+
var BYTES_PER_TOKEN = 3.6;
|
|
30198
|
+
var MODEL_PRICES = [
|
|
30199
|
+
{ id: "opus", contextTokens: 200000, inputUsdPerMTok: 5, outputUsdPerMTok: 25 },
|
|
30200
|
+
{ id: "opus[1m]", contextTokens: 1e6, inputUsdPerMTok: 5, outputUsdPerMTok: 25 },
|
|
30201
|
+
{ id: "sonnet", contextTokens: 200000, inputUsdPerMTok: 2, outputUsdPerMTok: 10 },
|
|
30202
|
+
{ id: "sonnet[1m]", contextTokens: 1e6, inputUsdPerMTok: 2, outputUsdPerMTok: 10 },
|
|
30203
|
+
{ id: "haiku", contextTokens: 200000, inputUsdPerMTok: 1, outputUsdPerMTok: 5 }
|
|
30204
|
+
];
|
|
30205
|
+
var ONE_MILLION_MARKER = "[1m]";
|
|
30206
|
+
var ONE_MILLION_TOKENS = 1e6;
|
|
30207
|
+
function familyOf(id) {
|
|
30208
|
+
return id.endsWith(ONE_MILLION_MARKER) ? id.slice(0, -ONE_MILLION_MARKER.length) : id;
|
|
30209
|
+
}
|
|
30210
|
+
function priceFor(model2) {
|
|
30211
|
+
if (model2 === null)
|
|
30212
|
+
return null;
|
|
30213
|
+
const name = model2.toLowerCase();
|
|
30214
|
+
const oneMillion = name.includes(ONE_MILLION_MARKER);
|
|
30215
|
+
let best = null;
|
|
30216
|
+
for (const row2 of MODEL_PRICES) {
|
|
30217
|
+
const family = familyOf(row2.id);
|
|
30218
|
+
if (family !== row2.id && !oneMillion)
|
|
30219
|
+
continue;
|
|
30220
|
+
if (!name.includes(family))
|
|
30221
|
+
continue;
|
|
30222
|
+
if (best === null || row2.id.length > best.id.length)
|
|
30223
|
+
best = row2;
|
|
30224
|
+
}
|
|
30225
|
+
return best;
|
|
30226
|
+
}
|
|
30227
|
+
function knownContextTokensFor(model2) {
|
|
30228
|
+
const priced = priceFor(model2);
|
|
30229
|
+
if (priced !== null)
|
|
30230
|
+
return priced.contextTokens;
|
|
30231
|
+
if (model2 !== null && model2.toLowerCase().includes(ONE_MILLION_MARKER))
|
|
30232
|
+
return ONE_MILLION_TOKENS;
|
|
30233
|
+
return null;
|
|
30234
|
+
}
|
|
30235
|
+
function estimateTokensFromBytes(bytes) {
|
|
30236
|
+
return Math.round(bytes / BYTES_PER_TOKEN);
|
|
30237
|
+
}
|
|
30238
|
+
|
|
30239
|
+
// src/core/facilitator/contextLedger.ts
|
|
30240
|
+
var DEFAULT_PROMPT_MAX_BYTES = 400 * 1024;
|
|
30241
|
+
var CONTEXT_WARN_PCT = 80;
|
|
30242
|
+
function buildLedger(input) {
|
|
30243
|
+
const rows = [];
|
|
30244
|
+
let stage2 = 0;
|
|
30245
|
+
let inputs = 0;
|
|
30246
|
+
let expertBodies = 0;
|
|
30247
|
+
let expertKnowledge = 0;
|
|
30248
|
+
let dispatchNotes = 0;
|
|
30249
|
+
let projectSkills = 0;
|
|
30250
|
+
let previousAttempt = 0;
|
|
30251
|
+
for (const part of input.parts) {
|
|
30252
|
+
const bytes = byteLength(part.text);
|
|
30253
|
+
switch (part.kind) {
|
|
30254
|
+
case "preamble":
|
|
30255
|
+
stage2 += bytes;
|
|
30256
|
+
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
30257
|
+
break;
|
|
30258
|
+
case "stage":
|
|
30259
|
+
stage2 += bytes;
|
|
30260
|
+
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
30261
|
+
break;
|
|
30262
|
+
case "expert-body":
|
|
30263
|
+
expertBodies += bytes;
|
|
30264
|
+
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
30265
|
+
break;
|
|
30266
|
+
case "expert-knowledge":
|
|
30267
|
+
expertKnowledge += bytes;
|
|
30268
|
+
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
30269
|
+
break;
|
|
30270
|
+
case "dispatch-notes":
|
|
30271
|
+
dispatchNotes += bytes;
|
|
30272
|
+
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
30273
|
+
break;
|
|
30274
|
+
case "project-skills":
|
|
30275
|
+
projectSkills += bytes;
|
|
30276
|
+
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
30277
|
+
break;
|
|
30278
|
+
case "previous-attempt":
|
|
30279
|
+
previousAttempt += bytes;
|
|
30280
|
+
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
30281
|
+
break;
|
|
30282
|
+
case "inputs":
|
|
30283
|
+
inputs += bytes;
|
|
30284
|
+
for (const file of input.inputBytes) {
|
|
30285
|
+
rows.push({ kind: "inputs", name: file.path, bytes: file.bytes });
|
|
30286
|
+
}
|
|
30287
|
+
break;
|
|
30288
|
+
}
|
|
30289
|
+
}
|
|
30290
|
+
const totalBytes = stage2 + inputs + expertBodies + expertKnowledge + dispatchNotes + projectSkills + previousAttempt;
|
|
30291
|
+
const estimatedTokens = estimateTokensFromBytes(totalBytes);
|
|
30292
|
+
const contextTokens = knownContextTokensFor(input.model);
|
|
30293
|
+
const contextPct = contextTokens === null || contextTokens === 0 ? null : estimatedTokens / contextTokens * 100;
|
|
30294
|
+
return {
|
|
30295
|
+
totalBytes,
|
|
30296
|
+
rows,
|
|
30297
|
+
groups: {
|
|
30298
|
+
stage: stage2,
|
|
30299
|
+
inputs,
|
|
30300
|
+
experts: expertBodies + expertKnowledge,
|
|
30301
|
+
expertBodies,
|
|
30302
|
+
expertKnowledge,
|
|
30303
|
+
dispatchNotes,
|
|
30304
|
+
projectSkills,
|
|
30305
|
+
previousAttempt,
|
|
30306
|
+
questions: input.questionsBytes ?? 0
|
|
30307
|
+
},
|
|
30308
|
+
truncatedInputs: input.truncatedInputs,
|
|
30309
|
+
limitBytes: input.limitBytes,
|
|
30310
|
+
overLimit: totalBytes > input.limitBytes,
|
|
30311
|
+
model: input.model,
|
|
30312
|
+
estimatedTokens,
|
|
30313
|
+
contextTokens,
|
|
30314
|
+
contextPct,
|
|
30315
|
+
contextWarns: contextPct !== null && contextPct >= CONTEXT_WARN_PCT
|
|
30316
|
+
};
|
|
30317
|
+
}
|
|
30318
|
+
function questionsBytesOf(stageMd) {
|
|
30319
|
+
const lines = stageMd.split(`
|
|
30320
|
+
`);
|
|
30321
|
+
const start = lines.findIndex((line) => line.trim() === "## Questions");
|
|
30322
|
+
if (start === -1)
|
|
30323
|
+
return 0;
|
|
30324
|
+
let end = lines.length;
|
|
30325
|
+
for (let i = start + 1;i < lines.length; i++) {
|
|
30326
|
+
if ((lines[i] ?? "").startsWith("## ")) {
|
|
30327
|
+
end = i;
|
|
30328
|
+
break;
|
|
30329
|
+
}
|
|
30330
|
+
}
|
|
30331
|
+
return byteLength(lines.slice(start, end).join(`
|
|
30332
|
+
`));
|
|
30333
|
+
}
|
|
30334
|
+
function renderLedger(ledger3, maxRows = 8) {
|
|
30335
|
+
const g = ledger3.groups;
|
|
30336
|
+
const lines = [
|
|
30337
|
+
`context ${bytes(ledger3.totalBytes)} of ${bytes(ledger3.limitBytes)} ` + `(~${tokens2(ledger3.estimatedTokens)} tok${windowClause(ledger3)})`,
|
|
30338
|
+
` stage ${bytes(g.stage)}${g.questions === 0 ? "" : ` (questions ${bytes(g.questions)})`}` + ` · inputs ${bytes(g.inputs)} · experts ${bytes(g.experts)}` + ` (bodies ${bytes(g.expertBodies)}, knowledge ${bytes(g.expertKnowledge)})` + (g.dispatchNotes === 0 ? "" : ` · dispatch notes ${bytes(g.dispatchNotes)}`) + (g.projectSkills === 0 ? "" : ` · project skills ${bytes(g.projectSkills)}`) + (g.previousAttempt === 0 ? "" : ` · previous attempt ${bytes(g.previousAttempt)}`)
|
|
30339
|
+
];
|
|
30340
|
+
const biggest = [...ledger3.rows].filter((row2) => row2.kind !== "inputs" || row2.name !== "Inputs").sort((a, b) => b.bytes - a.bytes).slice(0, maxRows);
|
|
30341
|
+
for (const row2 of biggest) {
|
|
30342
|
+
lines.push(` ${label(row2)} ${bytes(row2.bytes)}`);
|
|
30343
|
+
}
|
|
30344
|
+
return lines;
|
|
30345
|
+
}
|
|
30346
|
+
function renderRefusal(ledger3, stageId) {
|
|
30347
|
+
const g = ledger3.groups;
|
|
30348
|
+
const lines = [
|
|
30349
|
+
`[tldrx] context: refusing to start stage "${stageId}" — the prompt is ` + `${bytes(ledger3.totalBytes)} and prompt_max_bytes is ${bytes(ledger3.limitBytes)}.`,
|
|
30350
|
+
...renderLedger(ledger3, 5),
|
|
30351
|
+
"Shrink it, then run again:"
|
|
30352
|
+
];
|
|
30353
|
+
if (g.inputs >= g.experts) {
|
|
30354
|
+
lines.push(` inputs are the biggest section (${bytes(g.inputs)}) — lower \`inputs_max_bytes\` ` + "in the stage file, declare fewer inputs, or split the seed with `tldrx seed triage`.");
|
|
30355
|
+
}
|
|
30356
|
+
if (g.experts > 0) {
|
|
30357
|
+
lines.push(` experts are ${bytes(g.experts)} (${bytes(g.expertKnowledge)} of it trained knowledge) — ` + "lower `knowledge_max_bytes` in the stage file, or narrow the stage's `experts:` list.");
|
|
30358
|
+
}
|
|
30359
|
+
lines.push(` or raise the ceiling deliberately: \`prompt_max_bytes\` in the stage file, ` + "or `--prompt-max-bytes <n>` for this one invocation.");
|
|
30360
|
+
return lines;
|
|
30361
|
+
}
|
|
30362
|
+
function renderContextWarning(ledger3) {
|
|
30363
|
+
if (!ledger3.contextWarns || ledger3.contextTokens === null || ledger3.contextPct === null)
|
|
30364
|
+
return [];
|
|
30365
|
+
return [
|
|
30366
|
+
`note: this prompt is ~${tokens2(ledger3.estimatedTokens)} tokens, ` + `${ledger3.contextPct.toFixed(0)}% of ${shortModel2(ledger3.model)}'s ~${tokens2(ledger3.contextTokens)}-token ` + "window [assumption: both the window and the ~3.6 bytes/token ratio are estimates, " + "see src/core/budget/modelPrices.ts]. The sub-agent still has to fit its own reading and " + "its answer in what is left."
|
|
30367
|
+
];
|
|
30368
|
+
}
|
|
30369
|
+
function windowClause(ledger3) {
|
|
30370
|
+
if (ledger3.contextTokens === null || ledger3.contextPct === null)
|
|
30371
|
+
return "";
|
|
30372
|
+
return `, ${ledger3.contextPct.toFixed(0)}% of ${shortModel2(ledger3.model)}'s ` + `~${tokens2(ledger3.contextTokens)} window`;
|
|
30373
|
+
}
|
|
30374
|
+
function label(row2) {
|
|
30375
|
+
switch (row2.kind) {
|
|
30376
|
+
case "stage":
|
|
30377
|
+
return "stage.md";
|
|
30378
|
+
case "inputs":
|
|
30379
|
+
return `input ${row2.name}`;
|
|
30380
|
+
case "expert-body":
|
|
30381
|
+
return `expert ${row2.name} body`;
|
|
30382
|
+
case "expert-knowledge":
|
|
30383
|
+
return `expert ${row2.name} knowledge`;
|
|
30384
|
+
case "preamble":
|
|
30385
|
+
return "stage preamble";
|
|
30386
|
+
case "dispatch-notes":
|
|
30387
|
+
return "dispatch notes";
|
|
30388
|
+
case "project-skills":
|
|
30389
|
+
return "project skills";
|
|
30390
|
+
case "previous-attempt":
|
|
30391
|
+
return "previous attempt";
|
|
30392
|
+
}
|
|
30393
|
+
}
|
|
30394
|
+
function shortModel2(model2) {
|
|
30395
|
+
return model2 === null || model2 === "" ? "the default model" : model2;
|
|
30396
|
+
}
|
|
30397
|
+
function bytes(count) {
|
|
30398
|
+
return count < 1024 ? `${String(count)} B` : `${(count / 1024).toFixed(1)} KB`;
|
|
30399
|
+
}
|
|
30400
|
+
function tokens2(count) {
|
|
30401
|
+
return count < 1000 ? String(count) : `${(count / 1000).toFixed(1)}k`;
|
|
30402
|
+
}
|
|
30403
|
+
|
|
30404
|
+
// src/core/facilitator/stageSpec.ts
|
|
30405
|
+
var DEFAULT_STACK_EXPERTS = true;
|
|
30406
|
+
var DEFAULT_DRY_RUN_ALLOWED = true;
|
|
30407
|
+
function loadStageSpec(root2, scope, stageId) {
|
|
30408
|
+
const preset = loadWorkflowPreset(root2, scope);
|
|
30409
|
+
const planned = preset.stages.find((s) => s.id === stageId);
|
|
30410
|
+
if (planned === undefined) {
|
|
30411
|
+
throw new PresetError(`stage '${stageId}' is not in workflow '${preset.name}' (${preset.source})`);
|
|
30412
|
+
}
|
|
30413
|
+
const over = overlay(root2, scope, stageId);
|
|
30414
|
+
return {
|
|
30415
|
+
planned,
|
|
30416
|
+
scope: preset.name,
|
|
30417
|
+
skips: preset.skips,
|
|
30418
|
+
...over,
|
|
30419
|
+
tuning: { ...over.tuning, attempts: planned.attempts },
|
|
30420
|
+
...inputSplit(root2, stageId)
|
|
30421
|
+
};
|
|
30422
|
+
}
|
|
30423
|
+
function overlay(root2, scope, stageId) {
|
|
30424
|
+
const stageDoc = readDoc(stagePath(root2, stageId));
|
|
30425
|
+
const workflowDoc = readDoc(workflowPath(root2, scope));
|
|
30426
|
+
let skipIf = null;
|
|
30427
|
+
const entries = isRecord(workflowDoc) && Array.isArray(workflowDoc.stages) ? workflowDoc.stages : [];
|
|
30428
|
+
for (const entry of entries) {
|
|
30429
|
+
if (!isRecord(entry) || entry.id !== stageId)
|
|
30430
|
+
continue;
|
|
30431
|
+
if (typeof entry.skip_if === "string" && entry.skip_if !== "")
|
|
30432
|
+
skipIf = entry.skip_if;
|
|
30433
|
+
}
|
|
30434
|
+
const questions = isRecord(stageDoc) && isRecord(stageDoc.questions) ? stageDoc.questions : null;
|
|
30435
|
+
return {
|
|
30436
|
+
stackExperts: isRecord(stageDoc) && typeof stageDoc.stack_experts === "boolean" ? stageDoc.stack_experts : DEFAULT_STACK_EXPERTS,
|
|
30437
|
+
knowledgeMaxBytes: byteKey(stageDoc, "knowledge_max_bytes") ?? byteKey(stageDoc, "expert_knowledge_bytes") ?? DEFAULT_KNOWLEDGE_MAX_BYTES,
|
|
30438
|
+
inputsMaxBytes: byteKey(stageDoc, "inputs_max_bytes") ?? DEFAULT_INPUTS_MAX_BYTES,
|
|
30439
|
+
promptMaxBytes: byteKey(stageDoc, "prompt_max_bytes") ?? DEFAULT_PROMPT_MAX_BYTES,
|
|
30440
|
+
maxReads: byteKey(stageDoc, "max_reads") ?? defaultMaxReads(stageId),
|
|
30441
|
+
parallel: parallelKey(workflowDoc, stageId) ?? byteKey(stageDoc, "parallel"),
|
|
30442
|
+
reviewer: reviewerOverride(isRecord(stageDoc) ? stageDoc.reviewer : undefined),
|
|
30443
|
+
reviewerByStakes: reviewerByStakesMap(isRecord(stageDoc) ? stageDoc.reviewer_by_stakes : undefined),
|
|
30444
|
+
tuning: readStageTuning(stageDoc),
|
|
30445
|
+
dryRunAllowed: isRecord(stageDoc) && typeof stageDoc.dry_run_allowed === "boolean" ? stageDoc.dry_run_allowed : DEFAULT_DRY_RUN_ALLOWED,
|
|
30446
|
+
skipIf,
|
|
30447
|
+
questionsMax: questions !== null && typeof questions.max === "number" ? questions.max : null
|
|
30448
|
+
};
|
|
30449
|
+
}
|
|
30450
|
+
function inputSplit(root2, stageId) {
|
|
30451
|
+
const doc = readDoc(stagePath(root2, stageId));
|
|
30452
|
+
const inputs = isRecord(doc) ? doc.inputs : undefined;
|
|
30453
|
+
const topLevelSeed = isRecord(doc) && doc.seed === true;
|
|
30454
|
+
if (isRecord(inputs)) {
|
|
30455
|
+
return {
|
|
30456
|
+
requiredInputs: strings2(inputs.required),
|
|
30457
|
+
optionalInputs: strings2(inputs.optional),
|
|
30458
|
+
seedInputs: inputs.seed === true || topLevelSeed
|
|
30459
|
+
};
|
|
30460
|
+
}
|
|
30461
|
+
return { requiredInputs: [], optionalInputs: strings2(inputs), seedInputs: topLevelSeed };
|
|
30462
|
+
}
|
|
30463
|
+
function reviewerOverride(value) {
|
|
30464
|
+
if (!isRecord(value))
|
|
30465
|
+
return null;
|
|
30466
|
+
const model2 = typeof value.model === "string" && value.model !== "" ? value.model : undefined;
|
|
30467
|
+
const effort2 = EFFORT_LEVELS.includes(String(value.effort)) ? value.effort : undefined;
|
|
30468
|
+
if (model2 === undefined && effort2 === undefined)
|
|
30469
|
+
return null;
|
|
30470
|
+
return { ...model2 === undefined ? {} : { model: model2 }, ...effort2 === undefined ? {} : { effort: effort2 } };
|
|
30471
|
+
}
|
|
30472
|
+
function reviewerByStakesMap(value) {
|
|
30473
|
+
if (!isRecord(value))
|
|
30474
|
+
return {};
|
|
30475
|
+
const out = {};
|
|
30476
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
30477
|
+
const override = reviewerOverride(entry);
|
|
30478
|
+
if (override !== null)
|
|
30479
|
+
out[key] = override;
|
|
30480
|
+
}
|
|
30481
|
+
return out;
|
|
30482
|
+
}
|
|
30483
|
+
function parallelKey(workflowDoc, stageId) {
|
|
30484
|
+
if (!isRecord(workflowDoc))
|
|
30485
|
+
return null;
|
|
30486
|
+
const entry = workflowDoc[stageId];
|
|
30487
|
+
return isRecord(entry) ? byteKey(entry, "parallel") : null;
|
|
30488
|
+
}
|
|
30489
|
+
function byteKey(doc, key) {
|
|
30490
|
+
if (!isRecord(doc))
|
|
30491
|
+
return null;
|
|
30492
|
+
const value = doc[key];
|
|
30493
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
30494
|
+
return null;
|
|
30495
|
+
return Math.trunc(value);
|
|
30496
|
+
}
|
|
30497
|
+
function readDoc(path) {
|
|
30498
|
+
if (path === null || !existsSync44(path))
|
|
30499
|
+
return null;
|
|
30500
|
+
try {
|
|
30501
|
+
return parseYaml(readFileSync40(path, "utf8"));
|
|
30502
|
+
} catch {
|
|
30503
|
+
return null;
|
|
30504
|
+
}
|
|
30505
|
+
}
|
|
30506
|
+
function strings2(value) {
|
|
30507
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
30508
|
+
}
|
|
30509
|
+
|
|
30510
|
+
// src/core/run/checks.ts
|
|
29922
30511
|
var WRITE_TIME_ONLY = ["no-reask", "budget-gate", "dod"];
|
|
29923
30512
|
async function runChecks(checks, ctx) {
|
|
29924
30513
|
const outcomes = [];
|
|
@@ -29964,12 +30553,12 @@ function checkClaimSources(ctx) {
|
|
|
29964
30553
|
continue;
|
|
29965
30554
|
}
|
|
29966
30555
|
for (const hit of hits) {
|
|
29967
|
-
if (!
|
|
30556
|
+
if (!existsSync45(hit.absolute)) {
|
|
29968
30557
|
if (isHandoffOutput)
|
|
29969
30558
|
failures.push(`${hit.path} was declared but never written`);
|
|
29970
30559
|
continue;
|
|
29971
30560
|
}
|
|
29972
|
-
const text3 =
|
|
30561
|
+
const text3 = readFileSync41(hit.absolute, "utf8");
|
|
29973
30562
|
if (isHandoffOutput || isHandoff(text3)) {
|
|
29974
30563
|
handoffs++;
|
|
29975
30564
|
const validation = validateHandoff(text3, srcCtx);
|
|
@@ -30120,12 +30709,63 @@ function checkPlan(ctx) {
|
|
|
30120
30709
|
return { id: "plan", status: "failed", detail: describePlanIssues(issues) };
|
|
30121
30710
|
}
|
|
30122
30711
|
const model2 = branchModelFor(basename10(ctx.runDir), report2.epicChain);
|
|
30712
|
+
const over = planPricesOverStage(ctx, planDir);
|
|
30123
30713
|
return {
|
|
30124
30714
|
id: "plan",
|
|
30125
30715
|
status: "passed",
|
|
30126
|
-
detail: `${report2.epicCount} epic(s), ${report2.storyCount} story(ies), ${report2.waveCount} wave(s)` + ` — ${describeBranchModel(model2)}`
|
|
30716
|
+
detail: `${report2.epicCount} epic(s), ${report2.storyCount} story(ies), ${report2.waveCount} wave(s)` + ` — ${describeBranchModel(model2)}${over === null ? "" : ` — ${over}`}`
|
|
30127
30717
|
};
|
|
30128
30718
|
}
|
|
30719
|
+
function planPricesOverStage(ctx, planDir) {
|
|
30720
|
+
const run = rawYaml(join62(ctx.runDir, "run.yml"));
|
|
30721
|
+
if (run === null || typeof run.scope !== "string" || !Array.isArray(run.phases))
|
|
30722
|
+
return null;
|
|
30723
|
+
const stage2 = run.phases.find((phase) => phase.id === BUILD_PHASE2)?.stages?.[0];
|
|
30724
|
+
if (stage2 === undefined || typeof stage2.id !== "string" || typeof stage2.budget_usd !== "number")
|
|
30725
|
+
return null;
|
|
30726
|
+
const progress = buildProgress(ctx.runDir);
|
|
30727
|
+
if (progress === null || progress.implicit)
|
|
30728
|
+
return null;
|
|
30729
|
+
const ids = new Set(progress.waves.flatMap((wave) => wave.stories.map((story) => story.id)));
|
|
30730
|
+
const prices = loadPlanPrices(planDir, ids).prices;
|
|
30731
|
+
if (prices.size === 0)
|
|
30732
|
+
return null;
|
|
30733
|
+
let spec = null;
|
|
30734
|
+
try {
|
|
30735
|
+
spec = loadStageSpec(ctx.root, run.scope, stage2.id);
|
|
30736
|
+
} catch {
|
|
30737
|
+
spec = null;
|
|
30738
|
+
}
|
|
30739
|
+
const budget = rawYaml(join62(ctx.runDir, "budget.yml"));
|
|
30740
|
+
const perAgentMax = typeof budget?.per_agent_max_usd === "number" ? budget.per_agent_max_usd : Infinity;
|
|
30741
|
+
const budgetUsd = stage2.budget_usd;
|
|
30742
|
+
const parts = {
|
|
30743
|
+
prices,
|
|
30744
|
+
storyCount: ids.size,
|
|
30745
|
+
budgetUsd,
|
|
30746
|
+
maxBudgetUsd: Math.min(budgetUsd, perAgentMax),
|
|
30747
|
+
agentCap: (share = 1) => round23(Math.min(budgetUsd * share, perAgentMax)),
|
|
30748
|
+
...spec === null ? {} : {
|
|
30749
|
+
attempts: spec.tuning.attempts,
|
|
30750
|
+
reviewerShare: spec.tuning.reviewerShare,
|
|
30751
|
+
storyCapMultiplier: spec.tuning.storyCapMultiplier,
|
|
30752
|
+
storyCapFloorUsd: spec.tuning.storyCapFloorUsd
|
|
30753
|
+
}
|
|
30754
|
+
};
|
|
30755
|
+
const runId = basename10(ctx.runDir);
|
|
30756
|
+
return planOverStageAdvisory(parts, {
|
|
30757
|
+
raiseCommand: (usd3) => stageRaiseCommand(runId, BUILD_PHASE2, stage2.id, usd3)
|
|
30758
|
+
});
|
|
30759
|
+
}
|
|
30760
|
+
function rawYaml(path) {
|
|
30761
|
+
if (!existsSync45(path))
|
|
30762
|
+
return null;
|
|
30763
|
+
try {
|
|
30764
|
+
return parseYaml(readFileSync41(path, "utf8"));
|
|
30765
|
+
} catch {
|
|
30766
|
+
return null;
|
|
30767
|
+
}
|
|
30768
|
+
}
|
|
30129
30769
|
function checkSchema(ctx) {
|
|
30130
30770
|
const pairs = [
|
|
30131
30771
|
["run.yml", validateRunFile],
|
|
@@ -30133,9 +30773,9 @@ function checkSchema(ctx) {
|
|
|
30133
30773
|
];
|
|
30134
30774
|
for (const [name, validate2] of pairs) {
|
|
30135
30775
|
const path = join62(ctx.runDir, name);
|
|
30136
|
-
if (!
|
|
30776
|
+
if (!existsSync45(path))
|
|
30137
30777
|
return { id: "schema", status: "failed", detail: `${name} is missing` };
|
|
30138
|
-
const validation = validate2(parseYaml(
|
|
30778
|
+
const validation = validate2(parseYaml(readFileSync41(path, "utf8")));
|
|
30139
30779
|
if (!validation.ok) {
|
|
30140
30780
|
const first2 = validation.issues[0];
|
|
30141
30781
|
return { id: "schema", status: "failed", detail: `${name}: ${first2?.path ?? ""} ${first2?.message ?? ""}`.trim() };
|
|
@@ -30334,7 +30974,7 @@ var MISSING_QUESTIONS = "questions.md is a declared output of this stage and was
|
|
|
30334
30974
|
function questionsCondition(runDir, phaseId, planned) {
|
|
30335
30975
|
const path = join63(runDir, phaseId, "questions.md");
|
|
30336
30976
|
if (declaresQuestions(planned)) {
|
|
30337
|
-
if (!
|
|
30977
|
+
if (!existsSync46(path)) {
|
|
30338
30978
|
return { id: "questions", ok: false, detail: MISSING_QUESTIONS };
|
|
30339
30979
|
}
|
|
30340
30980
|
const unreadable = unreadableHeadings(path);
|
|
@@ -30357,17 +30997,17 @@ function declaresQuestions(planned) {
|
|
|
30357
30997
|
return planned.outputs.some((path) => path.endsWith("questions.md"));
|
|
30358
30998
|
}
|
|
30359
30999
|
function unreadableHeadings(path) {
|
|
30360
|
-
if (!
|
|
31000
|
+
if (!existsSync46(path))
|
|
30361
31001
|
return [];
|
|
30362
31002
|
try {
|
|
30363
|
-
return unreadableQuestionHeadings(
|
|
31003
|
+
return unreadableQuestionHeadings(readFileSync42(path, "utf8"));
|
|
30364
31004
|
} catch {
|
|
30365
31005
|
return [];
|
|
30366
31006
|
}
|
|
30367
31007
|
}
|
|
30368
31008
|
function parsedBlocks(path) {
|
|
30369
31009
|
try {
|
|
30370
|
-
return parseQuestions(
|
|
31010
|
+
return parseQuestions(readFileSync42(path, "utf8")).blocks.length;
|
|
30371
31011
|
} catch {
|
|
30372
31012
|
return 0;
|
|
30373
31013
|
}
|
|
@@ -30376,7 +31016,7 @@ function budgetCondition(input) {
|
|
|
30376
31016
|
if (isHostTokens(input.budget, input.phaseId)) {
|
|
30377
31017
|
return { id: "budget", ok: true, detail: "n/a (host-tokens economy)" };
|
|
30378
31018
|
}
|
|
30379
|
-
const spent =
|
|
31019
|
+
const spent = round24(input.stage.tasks.reduce((sum, task2) => sum + (task2.cost_usd ?? 0), 0));
|
|
30380
31020
|
const unmetered = input.stage.tasks.filter((task2) => task2.cost_usd === null).length;
|
|
30381
31021
|
const phase = input.budget.phases.find((entry) => entry.id === input.phaseId);
|
|
30382
31022
|
const phaseSpent = phase === undefined ? 0 : phase.spent_usd;
|
|
@@ -30446,10 +31086,10 @@ async function boundaryCondition(input) {
|
|
|
30446
31086
|
return { id: "boundary", ...verdict };
|
|
30447
31087
|
}
|
|
30448
31088
|
function openQuestions(path) {
|
|
30449
|
-
if (!
|
|
31089
|
+
if (!existsSync46(path))
|
|
30450
31090
|
return { blocking: [], advisory: [] };
|
|
30451
31091
|
try {
|
|
30452
|
-
const open = openBlocks(parseQuestions(
|
|
31092
|
+
const open = openBlocks(parseQuestions(readFileSync42(path, "utf8")).blocks);
|
|
30453
31093
|
return {
|
|
30454
31094
|
blocking: open.filter((block2) => !isAdvisory(block2)).map((block2) => block2.id),
|
|
30455
31095
|
advisory: open.filter((block2) => isAdvisory(block2)).map((block2) => block2.id)
|
|
@@ -30458,7 +31098,7 @@ function openQuestions(path) {
|
|
|
30458
31098
|
return { blocking: ["(questions.md does not parse)"], advisory: [] };
|
|
30459
31099
|
}
|
|
30460
31100
|
}
|
|
30461
|
-
function
|
|
31101
|
+
function round24(n) {
|
|
30462
31102
|
return Math.round(n * 100) / 100;
|
|
30463
31103
|
}
|
|
30464
31104
|
|
|
@@ -30527,7 +31167,7 @@ function describeGateSignature(sig) {
|
|
|
30527
31167
|
import { basename as basename11, join as join65 } from "node:path";
|
|
30528
31168
|
|
|
30529
31169
|
// src/core/facilitator/skipIf.ts
|
|
30530
|
-
import { existsSync as
|
|
31170
|
+
import { existsSync as existsSync47, readdirSync as readdirSync21, readFileSync as readFileSync43 } from "node:fs";
|
|
30531
31171
|
import { join as join64 } from "node:path";
|
|
30532
31172
|
var SKIP_IF_RE = /^(stories|repos|questions)(<=|>=|==|<|>)(\d{1,4})$/;
|
|
30533
31173
|
|
|
@@ -30564,7 +31204,7 @@ function countSkipInputs(runDir, run) {
|
|
|
30564
31204
|
}
|
|
30565
31205
|
function countStories(runDir) {
|
|
30566
31206
|
const dir = join64(runDir, "03-plan", "stories");
|
|
30567
|
-
if (!
|
|
31207
|
+
if (!existsSync47(dir))
|
|
30568
31208
|
return 0;
|
|
30569
31209
|
try {
|
|
30570
31210
|
return readdirSync21(dir).filter((name) => name.endsWith(".md")).length;
|
|
@@ -30587,10 +31227,10 @@ function phaseDirs(runDir) {
|
|
|
30587
31227
|
}
|
|
30588
31228
|
}
|
|
30589
31229
|
function blockingQuestionIds(path) {
|
|
30590
|
-
if (!
|
|
31230
|
+
if (!existsSync47(path))
|
|
30591
31231
|
return [];
|
|
30592
31232
|
try {
|
|
30593
|
-
return openBlocks(parseQuestions(
|
|
31233
|
+
return openBlocks(parseQuestions(readFileSync43(path, "utf8")).blocks).filter((block2) => !isAdvisory(block2)).map((block2) => block2.id);
|
|
30594
31234
|
} catch {
|
|
30595
31235
|
return [];
|
|
30596
31236
|
}
|
|
@@ -30702,7 +31342,7 @@ function cancelledMessage(cancelled) {
|
|
|
30702
31342
|
}
|
|
30703
31343
|
|
|
30704
31344
|
// src/core/run/operatorNote.ts
|
|
30705
|
-
import { existsSync as
|
|
31345
|
+
import { existsSync as existsSync48 } from "node:fs";
|
|
30706
31346
|
import { join as join66 } from "node:path";
|
|
30707
31347
|
var EXIT_OK3 = 0;
|
|
30708
31348
|
var EXIT_USAGE2 = 1;
|
|
@@ -30719,7 +31359,7 @@ function addOperatorNote(options) {
|
|
|
30719
31359
|
]
|
|
30720
31360
|
};
|
|
30721
31361
|
}
|
|
30722
|
-
if (options.soleArgument === true &&
|
|
31362
|
+
if (options.soleArgument === true && existsSync48(join66(options.root, PROJECT_WORK_DIR, text3))) {
|
|
30723
31363
|
return {
|
|
30724
31364
|
code: EXIT_USAGE2,
|
|
30725
31365
|
lines: [
|
|
@@ -30963,8 +31603,8 @@ function renderGates(rows, verbose = false) {
|
|
|
30963
31603
|
rows.forEach((row2, i) => {
|
|
30964
31604
|
const head2 = ` ${(where[i] ?? "").padEnd(width)} ${row2.policy.padEnd(5)} `;
|
|
30965
31605
|
const tail = tails[i] ?? "";
|
|
30966
|
-
const
|
|
30967
|
-
lines.push(tail === "" ? head2 +
|
|
31606
|
+
const describe6 = described[i] ?? "";
|
|
31607
|
+
lines.push(tail === "" ? head2 + describe6 : `${head2}${describe6.padEnd(tailWidth)} ${tail}`);
|
|
30968
31608
|
if (verbose)
|
|
30969
31609
|
lines.push(...gateDetail(row2));
|
|
30970
31610
|
});
|
|
@@ -31079,7 +31719,7 @@ function width(rows, of) {
|
|
|
31079
31719
|
}
|
|
31080
31720
|
|
|
31081
31721
|
// src/core/run/decisionCards.ts
|
|
31082
|
-
import { existsSync as
|
|
31722
|
+
import { existsSync as existsSync49, readFileSync as readFileSync44 } from "node:fs";
|
|
31083
31723
|
import { join as join67 } from "node:path";
|
|
31084
31724
|
function questionsCard(ctx) {
|
|
31085
31725
|
const blocks = openQuestionBlocks(join67(ctx.runDir, ctx.phaseId, "questions.md"));
|
|
@@ -31194,10 +31834,10 @@ function toRecommendation2(entry) {
|
|
|
31194
31834
|
return { option: entry.option, why: entry.why, src: entry.src };
|
|
31195
31835
|
}
|
|
31196
31836
|
function openQuestionBlocks(path) {
|
|
31197
|
-
if (!
|
|
31837
|
+
if (!existsSync49(path))
|
|
31198
31838
|
return [];
|
|
31199
31839
|
try {
|
|
31200
|
-
return openBlocks(parseQuestions(
|
|
31840
|
+
return openBlocks(parseQuestions(readFileSync44(path, "utf8")).blocks);
|
|
31201
31841
|
} catch {
|
|
31202
31842
|
return [];
|
|
31203
31843
|
}
|
|
@@ -31225,17 +31865,17 @@ function readRecommendations(ctx) {
|
|
|
31225
31865
|
return out;
|
|
31226
31866
|
}
|
|
31227
31867
|
function readOrNull(path) {
|
|
31228
|
-
if (!
|
|
31868
|
+
if (!existsSync49(path))
|
|
31229
31869
|
return null;
|
|
31230
31870
|
try {
|
|
31231
|
-
return
|
|
31871
|
+
return readFileSync44(path, "utf8");
|
|
31232
31872
|
} catch {
|
|
31233
31873
|
return null;
|
|
31234
31874
|
}
|
|
31235
31875
|
}
|
|
31236
31876
|
|
|
31237
31877
|
// src/core/run/questionCards.ts
|
|
31238
|
-
import { existsSync as
|
|
31878
|
+
import { existsSync as existsSync50, readFileSync as readFileSync45 } from "node:fs";
|
|
31239
31879
|
import { join as join68 } from "node:path";
|
|
31240
31880
|
var QUESTION_PHASES = PHASE_IDS;
|
|
31241
31881
|
var NEEDS_OPTIONS = "NEEDS OPTIONS";
|
|
@@ -31243,11 +31883,11 @@ function collectQuestionCards(runDir, runId) {
|
|
|
31243
31883
|
const cards = [];
|
|
31244
31884
|
for (const phase of QUESTION_PHASES) {
|
|
31245
31885
|
const path = join68(runDir, phase, "questions.md");
|
|
31246
|
-
if (!
|
|
31886
|
+
if (!existsSync50(path))
|
|
31247
31887
|
continue;
|
|
31248
31888
|
let text3;
|
|
31249
31889
|
try {
|
|
31250
|
-
text3 =
|
|
31890
|
+
text3 = readFileSync45(path, "utf8");
|
|
31251
31891
|
} catch {
|
|
31252
31892
|
continue;
|
|
31253
31893
|
}
|
|
@@ -31261,7 +31901,7 @@ function collectQuestionCards(runDir, runId) {
|
|
|
31261
31901
|
function countQuestionFiles(runDir) {
|
|
31262
31902
|
let found = 0;
|
|
31263
31903
|
for (const phase of QUESTION_PHASES) {
|
|
31264
|
-
if (
|
|
31904
|
+
if (existsSync50(join68(runDir, phase, "questions.md")))
|
|
31265
31905
|
found++;
|
|
31266
31906
|
}
|
|
31267
31907
|
return found;
|
|
@@ -31340,14 +31980,14 @@ function cardBlock(card, n, total) {
|
|
|
31340
31980
|
}
|
|
31341
31981
|
|
|
31342
31982
|
// src/core/facilitator/autoAnswer.ts
|
|
31343
|
-
import { existsSync as
|
|
31983
|
+
import { existsSync as existsSync54, readFileSync as readFileSync49 } from "node:fs";
|
|
31344
31984
|
import { join as join71 } from "node:path";
|
|
31345
31985
|
|
|
31346
31986
|
// src/core/answers/captureAnswers.ts
|
|
31347
|
-
import { existsSync as
|
|
31987
|
+
import { existsSync as existsSync53, readFileSync as readFileSync48, writeFileSync as writeFileSync11 } from "node:fs";
|
|
31348
31988
|
|
|
31349
31989
|
// src/core/answers/stampSuperseded.ts
|
|
31350
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
31990
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync51, readFileSync as readFileSync46, statSync as statSync17 } from "node:fs";
|
|
31351
31991
|
import { isAbsolute as isAbsolute6, join as join69, relative as relative11 } from "node:path";
|
|
31352
31992
|
var STAMP_MARKER = "tldrx:superseded";
|
|
31353
31993
|
var AFFECTS_KEY = "affects";
|
|
@@ -31387,7 +32027,7 @@ function stampSuperseded(runDir, questionsPath, block2, fact, at) {
|
|
|
31387
32027
|
const questionsRel = runRelative(runDir, questionsPath) ?? "questions.md";
|
|
31388
32028
|
const stamped = [];
|
|
31389
32029
|
for (const doc of affectedDocs(runDir, questionsPath, block2)) {
|
|
31390
|
-
const text3 =
|
|
32030
|
+
const text3 = readFileSync46(doc.abs, "utf8");
|
|
31391
32031
|
if (isStamped(text3, fact))
|
|
31392
32032
|
continue;
|
|
31393
32033
|
const lead = text3 === "" ? "" : text3.endsWith(`
|
|
@@ -31412,7 +32052,7 @@ function resolveInRun(runDir, raw) {
|
|
|
31412
32052
|
const rel = runRelative(runDir, candidate);
|
|
31413
32053
|
if (rel === null)
|
|
31414
32054
|
continue;
|
|
31415
|
-
if (!
|
|
32055
|
+
if (!existsSync51(candidate) || !statSync17(candidate).isFile())
|
|
31416
32056
|
continue;
|
|
31417
32057
|
return rel;
|
|
31418
32058
|
}
|
|
@@ -31461,18 +32101,18 @@ function reposFromAffects(affects, repoNames) {
|
|
|
31461
32101
|
}
|
|
31462
32102
|
|
|
31463
32103
|
// src/core/answers/raiseConflict.ts
|
|
31464
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
32104
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync52, readFileSync as readFileSync47 } from "node:fs";
|
|
31465
32105
|
import { join as join70 } from "node:path";
|
|
31466
32106
|
var RAISED_BY = "tldrx";
|
|
31467
32107
|
function nextQuestionId(runDir) {
|
|
31468
32108
|
let highest = 0;
|
|
31469
32109
|
for (const phase of QUESTION_PHASES) {
|
|
31470
32110
|
const path = join70(runDir, phase, "questions.md");
|
|
31471
|
-
if (!
|
|
32111
|
+
if (!existsSync52(path))
|
|
31472
32112
|
continue;
|
|
31473
32113
|
let blocks;
|
|
31474
32114
|
try {
|
|
31475
|
-
blocks = parseQuestions(
|
|
32115
|
+
blocks = parseQuestions(readFileSync47(path, "utf8")).blocks;
|
|
31476
32116
|
} catch {
|
|
31477
32117
|
continue;
|
|
31478
32118
|
}
|
|
@@ -31552,9 +32192,9 @@ function factWasTruncated(title, answer) {
|
|
|
31552
32192
|
return `${title} — ${answer}`.length > MAX_FACT_CHARS;
|
|
31553
32193
|
}
|
|
31554
32194
|
function captureAnswers(questionsPath, ctx) {
|
|
31555
|
-
if (!
|
|
32195
|
+
if (!existsSync53(questionsPath))
|
|
31556
32196
|
return [];
|
|
31557
|
-
let doc = parseQuestions(
|
|
32197
|
+
let doc = parseQuestions(readFileSync48(questionsPath, "utf8"));
|
|
31558
32198
|
const answered = detectAnswered(doc.blocks);
|
|
31559
32199
|
if (answered.length === 0)
|
|
31560
32200
|
return [];
|
|
@@ -31684,12 +32324,12 @@ function markSuperseded(log, ctx, questionsPath, block2, fact) {
|
|
|
31684
32324
|
}
|
|
31685
32325
|
}
|
|
31686
32326
|
function supersedeAnswer(questionsPath, qid, text3, ctx) {
|
|
31687
|
-
if (!
|
|
32327
|
+
if (!existsSync53(questionsPath))
|
|
31688
32328
|
throw new AnswerError(`no questions file at ${questionsPath}`);
|
|
31689
32329
|
const answer = text3.trim();
|
|
31690
32330
|
if (answer === "")
|
|
31691
32331
|
throw new AnswerError("a superseding answer cannot be empty");
|
|
31692
|
-
const doc = parseQuestions(
|
|
32332
|
+
const doc = parseQuestions(readFileSync48(questionsPath, "utf8"));
|
|
31693
32333
|
const block2 = doc.blocks.find((b) => b.id === qid);
|
|
31694
32334
|
if (block2 === undefined)
|
|
31695
32335
|
throw new AnswerError(`${qid} is not in ${questionsPath}`);
|
|
@@ -31767,11 +32407,11 @@ function supersedeAnswer(questionsPath, qid, text3, ctx) {
|
|
|
31767
32407
|
return conflict === undefined ? result2 : { ...result2, conflict };
|
|
31768
32408
|
}
|
|
31769
32409
|
function writeAnswerSlot(questionsPath, qid, text3) {
|
|
31770
|
-
if (!
|
|
32410
|
+
if (!existsSync53(questionsPath))
|
|
31771
32411
|
throw new AnswerError(`no questions file at ${questionsPath}`);
|
|
31772
32412
|
if (text3.trim() === "")
|
|
31773
32413
|
throw new AnswerError("an answer cannot be empty");
|
|
31774
|
-
const doc = parseQuestions(
|
|
32414
|
+
const doc = parseQuestions(readFileSync48(questionsPath, "utf8"));
|
|
31775
32415
|
const block2 = doc.blocks.find((b) => b.id === qid);
|
|
31776
32416
|
if (block2 === undefined)
|
|
31777
32417
|
throw new AnswerError(`${qid} is not in ${questionsPath}`);
|
|
@@ -31793,11 +32433,11 @@ function answerRecommended(ctx) {
|
|
|
31793
32433
|
for (const phase of QUESTION_PHASES) {
|
|
31794
32434
|
const rel = `${phase}/questions.md`;
|
|
31795
32435
|
const path = join71(ctx.runDir, phase, "questions.md");
|
|
31796
|
-
if (!
|
|
32436
|
+
if (!existsSync54(path))
|
|
31797
32437
|
continue;
|
|
31798
32438
|
const ids = blockingIds(path);
|
|
31799
32439
|
for (const id of ids) {
|
|
31800
|
-
const block2 = openBlocks(parseQuestions(
|
|
32440
|
+
const block2 = openBlocks(parseQuestions(readFileSync49(path, "utf8")).blocks).find((b) => b.id === id);
|
|
31801
32441
|
if (block2 === undefined)
|
|
31802
32442
|
continue;
|
|
31803
32443
|
if (pinnedToPerson(block2)) {
|
|
@@ -31839,23 +32479,23 @@ function answerRecommended(ctx) {
|
|
|
31839
32479
|
}
|
|
31840
32480
|
function blockingIds(path) {
|
|
31841
32481
|
try {
|
|
31842
|
-
return openBlocks(parseQuestions(
|
|
32482
|
+
return openBlocks(parseQuestions(readFileSync49(path, "utf8")).blocks).filter((block2) => !isAdvisory(block2)).map((block2) => block2.id);
|
|
31843
32483
|
} catch {
|
|
31844
32484
|
return [];
|
|
31845
32485
|
}
|
|
31846
32486
|
}
|
|
31847
32487
|
|
|
31848
32488
|
// src/core/notify/declaration.ts
|
|
31849
|
-
import { existsSync as
|
|
32489
|
+
import { existsSync as existsSync55, readFileSync as readFileSync50 } from "node:fs";
|
|
31850
32490
|
import { join as join72 } from "node:path";
|
|
31851
32491
|
var NOTIFY_TIMEOUT_MS = 30000;
|
|
31852
32492
|
function readNotifyDeclaration(root2) {
|
|
31853
32493
|
const path = join72(root2, PROJECT_WORKSPACE_FILE);
|
|
31854
|
-
if (!
|
|
32494
|
+
if (!existsSync55(path))
|
|
31855
32495
|
return null;
|
|
31856
32496
|
let parsed;
|
|
31857
32497
|
try {
|
|
31858
|
-
parsed = parseYaml(
|
|
32498
|
+
parsed = parseYaml(readFileSync50(path, "utf8"));
|
|
31859
32499
|
} catch {
|
|
31860
32500
|
return null;
|
|
31861
32501
|
}
|
|
@@ -32259,7 +32899,7 @@ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "node
|
|
|
32259
32899
|
import { dirname as dirname12, join as join74 } from "node:path";
|
|
32260
32900
|
|
|
32261
32901
|
// src/core/run/closeRun.ts
|
|
32262
|
-
import { existsSync as
|
|
32902
|
+
import { existsSync as existsSync56, readFileSync as readFileSync51 } from "node:fs";
|
|
32263
32903
|
import { join as join73, relative as relative12, resolve as resolve10, sep as sep9 } from "node:path";
|
|
32264
32904
|
|
|
32265
32905
|
// src/core/facts/decidedTally.ts
|
|
@@ -32304,11 +32944,11 @@ function collectOpenQuestions(runDir) {
|
|
|
32304
32944
|
for (const phase of QUESTION_PHASES) {
|
|
32305
32945
|
const rel = `${phase}/questions.md`;
|
|
32306
32946
|
const abs = join73(runDir, phase, "questions.md");
|
|
32307
|
-
if (!
|
|
32947
|
+
if (!existsSync56(abs))
|
|
32308
32948
|
continue;
|
|
32309
32949
|
let text3;
|
|
32310
32950
|
try {
|
|
32311
|
-
text3 =
|
|
32951
|
+
text3 = readFileSync51(abs, "utf8");
|
|
32312
32952
|
} catch {
|
|
32313
32953
|
continue;
|
|
32314
32954
|
}
|
|
@@ -32647,11 +33287,11 @@ function event2(ts, run, stage2, type, actor, payload) {
|
|
|
32647
33287
|
|
|
32648
33288
|
// src/core/facilitator/runNext.ts
|
|
32649
33289
|
import { rmSync as rmSync10 } from "node:fs";
|
|
32650
|
-
import { mkdirSync as mkdirSync19, readFileSync as
|
|
33290
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync75, writeFileSync as writeFileSync19 } from "node:fs";
|
|
32651
33291
|
import { join as join100, relative as relative18 } from "node:path";
|
|
32652
33292
|
|
|
32653
33293
|
// src/core/run/agentGate.ts
|
|
32654
|
-
import { existsSync as
|
|
33294
|
+
import { existsSync as existsSync57, readFileSync as readFileSync52 } from "node:fs";
|
|
32655
33295
|
var BUDGET_DECISION_EVENTS = ["budget.raised", "budget.blocked"];
|
|
32656
33296
|
async function evaluateAgentGate(input) {
|
|
32657
33297
|
const auto = await evaluateAutoGate(input);
|
|
@@ -32740,17 +33380,17 @@ function describeAgentFallthroughs(fallthroughs) {
|
|
|
32740
33380
|
return fallthroughs.map((f) => ` ${f.trigger}: ${f.detail}`);
|
|
32741
33381
|
}
|
|
32742
33382
|
function readOrNull2(path) {
|
|
32743
|
-
if (!
|
|
33383
|
+
if (!existsSync57(path))
|
|
32744
33384
|
return null;
|
|
32745
33385
|
try {
|
|
32746
|
-
return
|
|
33386
|
+
return readFileSync52(path, "utf8");
|
|
32747
33387
|
} catch {
|
|
32748
33388
|
return null;
|
|
32749
33389
|
}
|
|
32750
33390
|
}
|
|
32751
33391
|
|
|
32752
33392
|
// src/core/build/carriedRows.ts
|
|
32753
|
-
import { existsSync as
|
|
33393
|
+
import { existsSync as existsSync58, readFileSync as readFileSync53, readdirSync as readdirSync22 } from "node:fs";
|
|
32754
33394
|
import { join as join75 } from "node:path";
|
|
32755
33395
|
|
|
32756
33396
|
// src/core/build/unownedFindings.ts
|
|
@@ -32784,7 +33424,7 @@ function unownedFindings(carried, declared, repoNames) {
|
|
|
32784
33424
|
function declaredPhases(runDir) {
|
|
32785
33425
|
let doc;
|
|
32786
33426
|
try {
|
|
32787
|
-
doc = parseYaml(
|
|
33427
|
+
doc = parseYaml(readFileSync53(join75(runDir, "run.yml"), "utf8"));
|
|
32788
33428
|
} catch {
|
|
32789
33429
|
return [];
|
|
32790
33430
|
}
|
|
@@ -32810,7 +33450,7 @@ function scanStories(runDir) {
|
|
|
32810
33450
|
const seen = new Set;
|
|
32811
33451
|
for (const phase of phaseDirsOf(runDir)) {
|
|
32812
33452
|
const dir = join75(runDir, phase, STORIES_DIR);
|
|
32813
|
-
if (!
|
|
33453
|
+
if (!existsSync58(dir))
|
|
32814
33454
|
continue;
|
|
32815
33455
|
let names;
|
|
32816
33456
|
try {
|
|
@@ -32826,7 +33466,7 @@ function scanStories(runDir) {
|
|
|
32826
33466
|
const rel = `${phase}/${STORIES_DIR}/${name}`;
|
|
32827
33467
|
let text3;
|
|
32828
33468
|
try {
|
|
32829
|
-
text3 =
|
|
33469
|
+
text3 = readFileSync53(join75(dir, name), "utf8");
|
|
32830
33470
|
} catch (error) {
|
|
32831
33471
|
unreadable.push({ rel, reason: `the file could not be read — ${String(error)}` });
|
|
32832
33472
|
continue;
|
|
@@ -32889,404 +33529,6 @@ function carriedDetailLines(report2) {
|
|
|
32889
33529
|
return [...report2.rows.map(carriedCardLine), ...report2.unreadable.map(unreadableCardLine)];
|
|
32890
33530
|
}
|
|
32891
33531
|
|
|
32892
|
-
// src/core/facilitator/stageSpec.ts
|
|
32893
|
-
import { existsSync as existsSync58, readFileSync as readFileSync53 } from "node:fs";
|
|
32894
|
-
|
|
32895
|
-
// src/core/facilitator/seedInputs.ts
|
|
32896
|
-
import { readFileSync as readFileSync52, statSync as statSync17 } from "node:fs";
|
|
32897
|
-
var DEFAULT_INPUTS_MAX_BYTES = 256 * 1024;
|
|
32898
|
-
var MAX_SEED_INLINE_BYTES = 64 * 1024;
|
|
32899
|
-
var MIN_SLICE_BYTES = 2 * 1024;
|
|
32900
|
-
function inlineInputs(declared, options) {
|
|
32901
|
-
const budget = options.budgetBytes ?? DEFAULT_INPUTS_MAX_BYTES;
|
|
32902
|
-
const exempt = options.exempt ?? new Set;
|
|
32903
|
-
const inputs = [];
|
|
32904
|
-
const truncated = [];
|
|
32905
|
-
let spent = 0;
|
|
32906
|
-
let seedOverflowed = false;
|
|
32907
|
-
for (const path of declared) {
|
|
32908
|
-
const abs = resolveDeclared(path, options.ctx);
|
|
32909
|
-
if (exempt.has(path)) {
|
|
32910
|
-
inputs.push({ path, content: readOrEmpty3(abs) });
|
|
32911
|
-
continue;
|
|
32912
|
-
}
|
|
32913
|
-
const size = sizeOf4(abs);
|
|
32914
|
-
const room = budget - spent;
|
|
32915
|
-
if (size <= room) {
|
|
32916
|
-
spent += size;
|
|
32917
|
-
inputs.push({ path, content: readOrEmpty3(abs) });
|
|
32918
|
-
continue;
|
|
32919
|
-
}
|
|
32920
|
-
if (options.seed.has(path))
|
|
32921
|
-
seedOverflowed = true;
|
|
32922
|
-
if (room >= MIN_SLICE_BYTES) {
|
|
32923
|
-
spent += room;
|
|
32924
|
-
truncated.push({ path, totalBytes: size, inlinedBytes: room });
|
|
32925
|
-
inputs.push({ path, content: slice(abs, room), inlinedBytes: room, totalBytes: size });
|
|
32926
|
-
continue;
|
|
32927
|
-
}
|
|
32928
|
-
truncated.push({ path, totalBytes: size, inlinedBytes: 0 });
|
|
32929
|
-
inputs.push({ path, content: "", inlinedBytes: 0, totalBytes: size });
|
|
32930
|
-
}
|
|
32931
|
-
return {
|
|
32932
|
-
inputs,
|
|
32933
|
-
note: describe5(budget, truncated, seedOverflowed),
|
|
32934
|
-
truncated,
|
|
32935
|
-
inlinedBytes: spent,
|
|
32936
|
-
budgetBytes: budget
|
|
32937
|
-
};
|
|
32938
|
-
}
|
|
32939
|
-
function describe5(budget, truncated, seedOverflowed) {
|
|
32940
|
-
if (truncated.length === 0)
|
|
32941
|
-
return null;
|
|
32942
|
-
const listed = truncated.map((entry) => `${entry.path} (${entry.totalBytes.toLocaleString("en-US")} B` + `${entry.inlinedBytes === 0 ? ", none inlined" : `, first ${entry.inlinedBytes.toLocaleString("en-US")} B only`})`).join("; ");
|
|
32943
|
-
const fix = seedOverflowed ? "raise `inputs_max_bytes` in the stage file or split the seed (`tldrx seed triage`)" : "raise `inputs_max_bytes` in the stage file";
|
|
32944
|
-
return `truncated inputs: ${listed} — the ${budget.toLocaleString("en-US")}-byte ` + `\`inputs_max_bytes\` budget ran out. ${fix}. ` + "Work from what IS inlined and say in your handoff what you could not read.";
|
|
32945
|
-
}
|
|
32946
|
-
function describeTruncatedInputs(result2) {
|
|
32947
|
-
return result2.truncated.map((entry) => `truncated input: ${entry.path} (${entry.totalBytes.toLocaleString("en-US")} B` + `${entry.inlinedBytes === 0 ? ", none inlined" : `, ${entry.inlinedBytes.toLocaleString("en-US")} B inlined`})` + ` — raise inputs_max_bytes (now ${result2.budgetBytes.toLocaleString("en-US")}) or split the seed`);
|
|
32948
|
-
}
|
|
32949
|
-
function capInputs(declared, max = MAX_STAGE_INPUTS) {
|
|
32950
|
-
return declared.length <= max ? declared : declared.slice(0, max);
|
|
32951
|
-
}
|
|
32952
|
-
function sizeOf4(abs) {
|
|
32953
|
-
try {
|
|
32954
|
-
return statSync17(abs).size;
|
|
32955
|
-
} catch {
|
|
32956
|
-
return 0;
|
|
32957
|
-
}
|
|
32958
|
-
}
|
|
32959
|
-
function slice(abs, bytes) {
|
|
32960
|
-
try {
|
|
32961
|
-
return readFileSync52(abs).subarray(0, bytes).toString("utf8");
|
|
32962
|
-
} catch {
|
|
32963
|
-
return "";
|
|
32964
|
-
}
|
|
32965
|
-
}
|
|
32966
|
-
function readOrEmpty3(abs) {
|
|
32967
|
-
try {
|
|
32968
|
-
return readFileSync52(abs, "utf8");
|
|
32969
|
-
} catch {
|
|
32970
|
-
return "";
|
|
32971
|
-
}
|
|
32972
|
-
}
|
|
32973
|
-
|
|
32974
|
-
// src/core/budget/modelPrices.ts
|
|
32975
|
-
var CACHE_WRITE_MULTIPLIER = 1.25;
|
|
32976
|
-
var CACHE_READ_MULTIPLIER = 0.1;
|
|
32977
|
-
var BYTES_PER_TOKEN = 3.6;
|
|
32978
|
-
var MODEL_PRICES = [
|
|
32979
|
-
{ id: "opus", contextTokens: 200000, inputUsdPerMTok: 5, outputUsdPerMTok: 25 },
|
|
32980
|
-
{ id: "opus[1m]", contextTokens: 1e6, inputUsdPerMTok: 5, outputUsdPerMTok: 25 },
|
|
32981
|
-
{ id: "sonnet", contextTokens: 200000, inputUsdPerMTok: 2, outputUsdPerMTok: 10 },
|
|
32982
|
-
{ id: "sonnet[1m]", contextTokens: 1e6, inputUsdPerMTok: 2, outputUsdPerMTok: 10 },
|
|
32983
|
-
{ id: "haiku", contextTokens: 200000, inputUsdPerMTok: 1, outputUsdPerMTok: 5 }
|
|
32984
|
-
];
|
|
32985
|
-
var ONE_MILLION_MARKER = "[1m]";
|
|
32986
|
-
var ONE_MILLION_TOKENS = 1e6;
|
|
32987
|
-
function familyOf(id) {
|
|
32988
|
-
return id.endsWith(ONE_MILLION_MARKER) ? id.slice(0, -ONE_MILLION_MARKER.length) : id;
|
|
32989
|
-
}
|
|
32990
|
-
function priceFor(model2) {
|
|
32991
|
-
if (model2 === null)
|
|
32992
|
-
return null;
|
|
32993
|
-
const name = model2.toLowerCase();
|
|
32994
|
-
const oneMillion = name.includes(ONE_MILLION_MARKER);
|
|
32995
|
-
let best = null;
|
|
32996
|
-
for (const row2 of MODEL_PRICES) {
|
|
32997
|
-
const family = familyOf(row2.id);
|
|
32998
|
-
if (family !== row2.id && !oneMillion)
|
|
32999
|
-
continue;
|
|
33000
|
-
if (!name.includes(family))
|
|
33001
|
-
continue;
|
|
33002
|
-
if (best === null || row2.id.length > best.id.length)
|
|
33003
|
-
best = row2;
|
|
33004
|
-
}
|
|
33005
|
-
return best;
|
|
33006
|
-
}
|
|
33007
|
-
function knownContextTokensFor(model2) {
|
|
33008
|
-
const priced = priceFor(model2);
|
|
33009
|
-
if (priced !== null)
|
|
33010
|
-
return priced.contextTokens;
|
|
33011
|
-
if (model2 !== null && model2.toLowerCase().includes(ONE_MILLION_MARKER))
|
|
33012
|
-
return ONE_MILLION_TOKENS;
|
|
33013
|
-
return null;
|
|
33014
|
-
}
|
|
33015
|
-
function estimateTokensFromBytes(bytes) {
|
|
33016
|
-
return Math.round(bytes / BYTES_PER_TOKEN);
|
|
33017
|
-
}
|
|
33018
|
-
|
|
33019
|
-
// src/core/facilitator/contextLedger.ts
|
|
33020
|
-
var DEFAULT_PROMPT_MAX_BYTES = 400 * 1024;
|
|
33021
|
-
var CONTEXT_WARN_PCT = 80;
|
|
33022
|
-
function buildLedger(input) {
|
|
33023
|
-
const rows = [];
|
|
33024
|
-
let stage2 = 0;
|
|
33025
|
-
let inputs = 0;
|
|
33026
|
-
let expertBodies = 0;
|
|
33027
|
-
let expertKnowledge = 0;
|
|
33028
|
-
let dispatchNotes = 0;
|
|
33029
|
-
let projectSkills = 0;
|
|
33030
|
-
let previousAttempt = 0;
|
|
33031
|
-
for (const part of input.parts) {
|
|
33032
|
-
const bytes = byteLength(part.text);
|
|
33033
|
-
switch (part.kind) {
|
|
33034
|
-
case "preamble":
|
|
33035
|
-
stage2 += bytes;
|
|
33036
|
-
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
33037
|
-
break;
|
|
33038
|
-
case "stage":
|
|
33039
|
-
stage2 += bytes;
|
|
33040
|
-
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
33041
|
-
break;
|
|
33042
|
-
case "expert-body":
|
|
33043
|
-
expertBodies += bytes;
|
|
33044
|
-
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
33045
|
-
break;
|
|
33046
|
-
case "expert-knowledge":
|
|
33047
|
-
expertKnowledge += bytes;
|
|
33048
|
-
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
33049
|
-
break;
|
|
33050
|
-
case "dispatch-notes":
|
|
33051
|
-
dispatchNotes += bytes;
|
|
33052
|
-
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
33053
|
-
break;
|
|
33054
|
-
case "project-skills":
|
|
33055
|
-
projectSkills += bytes;
|
|
33056
|
-
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
33057
|
-
break;
|
|
33058
|
-
case "previous-attempt":
|
|
33059
|
-
previousAttempt += bytes;
|
|
33060
|
-
rows.push({ kind: part.kind, name: part.name, bytes });
|
|
33061
|
-
break;
|
|
33062
|
-
case "inputs":
|
|
33063
|
-
inputs += bytes;
|
|
33064
|
-
for (const file of input.inputBytes) {
|
|
33065
|
-
rows.push({ kind: "inputs", name: file.path, bytes: file.bytes });
|
|
33066
|
-
}
|
|
33067
|
-
break;
|
|
33068
|
-
}
|
|
33069
|
-
}
|
|
33070
|
-
const totalBytes = stage2 + inputs + expertBodies + expertKnowledge + dispatchNotes + projectSkills + previousAttempt;
|
|
33071
|
-
const estimatedTokens = estimateTokensFromBytes(totalBytes);
|
|
33072
|
-
const contextTokens = knownContextTokensFor(input.model);
|
|
33073
|
-
const contextPct = contextTokens === null || contextTokens === 0 ? null : estimatedTokens / contextTokens * 100;
|
|
33074
|
-
return {
|
|
33075
|
-
totalBytes,
|
|
33076
|
-
rows,
|
|
33077
|
-
groups: {
|
|
33078
|
-
stage: stage2,
|
|
33079
|
-
inputs,
|
|
33080
|
-
experts: expertBodies + expertKnowledge,
|
|
33081
|
-
expertBodies,
|
|
33082
|
-
expertKnowledge,
|
|
33083
|
-
dispatchNotes,
|
|
33084
|
-
projectSkills,
|
|
33085
|
-
previousAttempt,
|
|
33086
|
-
questions: input.questionsBytes ?? 0
|
|
33087
|
-
},
|
|
33088
|
-
truncatedInputs: input.truncatedInputs,
|
|
33089
|
-
limitBytes: input.limitBytes,
|
|
33090
|
-
overLimit: totalBytes > input.limitBytes,
|
|
33091
|
-
model: input.model,
|
|
33092
|
-
estimatedTokens,
|
|
33093
|
-
contextTokens,
|
|
33094
|
-
contextPct,
|
|
33095
|
-
contextWarns: contextPct !== null && contextPct >= CONTEXT_WARN_PCT
|
|
33096
|
-
};
|
|
33097
|
-
}
|
|
33098
|
-
function questionsBytesOf(stageMd) {
|
|
33099
|
-
const lines = stageMd.split(`
|
|
33100
|
-
`);
|
|
33101
|
-
const start = lines.findIndex((line) => line.trim() === "## Questions");
|
|
33102
|
-
if (start === -1)
|
|
33103
|
-
return 0;
|
|
33104
|
-
let end = lines.length;
|
|
33105
|
-
for (let i = start + 1;i < lines.length; i++) {
|
|
33106
|
-
if ((lines[i] ?? "").startsWith("## ")) {
|
|
33107
|
-
end = i;
|
|
33108
|
-
break;
|
|
33109
|
-
}
|
|
33110
|
-
}
|
|
33111
|
-
return byteLength(lines.slice(start, end).join(`
|
|
33112
|
-
`));
|
|
33113
|
-
}
|
|
33114
|
-
function renderLedger(ledger3, maxRows = 8) {
|
|
33115
|
-
const g = ledger3.groups;
|
|
33116
|
-
const lines = [
|
|
33117
|
-
`context ${bytes(ledger3.totalBytes)} of ${bytes(ledger3.limitBytes)} ` + `(~${tokens2(ledger3.estimatedTokens)} tok${windowClause(ledger3)})`,
|
|
33118
|
-
` stage ${bytes(g.stage)}${g.questions === 0 ? "" : ` (questions ${bytes(g.questions)})`}` + ` · inputs ${bytes(g.inputs)} · experts ${bytes(g.experts)}` + ` (bodies ${bytes(g.expertBodies)}, knowledge ${bytes(g.expertKnowledge)})` + (g.dispatchNotes === 0 ? "" : ` · dispatch notes ${bytes(g.dispatchNotes)}`) + (g.projectSkills === 0 ? "" : ` · project skills ${bytes(g.projectSkills)}`) + (g.previousAttempt === 0 ? "" : ` · previous attempt ${bytes(g.previousAttempt)}`)
|
|
33119
|
-
];
|
|
33120
|
-
const biggest = [...ledger3.rows].filter((row2) => row2.kind !== "inputs" || row2.name !== "Inputs").sort((a, b) => b.bytes - a.bytes).slice(0, maxRows);
|
|
33121
|
-
for (const row2 of biggest) {
|
|
33122
|
-
lines.push(` ${label(row2)} ${bytes(row2.bytes)}`);
|
|
33123
|
-
}
|
|
33124
|
-
return lines;
|
|
33125
|
-
}
|
|
33126
|
-
function renderRefusal(ledger3, stageId) {
|
|
33127
|
-
const g = ledger3.groups;
|
|
33128
|
-
const lines = [
|
|
33129
|
-
`[tldrx] context: refusing to start stage "${stageId}" — the prompt is ` + `${bytes(ledger3.totalBytes)} and prompt_max_bytes is ${bytes(ledger3.limitBytes)}.`,
|
|
33130
|
-
...renderLedger(ledger3, 5),
|
|
33131
|
-
"Shrink it, then run again:"
|
|
33132
|
-
];
|
|
33133
|
-
if (g.inputs >= g.experts) {
|
|
33134
|
-
lines.push(` inputs are the biggest section (${bytes(g.inputs)}) — lower \`inputs_max_bytes\` ` + "in the stage file, declare fewer inputs, or split the seed with `tldrx seed triage`.");
|
|
33135
|
-
}
|
|
33136
|
-
if (g.experts > 0) {
|
|
33137
|
-
lines.push(` experts are ${bytes(g.experts)} (${bytes(g.expertKnowledge)} of it trained knowledge) — ` + "lower `knowledge_max_bytes` in the stage file, or narrow the stage's `experts:` list.");
|
|
33138
|
-
}
|
|
33139
|
-
lines.push(` or raise the ceiling deliberately: \`prompt_max_bytes\` in the stage file, ` + "or `--prompt-max-bytes <n>` for this one invocation.");
|
|
33140
|
-
return lines;
|
|
33141
|
-
}
|
|
33142
|
-
function renderContextWarning(ledger3) {
|
|
33143
|
-
if (!ledger3.contextWarns || ledger3.contextTokens === null || ledger3.contextPct === null)
|
|
33144
|
-
return [];
|
|
33145
|
-
return [
|
|
33146
|
-
`note: this prompt is ~${tokens2(ledger3.estimatedTokens)} tokens, ` + `${ledger3.contextPct.toFixed(0)}% of ${shortModel2(ledger3.model)}'s ~${tokens2(ledger3.contextTokens)}-token ` + "window [assumption: both the window and the ~3.6 bytes/token ratio are estimates, " + "see src/core/budget/modelPrices.ts]. The sub-agent still has to fit its own reading and " + "its answer in what is left."
|
|
33147
|
-
];
|
|
33148
|
-
}
|
|
33149
|
-
function windowClause(ledger3) {
|
|
33150
|
-
if (ledger3.contextTokens === null || ledger3.contextPct === null)
|
|
33151
|
-
return "";
|
|
33152
|
-
return `, ${ledger3.contextPct.toFixed(0)}% of ${shortModel2(ledger3.model)}'s ` + `~${tokens2(ledger3.contextTokens)} window`;
|
|
33153
|
-
}
|
|
33154
|
-
function label(row2) {
|
|
33155
|
-
switch (row2.kind) {
|
|
33156
|
-
case "stage":
|
|
33157
|
-
return "stage.md";
|
|
33158
|
-
case "inputs":
|
|
33159
|
-
return `input ${row2.name}`;
|
|
33160
|
-
case "expert-body":
|
|
33161
|
-
return `expert ${row2.name} body`;
|
|
33162
|
-
case "expert-knowledge":
|
|
33163
|
-
return `expert ${row2.name} knowledge`;
|
|
33164
|
-
case "preamble":
|
|
33165
|
-
return "stage preamble";
|
|
33166
|
-
case "dispatch-notes":
|
|
33167
|
-
return "dispatch notes";
|
|
33168
|
-
case "project-skills":
|
|
33169
|
-
return "project skills";
|
|
33170
|
-
case "previous-attempt":
|
|
33171
|
-
return "previous attempt";
|
|
33172
|
-
}
|
|
33173
|
-
}
|
|
33174
|
-
function shortModel2(model2) {
|
|
33175
|
-
return model2 === null || model2 === "" ? "the default model" : model2;
|
|
33176
|
-
}
|
|
33177
|
-
function bytes(count) {
|
|
33178
|
-
return count < 1024 ? `${String(count)} B` : `${(count / 1024).toFixed(1)} KB`;
|
|
33179
|
-
}
|
|
33180
|
-
function tokens2(count) {
|
|
33181
|
-
return count < 1000 ? String(count) : `${(count / 1000).toFixed(1)}k`;
|
|
33182
|
-
}
|
|
33183
|
-
|
|
33184
|
-
// src/core/facilitator/stageSpec.ts
|
|
33185
|
-
var DEFAULT_STACK_EXPERTS = true;
|
|
33186
|
-
var DEFAULT_DRY_RUN_ALLOWED = true;
|
|
33187
|
-
function loadStageSpec(root2, scope, stageId) {
|
|
33188
|
-
const preset = loadWorkflowPreset(root2, scope);
|
|
33189
|
-
const planned = preset.stages.find((s) => s.id === stageId);
|
|
33190
|
-
if (planned === undefined) {
|
|
33191
|
-
throw new PresetError(`stage '${stageId}' is not in workflow '${preset.name}' (${preset.source})`);
|
|
33192
|
-
}
|
|
33193
|
-
const over = overlay(root2, scope, stageId);
|
|
33194
|
-
return {
|
|
33195
|
-
planned,
|
|
33196
|
-
scope: preset.name,
|
|
33197
|
-
skips: preset.skips,
|
|
33198
|
-
...over,
|
|
33199
|
-
tuning: { ...over.tuning, attempts: planned.attempts },
|
|
33200
|
-
...inputSplit(root2, stageId)
|
|
33201
|
-
};
|
|
33202
|
-
}
|
|
33203
|
-
function overlay(root2, scope, stageId) {
|
|
33204
|
-
const stageDoc = readDoc(stagePath(root2, stageId));
|
|
33205
|
-
const workflowDoc = readDoc(workflowPath(root2, scope));
|
|
33206
|
-
let skipIf = null;
|
|
33207
|
-
const entries = isRecord(workflowDoc) && Array.isArray(workflowDoc.stages) ? workflowDoc.stages : [];
|
|
33208
|
-
for (const entry of entries) {
|
|
33209
|
-
if (!isRecord(entry) || entry.id !== stageId)
|
|
33210
|
-
continue;
|
|
33211
|
-
if (typeof entry.skip_if === "string" && entry.skip_if !== "")
|
|
33212
|
-
skipIf = entry.skip_if;
|
|
33213
|
-
}
|
|
33214
|
-
const questions = isRecord(stageDoc) && isRecord(stageDoc.questions) ? stageDoc.questions : null;
|
|
33215
|
-
return {
|
|
33216
|
-
stackExperts: isRecord(stageDoc) && typeof stageDoc.stack_experts === "boolean" ? stageDoc.stack_experts : DEFAULT_STACK_EXPERTS,
|
|
33217
|
-
knowledgeMaxBytes: byteKey(stageDoc, "knowledge_max_bytes") ?? byteKey(stageDoc, "expert_knowledge_bytes") ?? DEFAULT_KNOWLEDGE_MAX_BYTES,
|
|
33218
|
-
inputsMaxBytes: byteKey(stageDoc, "inputs_max_bytes") ?? DEFAULT_INPUTS_MAX_BYTES,
|
|
33219
|
-
promptMaxBytes: byteKey(stageDoc, "prompt_max_bytes") ?? DEFAULT_PROMPT_MAX_BYTES,
|
|
33220
|
-
maxReads: byteKey(stageDoc, "max_reads") ?? defaultMaxReads(stageId),
|
|
33221
|
-
parallel: parallelKey(workflowDoc, stageId) ?? byteKey(stageDoc, "parallel"),
|
|
33222
|
-
reviewer: reviewerOverride(isRecord(stageDoc) ? stageDoc.reviewer : undefined),
|
|
33223
|
-
reviewerByStakes: reviewerByStakesMap(isRecord(stageDoc) ? stageDoc.reviewer_by_stakes : undefined),
|
|
33224
|
-
tuning: readStageTuning(stageDoc),
|
|
33225
|
-
dryRunAllowed: isRecord(stageDoc) && typeof stageDoc.dry_run_allowed === "boolean" ? stageDoc.dry_run_allowed : DEFAULT_DRY_RUN_ALLOWED,
|
|
33226
|
-
skipIf,
|
|
33227
|
-
questionsMax: questions !== null && typeof questions.max === "number" ? questions.max : null
|
|
33228
|
-
};
|
|
33229
|
-
}
|
|
33230
|
-
function inputSplit(root2, stageId) {
|
|
33231
|
-
const doc = readDoc(stagePath(root2, stageId));
|
|
33232
|
-
const inputs = isRecord(doc) ? doc.inputs : undefined;
|
|
33233
|
-
const topLevelSeed = isRecord(doc) && doc.seed === true;
|
|
33234
|
-
if (isRecord(inputs)) {
|
|
33235
|
-
return {
|
|
33236
|
-
requiredInputs: strings2(inputs.required),
|
|
33237
|
-
optionalInputs: strings2(inputs.optional),
|
|
33238
|
-
seedInputs: inputs.seed === true || topLevelSeed
|
|
33239
|
-
};
|
|
33240
|
-
}
|
|
33241
|
-
return { requiredInputs: [], optionalInputs: strings2(inputs), seedInputs: topLevelSeed };
|
|
33242
|
-
}
|
|
33243
|
-
function reviewerOverride(value) {
|
|
33244
|
-
if (!isRecord(value))
|
|
33245
|
-
return null;
|
|
33246
|
-
const model2 = typeof value.model === "string" && value.model !== "" ? value.model : undefined;
|
|
33247
|
-
const effort2 = EFFORT_LEVELS.includes(String(value.effort)) ? value.effort : undefined;
|
|
33248
|
-
if (model2 === undefined && effort2 === undefined)
|
|
33249
|
-
return null;
|
|
33250
|
-
return { ...model2 === undefined ? {} : { model: model2 }, ...effort2 === undefined ? {} : { effort: effort2 } };
|
|
33251
|
-
}
|
|
33252
|
-
function reviewerByStakesMap(value) {
|
|
33253
|
-
if (!isRecord(value))
|
|
33254
|
-
return {};
|
|
33255
|
-
const out = {};
|
|
33256
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
33257
|
-
const override = reviewerOverride(entry);
|
|
33258
|
-
if (override !== null)
|
|
33259
|
-
out[key] = override;
|
|
33260
|
-
}
|
|
33261
|
-
return out;
|
|
33262
|
-
}
|
|
33263
|
-
function parallelKey(workflowDoc, stageId) {
|
|
33264
|
-
if (!isRecord(workflowDoc))
|
|
33265
|
-
return null;
|
|
33266
|
-
const entry = workflowDoc[stageId];
|
|
33267
|
-
return isRecord(entry) ? byteKey(entry, "parallel") : null;
|
|
33268
|
-
}
|
|
33269
|
-
function byteKey(doc, key) {
|
|
33270
|
-
if (!isRecord(doc))
|
|
33271
|
-
return null;
|
|
33272
|
-
const value = doc[key];
|
|
33273
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
33274
|
-
return null;
|
|
33275
|
-
return Math.trunc(value);
|
|
33276
|
-
}
|
|
33277
|
-
function readDoc(path) {
|
|
33278
|
-
if (path === null || !existsSync58(path))
|
|
33279
|
-
return null;
|
|
33280
|
-
try {
|
|
33281
|
-
return parseYaml(readFileSync53(path, "utf8"));
|
|
33282
|
-
} catch {
|
|
33283
|
-
return null;
|
|
33284
|
-
}
|
|
33285
|
-
}
|
|
33286
|
-
function strings2(value) {
|
|
33287
|
-
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
33288
|
-
}
|
|
33289
|
-
|
|
33290
33532
|
// src/core/plan/schemaContract.ts
|
|
33291
33533
|
var PLAN_CONTRACT_HEADING = "Output schemas";
|
|
33292
33534
|
var STORY_OPTIONAL_KEYS = ["stakes"];
|
|
@@ -34820,9 +35062,10 @@ async function watchExecutor(ctx) {
|
|
|
34820
35062
|
tasks: [],
|
|
34821
35063
|
costUsd: 0,
|
|
34822
35064
|
outputs: [],
|
|
34823
|
-
lines: incoherent,
|
|
35065
|
+
lines: incoherent.lines,
|
|
34824
35066
|
error: null,
|
|
34825
|
-
refused: true
|
|
35067
|
+
refused: true,
|
|
35068
|
+
signature: incoherent.signature
|
|
34826
35069
|
};
|
|
34827
35070
|
}
|
|
34828
35071
|
prompts = features.map((feature, i) => featurePrompt(ctx, feature, diffs[i] ?? []));
|
|
@@ -34853,7 +35096,7 @@ async function watchExecutor(ctx) {
|
|
|
34853
35096
|
tasks.push({
|
|
34854
35097
|
key: feature.id,
|
|
34855
35098
|
model: ctx.model ?? ctx.spec.planned.model,
|
|
34856
|
-
costUsd:
|
|
35099
|
+
costUsd: round25(outcome.costUsd),
|
|
34857
35100
|
sessionId: outcome.sessionId,
|
|
34858
35101
|
error: outcome.error,
|
|
34859
35102
|
outputs: outcome.envelope?.outputs ?? [],
|
|
@@ -34890,7 +35133,7 @@ async function watchExecutor(ctx) {
|
|
|
34890
35133
|
ok: true,
|
|
34891
35134
|
awaiting: false,
|
|
34892
35135
|
tasks,
|
|
34893
|
-
costUsd:
|
|
35136
|
+
costUsd: round25(tasks.reduce((sum, task2) => sum + task2.costUsd, 0)),
|
|
34894
35137
|
outputs: [...written.map((w) => w.path), HANDOFF_REL],
|
|
34895
35138
|
lines: [
|
|
34896
35139
|
`${ctx.phaseId}/${ctx.stageId}: ${String(written.length)} watcher card(s) — ` + `${String(verified)} verified, ${String(written.length - verified)} draft`,
|
|
@@ -34940,7 +35183,7 @@ function collectResults(ctx, features) {
|
|
|
34940
35183
|
tasks.push({
|
|
34941
35184
|
key: feature.id,
|
|
34942
35185
|
model: ctx.model ?? ctx.spec.planned.model,
|
|
34943
|
-
costUsd:
|
|
35186
|
+
costUsd: round25(result2.cost_usd ?? 0),
|
|
34944
35187
|
sessionId: result2.session_id,
|
|
34945
35188
|
error: null,
|
|
34946
35189
|
outputs: result2.outputs
|
|
@@ -35059,7 +35302,14 @@ function branchIncoherence(ctx, features, diffs) {
|
|
|
35059
35302
|
const [opening, ...rest] = block2;
|
|
35060
35303
|
out.push(i === 0 ? `${ctx.phaseId}/${ctx.stageId} refuses to start: ${opening ?? ""}` : ` and: ${opening ?? ""}`, ...rest);
|
|
35061
35304
|
}
|
|
35062
|
-
|
|
35305
|
+
const faults = [
|
|
35306
|
+
...bases.map((row2) => `base \`${row2.diff.base}\` missing in \`${row2.diff.repo}\``),
|
|
35307
|
+
...branches.map((row2) => `branch \`${row2.diff.branch}\` (${row2.owner}) missing in \`${row2.diff.repo}\``)
|
|
35308
|
+
];
|
|
35309
|
+
return {
|
|
35310
|
+
lines: out,
|
|
35311
|
+
signature: `${ctx.phaseId}/${ctx.stageId} refuses to start: ${faults.join("; ")}`
|
|
35312
|
+
};
|
|
35063
35313
|
}
|
|
35064
35314
|
function dedupeByRepo(rows) {
|
|
35065
35315
|
const seen = new Set;
|
|
@@ -35090,7 +35340,7 @@ function failed2(ctx, error, tasks) {
|
|
|
35090
35340
|
ok: false,
|
|
35091
35341
|
awaiting: false,
|
|
35092
35342
|
tasks,
|
|
35093
|
-
costUsd:
|
|
35343
|
+
costUsd: round25(tasks.reduce((sum, task2) => sum + task2.costUsd, 0)),
|
|
35094
35344
|
outputs: [],
|
|
35095
35345
|
lines: [`${ctx.phaseId}/${ctx.stageId} failed: ${error}`],
|
|
35096
35346
|
error
|
|
@@ -35101,21 +35351,21 @@ function taskKey(ctx, feature) {
|
|
|
35101
35351
|
}
|
|
35102
35352
|
function agentShare(ctx, features) {
|
|
35103
35353
|
const share = features <= 1 ? ctx.maxBudgetUsd : ctx.maxBudgetUsd / features;
|
|
35104
|
-
return
|
|
35354
|
+
return round25(Math.max(MIN_AGENT_USD, share));
|
|
35105
35355
|
}
|
|
35106
35356
|
function floorOverrun(ctx, features) {
|
|
35107
|
-
const needed =
|
|
35357
|
+
const needed = round25(features * MIN_AGENT_USD);
|
|
35108
35358
|
if (needed <= ctx.budgetUsd + 0.001)
|
|
35109
35359
|
return null;
|
|
35110
|
-
const short2 =
|
|
35360
|
+
const short2 = round25(Math.ceil((needed - ctx.budgetUsd) * 100) / 100);
|
|
35111
35361
|
return `${ctx.phaseId}/${ctx.stageId} refuses to start: ${features} feature(s) need at least ` + `$${MIN_AGENT_USD.toFixed(2)} each ($${needed.toFixed(2)} in total) and the stage ceiling is ` + `$${ctx.budgetUsd.toFixed(2)}. A spawn under the floor fails as \`error_max_budget_usd\` before it ` + `does any work, so splitting it further would just buy N failures. ` + `Run \`tldrx budget raise ${ctx.phaseId} ${short2.toFixed(2)}\` (add \`--take-from <phase>\` to move ` + "the money instead of adding it), or ship fewer features in one run.";
|
|
35112
35362
|
}
|
|
35113
|
-
function
|
|
35363
|
+
function round25(n) {
|
|
35114
35364
|
return Math.round(n * 100) / 100;
|
|
35115
35365
|
}
|
|
35116
35366
|
|
|
35117
35367
|
// src/core/facilitator/executors/build.ts
|
|
35118
|
-
import { existsSync as existsSync83, mkdirSync as mkdirSync18, readFileSync as
|
|
35368
|
+
import { existsSync as existsSync83, mkdirSync as mkdirSync18, readFileSync as readFileSync74, rmSync as rmSync9, writeFileSync as writeFileSync18 } from "node:fs";
|
|
35119
35369
|
import { isAbsolute as isAbsolute8, join as join99, relative as relative16 } from "node:path";
|
|
35120
35370
|
|
|
35121
35371
|
// src/core/build/preflight.ts
|
|
@@ -35646,10 +35896,9 @@ async function redBaseRefusal(parts, stories) {
|
|
|
35646
35896
|
}
|
|
35647
35897
|
if (failures.length === 0)
|
|
35648
35898
|
return null;
|
|
35649
|
-
const first2 = failures[0];
|
|
35650
35899
|
return {
|
|
35651
35900
|
lines: [...baseRefusalLines(failures, parts.workspace)],
|
|
35652
|
-
error:
|
|
35901
|
+
error: failures.length === 0 ? "a workspace command fails on the base tree" : failures.map((row2) => `\`${row2.command}\` exits ${String(row2.exitCode ?? "?")} on the base tree of ${row2.repo}`).join("; ")
|
|
35653
35902
|
};
|
|
35654
35903
|
}
|
|
35655
35904
|
async function runStoryDod(parts) {
|
|
@@ -36176,7 +36425,7 @@ async function measureWorktree(parts, decl, dod, repoDir, baseRef, baseSha, decl
|
|
|
36176
36425
|
import { basename as basename12 } from "node:path";
|
|
36177
36426
|
|
|
36178
36427
|
// src/core/build/epicRelease.ts
|
|
36179
|
-
import {
|
|
36428
|
+
import { existsSync as existsSync73, readFileSync as readFileSync64, writeFileSync as writeFileSync16 } from "node:fs";
|
|
36180
36429
|
import { join as join88 } from "node:path";
|
|
36181
36430
|
var ASIDE_SEPARATOR = "@";
|
|
36182
36431
|
function asideBranchOf(epicBranch, ownerRunId) {
|
|
@@ -36302,14 +36551,15 @@ function appendToHandoff(runDir, released) {
|
|
|
36302
36551
|
const path = join88(runDir, BUILD_PHASE, "handoff.md");
|
|
36303
36552
|
if (!existsSync73(path))
|
|
36304
36553
|
return;
|
|
36305
|
-
const bullets = released.map((record2) => `- ${describeRelease(record2)} — ${record2.via} at ${record2.at}: ${record2.reason} ` + `[src: absent:${BUILD_PHASE}/log]`);
|
|
36554
|
+
const bullets = released.map((record2) => asOneLine(`- ${describeRelease(record2)} — ${record2.via} at ${record2.at}: ${record2.reason} ` + `[src: absent:${BUILD_PHASE}/log]`));
|
|
36306
36555
|
try {
|
|
36307
|
-
|
|
36556
|
+
const before = readFileSync64(path, "utf8");
|
|
36557
|
+
writeFileSync16(path, ensureLineBreakNote(`${before}
|
|
36308
36558
|
## Epic branch released
|
|
36309
36559
|
|
|
36310
36560
|
${bullets.join(`
|
|
36311
36561
|
`)}
|
|
36312
|
-
|
|
36562
|
+
`), "utf8");
|
|
36313
36563
|
} catch {}
|
|
36314
36564
|
}
|
|
36315
36565
|
|
|
@@ -36365,21 +36615,27 @@ async function foreignEpicRefusal(state, parts, stories) {
|
|
|
36365
36615
|
`[tldrx] build: \`${branch}\` already exists in ${planned.story.repo} and run ${parts.runId} ` + "did not cut it — refusing to stack this run's commits onto someone else's epic.",
|
|
36366
36616
|
" either delete or rename that branch, or run `tldrx next --reuse-epic` to work on it deliberately."
|
|
36367
36617
|
];
|
|
36368
|
-
const
|
|
36618
|
+
const about = `epic branch \`${branch}\` in ${planned.story.repo} was not created by this run`;
|
|
36369
36619
|
const owners = epicClaimants(parts.root, branch, parts.runId);
|
|
36370
36620
|
if (owners.unreadable.length > 0) {
|
|
36371
|
-
return {
|
|
36621
|
+
return {
|
|
36622
|
+
lines: [...verbatim, ` · ${owners.unreadable.join(", ")} could not be read, so who owns it is unknown`],
|
|
36623
|
+
error: `${about}; who owns it is unknown — unreadable claim(s): ${owners.unreadable.join(", ")}`
|
|
36624
|
+
};
|
|
36372
36625
|
}
|
|
36373
36626
|
const open = owners.claimants.filter((store) => !isFinished(store.run.status));
|
|
36374
36627
|
if (open.length > 0) {
|
|
36375
36628
|
const named = open.map((store) => `${store.runId} (${store.run.status})`).join(", ");
|
|
36376
|
-
return {
|
|
36629
|
+
return {
|
|
36630
|
+
lines: [...verbatim, ` · claimed by run ${named}, which is still open`],
|
|
36631
|
+
error: `${about}; claimed by open run(s) ${named}`
|
|
36632
|
+
};
|
|
36377
36633
|
}
|
|
36378
36634
|
const owner = owners.claimants[0];
|
|
36379
36635
|
if (owner === undefined) {
|
|
36380
36636
|
return {
|
|
36381
36637
|
lines: [...verbatim, ` · no run under ${PROJECT_WORK_DIR}/ records cutting it, so it is nobody's leftover to move`],
|
|
36382
|
-
error
|
|
36638
|
+
error: `${about}; no run records cutting it, so it is nobody's leftover to move`
|
|
36383
36639
|
};
|
|
36384
36640
|
}
|
|
36385
36641
|
const status = owner.run.status;
|
|
@@ -36403,7 +36659,7 @@ async function foreignEpicRefusal(state, parts, stories) {
|
|
|
36403
36659
|
`[tldrx] build: \`${branch}\` already exists in ${planned.story.repo}, left by run ${owner.runId} ` + `(${status}), and could not be moved aside — ${why}.`,
|
|
36404
36660
|
verbatim[1] ?? ""
|
|
36405
36661
|
],
|
|
36406
|
-
error
|
|
36662
|
+
error: `${about}; left by run ${owner.runId} (${status}) and could not be moved aside — ${why}`
|
|
36407
36663
|
};
|
|
36408
36664
|
}
|
|
36409
36665
|
state.released.push({ record: record2, owner: owner.runId, ownerStatus: status });
|
|
@@ -36482,7 +36738,7 @@ async function dirtyRepoRefusal(parts, stories) {
|
|
|
36482
36738
|
` git -C ${shellQuote2(dir)} stash pop`,
|
|
36483
36739
|
...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."]
|
|
36484
36740
|
],
|
|
36485
|
-
error: `repo \`${name}\` has uncommitted changes a pending story declares`
|
|
36741
|
+
error: `repo \`${name}\` has uncommitted changes a pending story declares: ` + verdict2.overlapping.map((row2) => `${row2.entry.path} (${row2.why})`).join("; ")
|
|
36486
36742
|
});
|
|
36487
36743
|
}
|
|
36488
36744
|
aside.push({ repo: name, repoDir: dir, paths: verdict2.foreign.map((entry) => entry.path) });
|
|
@@ -36509,92 +36765,11 @@ function epicRows(state, parts, outcomes) {
|
|
|
36509
36765
|
}
|
|
36510
36766
|
|
|
36511
36767
|
// src/core/build/reviewBundle.ts
|
|
36512
|
-
import { existsSync as existsSync75, readFileSync as
|
|
36768
|
+
import { existsSync as existsSync75, readFileSync as readFileSync66, renameSync as renameSync3, rmSync as rmSync8 } from "node:fs";
|
|
36513
36769
|
import { join as join90, relative as relative15 } from "node:path";
|
|
36514
36770
|
|
|
36515
|
-
// src/core/build/caps.ts
|
|
36516
|
-
var MAX_ATTEMPTS2 = STAGE_TUNING_DEFAULTS.attempts;
|
|
36517
|
-
var REVIEWER_SHARE2 = STAGE_TUNING_DEFAULTS.reviewerShare;
|
|
36518
|
-
var REVIEWER_FLOOR_USD2 = 1;
|
|
36519
|
-
var STORY_CAP_MULTIPLIER2 = STAGE_TUNING_DEFAULTS.storyCapMultiplier;
|
|
36520
|
-
var STORY_CAP_FLOOR_USD2 = STAGE_TUNING_DEFAULTS.storyCapFloorUsd;
|
|
36521
|
-
function developerAttemptDivisor2(attempt, attempts = MAX_ATTEMPTS2) {
|
|
36522
|
-
return attempt <= 1 ? 1 : attempts;
|
|
36523
|
-
}
|
|
36524
|
-
var DEFAULT_PARALLEL = 1;
|
|
36525
|
-
function clampParallel(requested) {
|
|
36526
|
-
if (requested === undefined || !Number.isFinite(requested))
|
|
36527
|
-
return DEFAULT_PARALLEL;
|
|
36528
|
-
return Math.max(1, Math.min(MAX_STORIES_PER_WAVE, Math.trunc(requested)));
|
|
36529
|
-
}
|
|
36530
|
-
function attemptsOf(parts) {
|
|
36531
|
-
return parts.attempts ?? MAX_ATTEMPTS2;
|
|
36532
|
-
}
|
|
36533
|
-
function storyCapMultiplierOf(parts) {
|
|
36534
|
-
return parts.storyCapMultiplier ?? STORY_CAP_MULTIPLIER2;
|
|
36535
|
-
}
|
|
36536
|
-
function storyCapFloorOf(parts) {
|
|
36537
|
-
return parts.storyCapFloorUsd ?? STORY_CAP_FLOOR_USD2;
|
|
36538
|
-
}
|
|
36539
|
-
function reviewerShareOf(parts) {
|
|
36540
|
-
return parts.reviewerShare ?? REVIEWER_SHARE2;
|
|
36541
|
-
}
|
|
36542
|
-
function round25(n) {
|
|
36543
|
-
return Math.round(n * 100) / 100;
|
|
36544
|
-
}
|
|
36545
|
-
function developerCap(parts, storyId, attempt = 1) {
|
|
36546
|
-
const price = priceOf(parts, storyId);
|
|
36547
|
-
if (price === null)
|
|
36548
|
-
return parts.agentCap(1 / worstCaseShares(parts));
|
|
36549
|
-
return parts.agentCap(shareOf(parts, storyCeilingUsd(parts, price) / developerAttemptDivisor2(attempt, attemptsOf(parts))));
|
|
36550
|
-
}
|
|
36551
|
-
function storyCeilingUsd(parts, price) {
|
|
36552
|
-
const floor = parts.budgetUsd > 0 ? Math.min(storyCapFloorOf(parts), parts.budgetUsd) : storyCapFloorOf(parts);
|
|
36553
|
-
return Math.max(price * storyCapMultiplierOf(parts), floor);
|
|
36554
|
-
}
|
|
36555
|
-
function reviewerCap(parts, spentUsd, storyId) {
|
|
36556
|
-
const price = priceOf(parts, storyId);
|
|
36557
|
-
const share = reviewerShareOf(parts);
|
|
36558
|
-
const derived = price === null ? parts.agentCap(share / worstCaseShares(parts)) : parts.agentCap(shareOf(parts, price * share / (attemptsOf(parts) * (1 + share))));
|
|
36559
|
-
const floor = Math.min(REVIEWER_FLOOR_USD2, Math.max(parts.budgetUsd - spentUsd, 0));
|
|
36560
|
-
return round25(Math.min(Math.max(derived, floor), parts.maxBudgetUsd));
|
|
36561
|
-
}
|
|
36562
|
-
function stageRemainderUsd(parts, spentUsd) {
|
|
36563
|
-
if (!(parts.budgetUsd > 0))
|
|
36564
|
-
return null;
|
|
36565
|
-
return Math.max(round25(parts.budgetUsd - spentUsd), 0);
|
|
36566
|
-
}
|
|
36567
|
-
function reviewerUnderfunded(parts, spentUsd) {
|
|
36568
|
-
const left = stageRemainderUsd(parts, spentUsd);
|
|
36569
|
-
return left !== null && left < REVIEWER_FLOOR_USD2;
|
|
36570
|
-
}
|
|
36571
|
-
function priceOf(parts, storyId) {
|
|
36572
|
-
if (storyId === undefined)
|
|
36573
|
-
return null;
|
|
36574
|
-
const price = parts.prices.get(storyId);
|
|
36575
|
-
if (price === undefined || !Number.isFinite(price) || price <= 0)
|
|
36576
|
-
return null;
|
|
36577
|
-
return price * priceScale(parts);
|
|
36578
|
-
}
|
|
36579
|
-
function priceScale(parts) {
|
|
36580
|
-
if (parts.budgetUsd <= 0)
|
|
36581
|
-
return 1;
|
|
36582
|
-
let sum = 0;
|
|
36583
|
-
for (const price of parts.prices.values()) {
|
|
36584
|
-
if (Number.isFinite(price) && price > 0)
|
|
36585
|
-
sum += price;
|
|
36586
|
-
}
|
|
36587
|
-
return sum <= parts.budgetUsd ? 1 : parts.budgetUsd / sum;
|
|
36588
|
-
}
|
|
36589
|
-
function shareOf(parts, usd2) {
|
|
36590
|
-
return parts.budgetUsd <= 0 ? 1 : usd2 / parts.budgetUsd;
|
|
36591
|
-
}
|
|
36592
|
-
function worstCaseShares(parts) {
|
|
36593
|
-
return Math.max(parts.storyCount, 1) * attemptsOf(parts) * (1 + reviewerShareOf(parts));
|
|
36594
|
-
}
|
|
36595
|
-
|
|
36596
36771
|
// src/core/build/reviewLedger.ts
|
|
36597
|
-
import { existsSync as existsSync74, readFileSync as
|
|
36772
|
+
import { existsSync as existsSync74, readFileSync as readFileSync65 } from "node:fs";
|
|
36598
36773
|
import { join as join89 } from "node:path";
|
|
36599
36774
|
function readReviewLedger(runDir, storyId) {
|
|
36600
36775
|
const path = join89(runDir, "events.jsonl");
|
|
@@ -36610,6 +36785,7 @@ function readReviewLedger(runDir, storyId) {
|
|
|
36610
36785
|
blockedWithNothingRun: false,
|
|
36611
36786
|
reopened: null,
|
|
36612
36787
|
asIs: null,
|
|
36788
|
+
lastMerge: null,
|
|
36613
36789
|
fixRound: null,
|
|
36614
36790
|
formatRetries: 0,
|
|
36615
36791
|
formatRefusal: null,
|
|
@@ -36631,12 +36807,13 @@ function readReviewLedger(runDir, storyId) {
|
|
|
36631
36807
|
let lastDodOutputPath = null;
|
|
36632
36808
|
let reopened = null;
|
|
36633
36809
|
let asIs = null;
|
|
36810
|
+
let lastMerge = null;
|
|
36634
36811
|
let fixRound = null;
|
|
36635
36812
|
let formatRetries = 0;
|
|
36636
36813
|
let formatRefusal = null;
|
|
36637
36814
|
let spawned = null;
|
|
36638
36815
|
let reviewer = null;
|
|
36639
|
-
for (const line of
|
|
36816
|
+
for (const line of readFileSync65(path, "utf8").split(`
|
|
36640
36817
|
`)) {
|
|
36641
36818
|
if (line.trim() === "")
|
|
36642
36819
|
continue;
|
|
@@ -36698,6 +36875,13 @@ function readReviewLedger(runDir, storyId) {
|
|
|
36698
36875
|
commit2 = payload.commit;
|
|
36699
36876
|
if (typeof payload.epic_base === "string" && payload.epic_base !== "")
|
|
36700
36877
|
epicBase = payload.epic_base;
|
|
36878
|
+
if (typeof payload.commit === "string" && payload.commit !== "" && typeof payload.epic_base === "string" && payload.epic_base !== "") {
|
|
36879
|
+
lastMerge = {
|
|
36880
|
+
commit: payload.commit,
|
|
36881
|
+
epicBase: payload.epic_base,
|
|
36882
|
+
verdict: typeof payload.verdict === "string" && payload.verdict !== "" ? payload.verdict : "n-a"
|
|
36883
|
+
};
|
|
36884
|
+
}
|
|
36701
36885
|
if (payload.status === "done")
|
|
36702
36886
|
fixRound = null;
|
|
36703
36887
|
asIs = null;
|
|
@@ -36761,6 +36945,7 @@ function readReviewLedger(runDir, storyId) {
|
|
|
36761
36945
|
blockedWithNothingRun,
|
|
36762
36946
|
reopened,
|
|
36763
36947
|
asIs,
|
|
36948
|
+
lastMerge,
|
|
36764
36949
|
fixRound,
|
|
36765
36950
|
formatRetries,
|
|
36766
36951
|
formatRefusal,
|
|
@@ -36864,7 +37049,7 @@ function reviewWorkFromBundle(runDir, key) {
|
|
|
36864
37049
|
return null;
|
|
36865
37050
|
let doc;
|
|
36866
37051
|
try {
|
|
36867
|
-
doc = JSON.parse(
|
|
37052
|
+
doc = JSON.parse(readFileSync66(path, "utf8"));
|
|
36868
37053
|
} catch {
|
|
36869
37054
|
return null;
|
|
36870
37055
|
}
|
|
@@ -36919,11 +37104,11 @@ function resumableReview(runDir, storyId, status, fresh) {
|
|
|
36919
37104
|
}
|
|
36920
37105
|
|
|
36921
37106
|
// src/core/retro/findings.ts
|
|
36922
|
-
import { existsSync as existsSync81, readdirSync as readdirSync26, readFileSync as
|
|
37107
|
+
import { existsSync as existsSync81, readdirSync as readdirSync26, readFileSync as readFileSync72 } from "node:fs";
|
|
36923
37108
|
import { join as join97 } from "node:path";
|
|
36924
37109
|
|
|
36925
37110
|
// src/core/replay/loadRun.ts
|
|
36926
|
-
import { existsSync as existsSync76, readdirSync as readdirSync25, readFileSync as
|
|
37111
|
+
import { existsSync as existsSync76, readdirSync as readdirSync25, readFileSync as readFileSync67, statSync as statSync21 } from "node:fs";
|
|
36927
37112
|
import { join as join91 } from "node:path";
|
|
36928
37113
|
// src/core/replay/RunDocument.ts
|
|
36929
37114
|
function toRunDocument(input, fallbackId) {
|
|
@@ -37171,7 +37356,7 @@ function loadRunResult(root2, id) {
|
|
|
37171
37356
|
return { kind: "missing" };
|
|
37172
37357
|
let doc;
|
|
37173
37358
|
try {
|
|
37174
|
-
doc = parseYamlRepairing(
|
|
37359
|
+
doc = parseYamlRepairing(readFileSync67(runPath, "utf8")).doc;
|
|
37175
37360
|
} catch (error2) {
|
|
37176
37361
|
return { kind: "unreadable", dir, error: error2 instanceof Error ? error2.message : String(error2) };
|
|
37177
37362
|
}
|
|
@@ -37182,7 +37367,7 @@ function loadRunResult(root2, id) {
|
|
|
37182
37367
|
let budget = null;
|
|
37183
37368
|
if (existsSync76(budgetPath)) {
|
|
37184
37369
|
try {
|
|
37185
|
-
budget = toBudgetDocument(parseYamlRepairing(
|
|
37370
|
+
budget = toBudgetDocument(parseYamlRepairing(readFileSync67(budgetPath, "utf8")).doc);
|
|
37186
37371
|
} catch {
|
|
37187
37372
|
budget = null;
|
|
37188
37373
|
}
|
|
@@ -37233,7 +37418,7 @@ function readEvents2(dir) {
|
|
|
37233
37418
|
function loadPhaseArtefacts(loaded, phase) {
|
|
37234
37419
|
const read2 = (name) => {
|
|
37235
37420
|
const path = join91(loaded.dir, phase, name);
|
|
37236
|
-
return existsSync76(path) ?
|
|
37421
|
+
return existsSync76(path) ? readFileSync67(path, "utf8") : null;
|
|
37237
37422
|
};
|
|
37238
37423
|
return { phase, handoff: read2(HANDOFF_FILE2), questions: read2(QUESTIONS_FILE3) };
|
|
37239
37424
|
}
|
|
@@ -37244,7 +37429,7 @@ function loadGateEvidence(loaded, relPath) {
|
|
|
37244
37429
|
if (!existsSync76(path))
|
|
37245
37430
|
return null;
|
|
37246
37431
|
try {
|
|
37247
|
-
return
|
|
37432
|
+
return readFileSync67(path, "utf8");
|
|
37248
37433
|
} catch {
|
|
37249
37434
|
return null;
|
|
37250
37435
|
}
|
|
@@ -37257,7 +37442,7 @@ function runLevelEvents(loaded) {
|
|
|
37257
37442
|
}
|
|
37258
37443
|
|
|
37259
37444
|
// src/core/build/retroLog.ts
|
|
37260
|
-
import { existsSync as existsSync77, mkdirSync as mkdirSync17, readFileSync as
|
|
37445
|
+
import { existsSync as existsSync77, mkdirSync as mkdirSync17, readFileSync as readFileSync68, writeFileSync as writeFileSync17 } from "node:fs";
|
|
37261
37446
|
import { join as join92 } from "node:path";
|
|
37262
37447
|
var RETRO_FILE = "retro.md";
|
|
37263
37448
|
var BUILD_RETRO_SECTION = "Build feedback";
|
|
@@ -37306,7 +37491,7 @@ function gateRetroLines(runDir2, runId) {
|
|
|
37306
37491
|
return [];
|
|
37307
37492
|
let text3;
|
|
37308
37493
|
try {
|
|
37309
|
-
text3 =
|
|
37494
|
+
text3 = readFileSync68(path, "utf8");
|
|
37310
37495
|
} catch {
|
|
37311
37496
|
return [];
|
|
37312
37497
|
}
|
|
@@ -37341,7 +37526,7 @@ function appendBuildRetro(runDir2, incoming) {
|
|
|
37341
37526
|
if (incoming.length === 0)
|
|
37342
37527
|
return [];
|
|
37343
37528
|
const path = buildRetroPath(runDir2);
|
|
37344
|
-
const existing = existsSync77(path) ?
|
|
37529
|
+
const existing = existsSync77(path) ? readFileSync68(path, "utf8") : "";
|
|
37345
37530
|
const { before, section: section2 } = splitBuildSection(existing);
|
|
37346
37531
|
const already = new Set(section2.split(`
|
|
37347
37532
|
`).map((line) => line.trim()));
|
|
@@ -37363,7 +37548,7 @@ function appendBuildRetro(runDir2, incoming) {
|
|
|
37363
37548
|
].join(`
|
|
37364
37549
|
`);
|
|
37365
37550
|
mkdirSync17(runDir2, { recursive: true });
|
|
37366
|
-
|
|
37551
|
+
writeFileSync17(path, out, "utf8");
|
|
37367
37552
|
return added;
|
|
37368
37553
|
}
|
|
37369
37554
|
function extractBuildSection(text3) {
|
|
@@ -37400,7 +37585,7 @@ function str7(value) {
|
|
|
37400
37585
|
}
|
|
37401
37586
|
|
|
37402
37587
|
// src/core/retro/renderRetro.ts
|
|
37403
|
-
import { existsSync as existsSync79, readFileSync as
|
|
37588
|
+
import { existsSync as existsSync79, readFileSync as readFileSync70 } from "node:fs";
|
|
37404
37589
|
import { join as join95 } from "node:path";
|
|
37405
37590
|
|
|
37406
37591
|
// src/core/retro/factsFromRun.ts
|
|
@@ -37447,7 +37632,7 @@ function str8(value) {
|
|
|
37447
37632
|
}
|
|
37448
37633
|
|
|
37449
37634
|
// src/core/retro/proposals.ts
|
|
37450
|
-
import { existsSync as existsSync78, readFileSync as
|
|
37635
|
+
import { existsSync as existsSync78, readFileSync as readFileSync69 } from "node:fs";
|
|
37451
37636
|
import { join as join94 } from "node:path";
|
|
37452
37637
|
var PROPOSE_STAGE_MARKER = "propose stage:";
|
|
37453
37638
|
function practiceProposals(loaded) {
|
|
@@ -37526,7 +37711,7 @@ function questionsMax(root2, stage2) {
|
|
|
37526
37711
|
if (!existsSync78(path))
|
|
37527
37712
|
return null;
|
|
37528
37713
|
try {
|
|
37529
|
-
const doc = parseYaml(
|
|
37714
|
+
const doc = parseYaml(readFileSync69(path, "utf8"));
|
|
37530
37715
|
if (typeof doc !== "object" || doc === null)
|
|
37531
37716
|
return null;
|
|
37532
37717
|
const questions = doc.questions;
|
|
@@ -37618,7 +37803,7 @@ function carryBuildSection(dir) {
|
|
|
37618
37803
|
if (!existsSync79(path))
|
|
37619
37804
|
return "";
|
|
37620
37805
|
try {
|
|
37621
|
-
return extractBuildSection(
|
|
37806
|
+
return extractBuildSection(readFileSync70(path, "utf8"));
|
|
37622
37807
|
} catch {
|
|
37623
37808
|
return "";
|
|
37624
37809
|
}
|
|
@@ -37643,7 +37828,7 @@ var OTHER = "other";
|
|
|
37643
37828
|
var RULED_CLASSES = FINDING_CLASSES.filter((cls) => cls !== OTHER);
|
|
37644
37829
|
|
|
37645
37830
|
// src/core/retro/findingClasses.ts
|
|
37646
|
-
import { existsSync as existsSync80, readFileSync as
|
|
37831
|
+
import { existsSync as existsSync80, readFileSync as readFileSync71 } from "node:fs";
|
|
37647
37832
|
import { join as join96 } from "node:path";
|
|
37648
37833
|
var FINDING_CLASSES_FILE = `${PROJECT_FRAMEWORK_DIR}/memory/finding-classes.yml`;
|
|
37649
37834
|
var MAX_CLASSES = 16;
|
|
@@ -37665,7 +37850,7 @@ function loadExtraClasses(root2) {
|
|
|
37665
37850
|
return [];
|
|
37666
37851
|
let text3;
|
|
37667
37852
|
try {
|
|
37668
|
-
text3 =
|
|
37853
|
+
text3 = readFileSync71(path, "utf8");
|
|
37669
37854
|
} catch (error) {
|
|
37670
37855
|
throw new FindingClassesError(`cannot be read — ${messageOf3(error)}`);
|
|
37671
37856
|
}
|
|
@@ -37841,7 +38026,7 @@ function readText3(path) {
|
|
|
37841
38026
|
if (!existsSync81(path))
|
|
37842
38027
|
return null;
|
|
37843
38028
|
try {
|
|
37844
|
-
return
|
|
38029
|
+
return readFileSync72(path, "utf8");
|
|
37845
38030
|
} catch {
|
|
37846
38031
|
return null;
|
|
37847
38032
|
}
|
|
@@ -38220,6 +38405,38 @@ function clipDetail(detail) {
|
|
|
38220
38405
|
return text3.length <= 1200 ? text3 : `${text3.slice(0, 1197)}…`;
|
|
38221
38406
|
}
|
|
38222
38407
|
|
|
38408
|
+
// src/core/build/dependencyHold.ts
|
|
38409
|
+
function dependencyIsPending(status) {
|
|
38410
|
+
return status === "review" || status === "in_progress";
|
|
38411
|
+
}
|
|
38412
|
+
function decidingHold(holds) {
|
|
38413
|
+
return holds.find((hold) => !dependencyIsPending(hold.status)) ?? holds[0] ?? null;
|
|
38414
|
+
}
|
|
38415
|
+
function dependencyHoldReason(held2) {
|
|
38416
|
+
return held2.status === "blocked" ? `dependency ${held2.id} blocked` : `dependency ${held2.id} is \`${held2.status}\`, not \`done\``;
|
|
38417
|
+
}
|
|
38418
|
+
function dependencyWaitReason(held2) {
|
|
38419
|
+
return `it waits on dependency ${held2.id}, which is \`${held2.status}\` and is offered again by the next ` + `invocation; ${held2.id} \`done\` is what releases it, and no reopen is owed`;
|
|
38420
|
+
}
|
|
38421
|
+
function dependencyWaitLine(storyId, held2) {
|
|
38422
|
+
return ` · ${storyId} waits on ${held2.id}, which is \`${held2.status}\` — left \`todo\`, not \`blocked\`: ` + `it is asked again once ${held2.id} settles`;
|
|
38423
|
+
}
|
|
38424
|
+
function dependencyNamedByHold(reason) {
|
|
38425
|
+
const found = /^dependency (\S+) (?:blocked|is `\w+`, not `done`)$/.exec(reason.trim());
|
|
38426
|
+
return found?.[1] ?? null;
|
|
38427
|
+
}
|
|
38428
|
+
function dependencyHoldOfLog(log) {
|
|
38429
|
+
const lines = log.split(`
|
|
38430
|
+
`);
|
|
38431
|
+
const start = lines.indexOf(WHY_NOT_DONE_HEADING);
|
|
38432
|
+
if (start === -1)
|
|
38433
|
+
return null;
|
|
38434
|
+
const rest = lines.slice(start + 1);
|
|
38435
|
+
const end = rest.findIndex((line) => line.startsWith("## "));
|
|
38436
|
+
const body = (end === -1 ? rest : rest.slice(0, end)).map((line) => line.trim()).filter((line) => line !== "");
|
|
38437
|
+
return body.length === 1 && body[0] !== undefined ? dependencyNamedByHold(body[0]) : null;
|
|
38438
|
+
}
|
|
38439
|
+
|
|
38223
38440
|
// src/core/facilitator/reviewerModel.ts
|
|
38224
38441
|
function resolveReviewer(layers) {
|
|
38225
38442
|
const byStakes = layers.stakes === null ? undefined : layers.byStakes[layers.stakes];
|
|
@@ -38326,7 +38543,7 @@ function overShareSentence(ceilingUsd, measuredUsd, stories) {
|
|
|
38326
38543
|
if (ratio <= 1)
|
|
38327
38544
|
return null;
|
|
38328
38545
|
const over = stories === 1 ? "1 story" : `${String(stories)} stories`;
|
|
38329
|
-
return `${over} measured $${
|
|
38546
|
+
return `${over} measured $${round23(measuredUsd).toFixed(2)} against ` + `$${round23(ceilingUsd).toFixed(2)} of spawn ceilings — ${ratio.toFixed(1)}x`;
|
|
38330
38547
|
}
|
|
38331
38548
|
function sumOrNull(values) {
|
|
38332
38549
|
if (values.length === 0)
|
|
@@ -38344,10 +38561,10 @@ function sumOrNull(values) {
|
|
|
38344
38561
|
import { basename as basename13 } from "node:path";
|
|
38345
38562
|
|
|
38346
38563
|
// src/core/budget/loadBudget.ts
|
|
38347
|
-
import { existsSync as existsSync82, readFileSync as
|
|
38564
|
+
import { existsSync as existsSync82, readFileSync as readFileSync73 } from "node:fs";
|
|
38348
38565
|
import { join as join98 } from "node:path";
|
|
38349
38566
|
function loadBudget(path) {
|
|
38350
|
-
const doc = parseYaml(
|
|
38567
|
+
const doc = parseYaml(readFileSync73(path, "utf8"));
|
|
38351
38568
|
const validation = validateRunBudget(doc);
|
|
38352
38569
|
noteDeprecations(path, validation);
|
|
38353
38570
|
if (!validation.ok) {
|
|
@@ -38794,7 +39011,7 @@ function phaseCostToDate(runDir2, phaseId, stageId, invocationUsd, invocationTur
|
|
|
38794
39011
|
turns = [
|
|
38795
39012
|
...turns,
|
|
38796
39013
|
...invocationTurns.map((task2) => ({
|
|
38797
|
-
costUsd: task2.metered === false ? null :
|
|
39014
|
+
costUsd: task2.metered === false ? null : round23(task2.costUsd),
|
|
38798
39015
|
metered: task2.metered !== false,
|
|
38799
39016
|
tokens: turnTokens({
|
|
38800
39017
|
tokens: task2.tokens,
|
|
@@ -38812,12 +39029,12 @@ function phaseCostToDate(runDir2, phaseId, stageId, invocationUsd, invocationTur
|
|
|
38812
39029
|
const tally = { unmetered, metered: turns.length - unmetered };
|
|
38813
39030
|
if (recorded === null) {
|
|
38814
39031
|
return {
|
|
38815
|
-
usd:
|
|
39032
|
+
usd: round23(invocationUsd),
|
|
38816
39033
|
note: "this invocation only — `run.yml` could not be read for what the stage spent before it" + (note === null ? "" : `; ${note}`),
|
|
38817
39034
|
...tally
|
|
38818
39035
|
};
|
|
38819
39036
|
}
|
|
38820
|
-
return { usd:
|
|
39037
|
+
return { usd: round23(recorded + invocationUsd), note, ...tally };
|
|
38821
39038
|
}
|
|
38822
39039
|
|
|
38823
39040
|
// src/core/facilitator/executors/build.ts
|
|
@@ -38947,7 +39164,7 @@ async function rederiveImplicitPlan(ctx, workspace, parts, lines) {
|
|
|
38947
39164
|
const kept = (why) => {
|
|
38948
39165
|
lines.push(` · kept ${IMPLICIT_PLAN_REL} (--discard-pending re-derives only an unbuilt plan): ${why}`);
|
|
38949
39166
|
};
|
|
38950
|
-
const blocker = implicitPlanIsStale(
|
|
39167
|
+
const blocker = implicitPlanIsStale(readFileSync74(implicitPlanPath(ctx.runDir), "utf8"));
|
|
38951
39168
|
if (blocker !== null) {
|
|
38952
39169
|
kept(blocker);
|
|
38953
39170
|
return;
|
|
@@ -39015,6 +39232,7 @@ class BuildSession {
|
|
|
39015
39232
|
aside = [];
|
|
39016
39233
|
preflight;
|
|
39017
39234
|
lanes;
|
|
39235
|
+
waits = new Map;
|
|
39018
39236
|
constructor(ctx, workspace, plan, opening = []) {
|
|
39019
39237
|
this.ctx = ctx;
|
|
39020
39238
|
this.workspace = workspace;
|
|
@@ -39028,6 +39246,9 @@ class BuildSession {
|
|
|
39028
39246
|
}
|
|
39029
39247
|
if (plan.priceIssue !== null)
|
|
39030
39248
|
this.advisories.push(plan.priceIssue);
|
|
39249
|
+
const over = planOverStageAdvisory(this.capParts, this.capLever);
|
|
39250
|
+
if (over !== null)
|
|
39251
|
+
this.advisories.push(over);
|
|
39031
39252
|
}
|
|
39032
39253
|
async runAll() {
|
|
39033
39254
|
const refusal = await this.refuseOnDirtyRepos() ?? await this.refuseOnForeignEpic() ?? await this.setAsideForeign() ?? await this.refuseOnRedBase() ?? await this.refuseOnUnrunnableWorktree();
|
|
@@ -39044,13 +39265,22 @@ class BuildSession {
|
|
|
39044
39265
|
pending.push(planned);
|
|
39045
39266
|
continue;
|
|
39046
39267
|
}
|
|
39268
|
+
const released = status === "blocked" ? this.staleDependencyHold(planned) : null;
|
|
39269
|
+
if (released !== null) {
|
|
39270
|
+
this.lines.push(` · ${planned.story.id} was \`blocked\` for dependency ${released}, which is now \`done\` — ` + "that was never an attempt, so it is offered again");
|
|
39271
|
+
pending.push(planned);
|
|
39272
|
+
continue;
|
|
39273
|
+
}
|
|
39047
39274
|
if (status === "done" || status === "blocked") {
|
|
39048
39275
|
this.lines.push(` · ${planned.story.id} is already \`${status}\` — left alone`);
|
|
39049
39276
|
continue;
|
|
39050
39277
|
}
|
|
39051
39278
|
const held2 = this.blockingDependency(planned);
|
|
39052
39279
|
if (held2 !== null) {
|
|
39053
|
-
|
|
39280
|
+
if (dependencyIsPending(held2.status))
|
|
39281
|
+
this.waitOnDependency(planned, held2);
|
|
39282
|
+
else
|
|
39283
|
+
this.blockOnDependency(planned, held2);
|
|
39054
39284
|
continue;
|
|
39055
39285
|
}
|
|
39056
39286
|
this.noteIfReopened(planned, status);
|
|
@@ -39286,7 +39516,7 @@ class BuildSession {
|
|
|
39286
39516
|
}
|
|
39287
39517
|
const story = await this.openStory(planned);
|
|
39288
39518
|
const declared2 = this.ctx.costUsd ?? result2.cost_usd;
|
|
39289
|
-
const cost = declared2 === null ? null :
|
|
39519
|
+
const cost = declared2 === null ? null : round23(declared2);
|
|
39290
39520
|
this.tasks.push({
|
|
39291
39521
|
key: planned.story.id,
|
|
39292
39522
|
model: this.model(),
|
|
@@ -39430,7 +39660,22 @@ class BuildSession {
|
|
|
39430
39660
|
};
|
|
39431
39661
|
}
|
|
39432
39662
|
const ahead = await commitsBetween(story.repoDir, story.epicBranch, story.branch);
|
|
39433
|
-
if (ahead ===
|
|
39663
|
+
if (ahead === 0) {
|
|
39664
|
+
const merge = readReviewLedger(this.ctx.runDir, id).lastMerge;
|
|
39665
|
+
if (merge !== null && reviewNeverCompleted(merge.verdict)) {
|
|
39666
|
+
return await this.reviewOnlyHalf(planned, asIs, story, before, merge);
|
|
39667
|
+
}
|
|
39668
|
+
return {
|
|
39669
|
+
story,
|
|
39670
|
+
cost: 0,
|
|
39671
|
+
dod: [],
|
|
39672
|
+
commit: null,
|
|
39673
|
+
failure: asIsNotAheadReason(story.branch, story.epicBranch, ahead, merge === null ? undefined : merge),
|
|
39674
|
+
developerError: null,
|
|
39675
|
+
before
|
|
39676
|
+
};
|
|
39677
|
+
}
|
|
39678
|
+
if (ahead === null) {
|
|
39434
39679
|
return {
|
|
39435
39680
|
story,
|
|
39436
39681
|
cost: 0,
|
|
@@ -39468,6 +39713,27 @@ class BuildSession {
|
|
|
39468
39713
|
}
|
|
39469
39714
|
return { story, cost: 0, dod, commit: commit2, failure: null, developerError: null, before };
|
|
39470
39715
|
}
|
|
39716
|
+
async reviewOnlyHalf(planned, asIs, story, before, merge) {
|
|
39717
|
+
const id = planned.story.id;
|
|
39718
|
+
const reviewed = { commit: merge.commit, epicBase: merge.epicBase };
|
|
39719
|
+
this.asIsSettlements.set(id, { ...asIs, reason: "review-only", reviewed });
|
|
39720
|
+
this.lines.push(` · ${id}: ${AS_IS_REVIEW_ONLY_MARK} — \`${merge.commit.slice(0, 7)}\` is already on ` + `\`${story.epicBranch}\` and nothing judged it; running the dod on the epic head and the review ` + `over \`${reviewDiffRange(merge.epicBase, story.epicBranch, story.branch)}\``);
|
|
39721
|
+
await this.writes.run(() => this.refreshStoryBase(planned, story.repoDir, story.worktree, story.branch, story.epicBranch));
|
|
39722
|
+
const dod = await this.runDod(story);
|
|
39723
|
+
if (!this.dodProves(dod)) {
|
|
39724
|
+
const failing = dodFailure(dod);
|
|
39725
|
+
return {
|
|
39726
|
+
story,
|
|
39727
|
+
cost: 0,
|
|
39728
|
+
dod,
|
|
39729
|
+
commit: null,
|
|
39730
|
+
failure: failing === undefined ? "the story declares no dod commands, so nothing could prove it" : dodFailureReason(failing, planned.story.repo),
|
|
39731
|
+
developerError: null,
|
|
39732
|
+
before
|
|
39733
|
+
};
|
|
39734
|
+
}
|
|
39735
|
+
return { story, cost: 0, dod, commit: merge.commit, failure: null, developerError: null, before, reviewOnly: reviewed };
|
|
39736
|
+
}
|
|
39471
39737
|
async buildHalf(planned) {
|
|
39472
39738
|
const asIs = this.asIsFor(planned);
|
|
39473
39739
|
if (asIs !== null)
|
|
@@ -39507,7 +39773,7 @@ class BuildSession {
|
|
|
39507
39773
|
let spent = 0;
|
|
39508
39774
|
let separatorRetries = 0;
|
|
39509
39775
|
for (;; ) {
|
|
39510
|
-
spent =
|
|
39776
|
+
spent = round23(spent + developer.cost);
|
|
39511
39777
|
const capDeath = developer.error !== null && diedOnCap(developer.error);
|
|
39512
39778
|
if (developer.error !== null) {
|
|
39513
39779
|
const proven2 = capDeath && await workSince({
|
|
@@ -39572,7 +39838,7 @@ class BuildSession {
|
|
|
39572
39838
|
failure: [
|
|
39573
39839
|
why,
|
|
39574
39840
|
...developer.refused === null ? [] : [permissionBlockReason(developer.refused, { declared: this.repoCommands(story.planned.story.repo) })],
|
|
39575
|
-
...budgetDeath === null ? [] : [capDeathReason(budgetDeath)]
|
|
39841
|
+
...budgetDeath === null ? [] : [capDeathReason(budgetDeath, this.capParts, story.planned.story.id, story.attempt, this.capLever)]
|
|
39576
39842
|
].join("; and "),
|
|
39577
39843
|
developerError: null,
|
|
39578
39844
|
before
|
|
@@ -39606,6 +39872,10 @@ class BuildSession {
|
|
|
39606
39872
|
await this.block(story, "the story produced no commit to review", half.cost, dod);
|
|
39607
39873
|
return "settled";
|
|
39608
39874
|
}
|
|
39875
|
+
if (half.reviewOnly !== undefined) {
|
|
39876
|
+
this.noteMerged(story, null);
|
|
39877
|
+
return await this.reviewAndSettle(story, dod, half.reviewOnly.commit, half.cost, null, half.reviewOnly.epicBase);
|
|
39878
|
+
}
|
|
39609
39879
|
const update = await updateStoryBase({
|
|
39610
39880
|
storyId: story.planned.story.id,
|
|
39611
39881
|
repoDir: story.repoDir,
|
|
@@ -39665,14 +39935,14 @@ class BuildSession {
|
|
|
39665
39935
|
epicBase,
|
|
39666
39936
|
verdict: "n-a",
|
|
39667
39937
|
review: review2,
|
|
39668
|
-
cost:
|
|
39938
|
+
cost: round23(priorCost),
|
|
39669
39939
|
reason: review2.summary
|
|
39670
39940
|
});
|
|
39671
39941
|
return "settled";
|
|
39672
39942
|
}
|
|
39673
39943
|
const outcome = supplied === undefined ? await this.spawnReviewer(story, dod, epicBase) : { review: supplied, cost: 0 };
|
|
39674
39944
|
const review = outcome.review;
|
|
39675
|
-
const cost =
|
|
39945
|
+
const cost = round23(priorCost + outcome.cost);
|
|
39676
39946
|
if (review.verdict === "error") {
|
|
39677
39947
|
await this.settle(story, "review", {
|
|
39678
39948
|
dod,
|
|
@@ -39690,6 +39960,21 @@ class BuildSession {
|
|
|
39690
39960
|
if (review.verdict === "fixlist") {
|
|
39691
39961
|
const rel = this.writeFixlistFor(story, review, commit2, epicBase);
|
|
39692
39962
|
const open2 = openFindings(review.fixlist).length;
|
|
39963
|
+
if (open2 === 0) {
|
|
39964
|
+
this.lines.push(` · ${story.planned.story.id}: ${FIXLIST_SETTLED_MARK} (${rel})`);
|
|
39965
|
+
await this.settle(story, "done", {
|
|
39966
|
+
dod,
|
|
39967
|
+
commit: commit2,
|
|
39968
|
+
merged: true,
|
|
39969
|
+
carried,
|
|
39970
|
+
epicBase,
|
|
39971
|
+
verdict: "fixlist",
|
|
39972
|
+
review,
|
|
39973
|
+
cost,
|
|
39974
|
+
reason: null
|
|
39975
|
+
});
|
|
39976
|
+
return "settled";
|
|
39977
|
+
}
|
|
39693
39978
|
await this.settle(story, "review", {
|
|
39694
39979
|
dod,
|
|
39695
39980
|
commit: commit2,
|
|
@@ -39789,17 +40074,17 @@ class BuildSession {
|
|
|
39789
40074
|
}
|
|
39790
40075
|
findings3.push({ ...finding2, resolved: false, resolvedSha: null });
|
|
39791
40076
|
refused2.push(`#${String(finding2.n)} — ${why}`);
|
|
39792
|
-
text3 = markUnverified(text3 ??
|
|
40077
|
+
text3 = markUnverified(text3 ?? readFileSync74(fixlist.path, "utf8"), finding2.n, why);
|
|
39793
40078
|
this.lines.push(` · ${id}: fix-list finding #${String(finding2.n)} claimed \`Resolved: yes\` and ` + `${why} — recorded as \`${CLAIMED_UNVERIFIED}\`, and it still blocks \`done\``);
|
|
39794
40079
|
}
|
|
39795
|
-
const canonicalized = await canonicalizeResolutions(story.repoDir, findings3, text3 ??
|
|
40080
|
+
const canonicalized = await canonicalizeResolutions(story.repoDir, findings3, text3 ?? readFileSync74(fixlist.path, "utf8"));
|
|
39796
40081
|
if (canonicalized.lines.length > 0) {
|
|
39797
40082
|
text3 = canonicalized.text;
|
|
39798
40083
|
for (const line of canonicalized.lines)
|
|
39799
40084
|
this.lines.push(` · ${id}: ${line}`);
|
|
39800
40085
|
}
|
|
39801
40086
|
if (text3 !== null)
|
|
39802
|
-
|
|
40087
|
+
writeFileSync18(fixlist.path, text3, "utf8");
|
|
39803
40088
|
return { findings: canonicalized.findings, refused: refused2 };
|
|
39804
40089
|
}
|
|
39805
40090
|
async unverifiedBecause(story, sha) {
|
|
@@ -39891,19 +40176,36 @@ class BuildSession {
|
|
|
39891
40176
|
return wave.stories.some((planned) => this.statusOf(planned) === "blocked");
|
|
39892
40177
|
}
|
|
39893
40178
|
blockingDependency(planned) {
|
|
40179
|
+
const holds = [];
|
|
39894
40180
|
for (const id of planned.story.depends_on) {
|
|
39895
40181
|
const dependency = this.plan.stories.get(id);
|
|
39896
40182
|
if (dependency === undefined)
|
|
39897
40183
|
continue;
|
|
39898
40184
|
const status = this.statusOf(dependency);
|
|
39899
40185
|
if (status !== "done")
|
|
39900
|
-
|
|
40186
|
+
holds.push({ id, status });
|
|
39901
40187
|
}
|
|
39902
|
-
return
|
|
40188
|
+
return decidingHold(holds);
|
|
40189
|
+
}
|
|
40190
|
+
waitOnDependency(planned, held2) {
|
|
40191
|
+
this.waits.set(planned.story.id, held2);
|
|
40192
|
+
this.lines.push(dependencyWaitLine(planned.story.id, held2));
|
|
40193
|
+
}
|
|
40194
|
+
staleDependencyHold(planned) {
|
|
40195
|
+
const path = join99(this.ctx.runDir, BUILD_PHASE, LOG_DIR, `${planned.story.id}.md`);
|
|
40196
|
+
let named;
|
|
40197
|
+
try {
|
|
40198
|
+
named = dependencyHoldOfLog(readFileSync74(path, "utf8"));
|
|
40199
|
+
} catch {
|
|
40200
|
+
return null;
|
|
40201
|
+
}
|
|
40202
|
+
if (named === null)
|
|
40203
|
+
return null;
|
|
40204
|
+
return this.blockingDependency(planned) === null ? named : null;
|
|
39903
40205
|
}
|
|
39904
40206
|
blockOnDependency(planned, held2) {
|
|
39905
40207
|
const id = planned.story.id;
|
|
39906
|
-
const reason = held2
|
|
40208
|
+
const reason = dependencyHoldReason(held2);
|
|
39907
40209
|
const epic = this.plan.epics.get(planned.story.epic);
|
|
39908
40210
|
const outcome = {
|
|
39909
40211
|
id,
|
|
@@ -40076,7 +40378,7 @@ class BuildSession {
|
|
|
40076
40378
|
this.tasks.push({
|
|
40077
40379
|
key: story.planned.story.id,
|
|
40078
40380
|
model: this.model(),
|
|
40079
|
-
costUsd:
|
|
40381
|
+
costUsd: round23(agent.costUsd),
|
|
40080
40382
|
sessionId: agent.sessionId,
|
|
40081
40383
|
error: agent.error,
|
|
40082
40384
|
role: "developer",
|
|
@@ -40086,7 +40388,7 @@ class BuildSession {
|
|
|
40086
40388
|
durationMs: agent.durationMs
|
|
40087
40389
|
});
|
|
40088
40390
|
if (agent.ok) {
|
|
40089
|
-
return { cost:
|
|
40391
|
+
return { cost: round23(agent.costUsd), error: null, refused: agent.permissionRefusal };
|
|
40090
40392
|
}
|
|
40091
40393
|
const error = (agent.error ?? "").trim() === "" ? DEVELOPER_FAILED : (agent.error ?? "").trim();
|
|
40092
40394
|
this.ctx.emit("check.failed", {
|
|
@@ -40097,7 +40399,7 @@ class BuildSession {
|
|
|
40097
40399
|
attempt: story.attempt,
|
|
40098
40400
|
detail: error
|
|
40099
40401
|
});
|
|
40100
|
-
return { cost:
|
|
40402
|
+
return { cost: round23(agent.costUsd), error, refused: null };
|
|
40101
40403
|
}
|
|
40102
40404
|
dodProves(dod) {
|
|
40103
40405
|
return dod.length === 0 ? dodIsSatisfiedEmpty(this.plan) : dodGreen({ dod });
|
|
@@ -40268,7 +40570,7 @@ class BuildSession {
|
|
|
40268
40570
|
});
|
|
40269
40571
|
const parsed = agent.ok ? parseReview(agent.structured, agent.result) : reviewerFailed(agent.error);
|
|
40270
40572
|
const review = this.narrowFixlist(id, parsed);
|
|
40271
|
-
const turn =
|
|
40573
|
+
const turn = round23(agent.costUsd);
|
|
40272
40574
|
const again = this.formatRetry(story, review, {
|
|
40273
40575
|
costUsd: turn,
|
|
40274
40576
|
sessionId: agent.sessionId,
|
|
@@ -40278,7 +40580,7 @@ class BuildSession {
|
|
|
40278
40580
|
});
|
|
40279
40581
|
if (again !== null) {
|
|
40280
40582
|
refusal = again;
|
|
40281
|
-
spent =
|
|
40583
|
+
spent = round23(spent + turn);
|
|
40282
40584
|
continue;
|
|
40283
40585
|
}
|
|
40284
40586
|
this.recordReview(story, review, {
|
|
@@ -40291,7 +40593,7 @@ class BuildSession {
|
|
|
40291
40593
|
source: "agent",
|
|
40292
40594
|
reviewer: { model: reviewer.model, effort: reviewer.effort, basis: "spawned" }
|
|
40293
40595
|
});
|
|
40294
|
-
return { review, cost:
|
|
40596
|
+
return { review, cost: round23(spent + turn) };
|
|
40295
40597
|
}
|
|
40296
40598
|
}
|
|
40297
40599
|
formatRetry(story, review, task2) {
|
|
@@ -40492,17 +40794,18 @@ class BuildSession {
|
|
|
40492
40794
|
...outcome.permissionRefused == null ? {} : { permission_refused: outcome.permissionRefused },
|
|
40493
40795
|
...outcome.budgetDeath == null ? {} : { budget_death: outcome.budgetDeath },
|
|
40494
40796
|
...outcome.reviewerUnfunded == null ? {} : { reviewer_unfunded: outcome.reviewerUnfunded },
|
|
40495
|
-
...outcome.asIs == null ? {} : { as_is: true, as_is_by: outcome.asIs.actor, as_is_note: outcome.asIs.note }
|
|
40797
|
+
...outcome.asIs == null ? {} : { as_is: true, as_is_by: outcome.asIs.actor, as_is_note: outcome.asIs.note },
|
|
40798
|
+
...outcome.asIs?.reason === undefined ? {} : { as_is_reason: outcome.asIs.reason }
|
|
40496
40799
|
});
|
|
40497
40800
|
if (pruning && (rescued === null || rescued.worktree === null)) {
|
|
40498
40801
|
await removeWorktree(story.repoDir, story.worktree);
|
|
40499
40802
|
}
|
|
40500
|
-
this.lines.push(` ${status === "done" ? "✓" : "·"} ${id} → \`${status}\`` + (outcome.reason === null ? "" : ` (${outcome.reason})`) + (outcome.asIs == null ? "" : ` — ${AS_IS_MARK};
|
|
40803
|
+
this.lines.push(` ${status === "done" ? "✓" : "·"} ${id} → \`${status}\`` + (outcome.reason === null ? "" : ` (${outcome.reason})`) + (outcome.asIs == null ? "" : ` — ${outcome.asIs.reason === "review-only" ? AS_IS_REVIEW_ONLY_MARK : AS_IS_MARK}; ` + `${outcome.asIs.actor} signed it`) + (outcome.permissionRefused == null ? "" : ` — ${withCure(`\`${outcome.permissionRefused}\` was refused for approval; the tree held committed work, so the DoD decided`, outcome.permissionRefused, outcome.declaredCommands)}`));
|
|
40501
40804
|
}
|
|
40502
40805
|
writeLog(outcome) {
|
|
40503
40806
|
const dir = join99(this.ctx.runDir, BUILD_PHASE, LOG_DIR);
|
|
40504
40807
|
mkdirSync18(dir, { recursive: true });
|
|
40505
|
-
|
|
40808
|
+
writeFileSync18(join99(dir, `${outcome.id}.md`), renderReviewLog(outcome), "utf8");
|
|
40506
40809
|
}
|
|
40507
40810
|
recordStoryFeedback(outcome) {
|
|
40508
40811
|
appendBuildRetro(this.ctx.runDir, storyRetroLines(outcome, this.ctx.runId));
|
|
@@ -40546,11 +40849,12 @@ class BuildSession {
|
|
|
40546
40849
|
for (const planned of wave.stories) {
|
|
40547
40850
|
if (named.has(planned.story.id))
|
|
40548
40851
|
continue;
|
|
40852
|
+
const wait = this.waits.get(planned.story.id);
|
|
40549
40853
|
rows.push({
|
|
40550
40854
|
id: planned.story.id,
|
|
40551
40855
|
rel: this.plan.implicit ? IMPLICIT_PLAN_REL : planned.rel,
|
|
40552
40856
|
status: this.statusOf(planned),
|
|
40553
|
-
reason: "this stage recorded no attempt and no reason for it — " + "nothing here says the work was done, and nothing says why it was not"
|
|
40857
|
+
reason: wait !== undefined ? dependencyWaitReason(wait) : "this stage recorded no attempt and no reason for it — " + "nothing here says the work was done, and nothing says why it was not"
|
|
40554
40858
|
});
|
|
40555
40859
|
}
|
|
40556
40860
|
}
|
|
@@ -40561,7 +40865,7 @@ class BuildSession {
|
|
|
40561
40865
|
mkdirSync18(join99(path, ".."), { recursive: true });
|
|
40562
40866
|
const cost = phaseCostToDate(this.ctx.runDir, this.ctx.phaseId, this.ctx.stageId, this.spent(), this.tasks, storySpendToDate(this.ctx.runDir, this.ctx.phaseId, this.ctx.stageId, this.tasks));
|
|
40563
40867
|
const carried = this.carriedRows();
|
|
40564
|
-
|
|
40868
|
+
writeFileSync18(path, renderBuildHandoff({
|
|
40565
40869
|
runId: this.ctx.runId,
|
|
40566
40870
|
stageId: this.ctx.stageId,
|
|
40567
40871
|
model: this.model(),
|
|
@@ -40594,7 +40898,7 @@ class BuildSession {
|
|
|
40594
40898
|
}
|
|
40595
40899
|
wideningRows() {
|
|
40596
40900
|
try {
|
|
40597
|
-
return wideningRows(
|
|
40901
|
+
return wideningRows(readFileSync74(join99(this.ctx.runDir, EVENTS_FILE2), "utf8"));
|
|
40598
40902
|
} catch {
|
|
40599
40903
|
return [];
|
|
40600
40904
|
}
|
|
@@ -40812,7 +41116,7 @@ class BuildSession {
|
|
|
40812
41116
|
` ${namePaths(row2.paths)}`,
|
|
40813
41117
|
" Commit them, or set them aside by hand, then run " + `\`${relaunchCommand(this.ctx.mode, this.ctx.runId)}\`.`
|
|
40814
41118
|
],
|
|
40815
|
-
error: `repo \`${row2.repo}\` has uncommitted changes that could not be set aside`
|
|
41119
|
+
error: `repo \`${row2.repo}\` has uncommitted changes that could not be set aside: ` + `${outcome.reason ?? "no reason given"}`
|
|
40816
41120
|
};
|
|
40817
41121
|
}
|
|
40818
41122
|
this.ctx.emit(FOREIGN_ASIDE_EVENT, asidePayload(outcome.stash, reason));
|
|
@@ -40823,7 +41127,7 @@ class BuildSession {
|
|
|
40823
41127
|
async restoreForeignWorkAside() {
|
|
40824
41128
|
let open;
|
|
40825
41129
|
try {
|
|
40826
|
-
open = pendingAsides(
|
|
41130
|
+
open = pendingAsides(readFileSync74(join99(this.ctx.runDir, EVENTS_FILE2), "utf8"), (repo) => {
|
|
40827
41131
|
try {
|
|
40828
41132
|
return repoDirOf(this.workspace, repo);
|
|
40829
41133
|
} catch {
|
|
@@ -40852,7 +41156,7 @@ class BuildSession {
|
|
|
40852
41156
|
const status = this.statusOf(planned);
|
|
40853
41157
|
if (status === "done")
|
|
40854
41158
|
continue;
|
|
40855
|
-
if (status === "blocked" && this.blockedByFailedDeveloper(planned) === null)
|
|
41159
|
+
if (status === "blocked" && this.blockedByFailedDeveloper(planned) === null && this.staleDependencyHold(planned) === null)
|
|
40856
41160
|
continue;
|
|
40857
41161
|
rows.push(planned);
|
|
40858
41162
|
}
|
|
@@ -40870,16 +41174,16 @@ class BuildSession {
|
|
|
40870
41174
|
if (fresh !== undefined)
|
|
40871
41175
|
return fresh.status;
|
|
40872
41176
|
try {
|
|
40873
|
-
const value = /^status\s*:\s*(\w+)\s*$/m.exec(
|
|
41177
|
+
const value = /^status\s*:\s*(\w+)\s*$/m.exec(readFileSync74(planned.path, "utf8"))?.[1];
|
|
40874
41178
|
return isPlanStatus2(value) ? value : planned.story.status;
|
|
40875
41179
|
} catch {
|
|
40876
41180
|
return planned.story.status;
|
|
40877
41181
|
}
|
|
40878
41182
|
}
|
|
40879
41183
|
setStoryStatus(planned, status, evidence) {
|
|
40880
|
-
const text3 =
|
|
41184
|
+
const text3 = readFileSync74(planned.path, "utf8");
|
|
40881
41185
|
const patched = this.plan.implicit ? updateImplicitPlan(text3, { status, evidence }) : updateStoryFront(text3, { status, evidence });
|
|
40882
|
-
|
|
41186
|
+
writeFileSync18(planned.path, patched, "utf8");
|
|
40883
41187
|
}
|
|
40884
41188
|
updateEpicStatus(epic) {
|
|
40885
41189
|
const statuses = epic.epic.stories.map((id) => {
|
|
@@ -40887,7 +41191,7 @@ class BuildSession {
|
|
|
40887
41191
|
return planned === undefined ? "todo" : this.statusOf(planned);
|
|
40888
41192
|
});
|
|
40889
41193
|
const status = statuses.every((s) => s === "done") ? "done" : statuses.some((s) => s === "blocked") ? "blocked" : "in_progress";
|
|
40890
|
-
|
|
41194
|
+
writeFileSync18(epic.path, updateStoryFront(readFileSync74(epic.path, "utf8"), { status }), "utf8");
|
|
40891
41195
|
return status;
|
|
40892
41196
|
}
|
|
40893
41197
|
noteMerged(story, carried) {
|
|
@@ -40957,7 +41261,7 @@ class BuildSession {
|
|
|
40957
41261
|
if (!existsSync83(path))
|
|
40958
41262
|
return null;
|
|
40959
41263
|
try {
|
|
40960
|
-
const doc = JSON.parse(
|
|
41264
|
+
const doc = JSON.parse(readFileSync74(path, "utf8"));
|
|
40961
41265
|
return typeof doc.session_id === "string" && doc.session_id !== "" ? doc.session_id : null;
|
|
40962
41266
|
} catch {
|
|
40963
41267
|
return null;
|
|
@@ -41021,6 +41325,11 @@ class BuildSession {
|
|
|
41021
41325
|
storyCapFloorUsd: this.ctx.spec.tuning.storyCapFloorUsd
|
|
41022
41326
|
};
|
|
41023
41327
|
}
|
|
41328
|
+
get capLever() {
|
|
41329
|
+
return {
|
|
41330
|
+
raiseCommand: (usd3) => stageRaiseCommand(this.ctx.runId, this.ctx.phaseId, this.ctx.stageId, usd3)
|
|
41331
|
+
};
|
|
41332
|
+
}
|
|
41024
41333
|
get attempts() {
|
|
41025
41334
|
return this.ctx.spec.tuning.attempts;
|
|
41026
41335
|
}
|
|
@@ -41028,7 +41337,7 @@ class BuildSession {
|
|
|
41028
41337
|
return this.ctx.spec.tuning.fixlistRounds;
|
|
41029
41338
|
}
|
|
41030
41339
|
spent() {
|
|
41031
|
-
return
|
|
41340
|
+
return round23(this.tasks.reduce((sum, task2) => sum + task2.costUsd, 0));
|
|
41032
41341
|
}
|
|
41033
41342
|
logPaths() {
|
|
41034
41343
|
return [...this.outcomes.values()].map((outcome) => outcome.reviewRel);
|
|
@@ -41141,7 +41450,7 @@ class BuildSession {
|
|
|
41141
41450
|
const path = join99(this.ctx.runDir, BUILD_PHASE, LOG_DIR, `${storyId}.md`);
|
|
41142
41451
|
if (!this.hasPriorAttempt(storyId) || !existsSync83(path))
|
|
41143
41452
|
return "";
|
|
41144
|
-
return
|
|
41453
|
+
return readFileSync74(path, "utf8").trimEnd().split(`
|
|
41145
41454
|
`).map((line) => `> ${line}`).join(`
|
|
41146
41455
|
`);
|
|
41147
41456
|
}
|
|
@@ -41190,9 +41499,6 @@ function permissionBlockReason(command2, options = {}) {
|
|
|
41190
41499
|
function diedOnCap(error) {
|
|
41191
41500
|
return error.includes("Reached maximum budget");
|
|
41192
41501
|
}
|
|
41193
|
-
function capDeathReason(error) {
|
|
41194
|
-
return `the developer died on its per-story cap — ${error}: the ceiling is derived from ` + "`03-plan/budget.yml`'s price for this story, so a story that really costs more than the " + "plan guessed wants a higher price there or a higher `story_cap_multiplier:` on the stage";
|
|
41195
|
-
}
|
|
41196
41502
|
var REVIEWER_TOOLS = ["Read", "Grep", "Glob", "Bash(git diff *)"];
|
|
41197
41503
|
function summaryOf(envelope) {
|
|
41198
41504
|
return typeof envelope.summary === "string" ? envelope.summary : "";
|
|
@@ -41221,7 +41527,7 @@ function failed3(ctx, error, tasks) {
|
|
|
41221
41527
|
ok: false,
|
|
41222
41528
|
awaiting: false,
|
|
41223
41529
|
tasks,
|
|
41224
|
-
costUsd:
|
|
41530
|
+
costUsd: round23(tasks.reduce((sum, task2) => sum + task2.costUsd, 0)),
|
|
41225
41531
|
outputs: [],
|
|
41226
41532
|
lines: [`${ctx.phaseId}/${ctx.stageId} failed: ${error}`],
|
|
41227
41533
|
error
|
|
@@ -41401,7 +41707,7 @@ function preparedRefusal(store, options, notes) {
|
|
|
41401
41707
|
` finish it: run ${where}/prompt.md, write ${where}/result.json, then \`tldrx next --commit ${store.runId}\``,
|
|
41402
41708
|
` drop it: \`tldrx reject --run ${store.runId} --note "…"\``,
|
|
41403
41709
|
` redo it: \`tldrx next --discard-pending\` (throws the bundle away and runs the stage again)`
|
|
41404
|
-
]);
|
|
41710
|
+
], [], `${entry.phase.id}/${entry.stage.id} has a --prepare bundle waiting and nothing is holding the run`);
|
|
41405
41711
|
}
|
|
41406
41712
|
function demoteStaleRunning(store, deadPid) {
|
|
41407
41713
|
const stuck = [];
|
|
@@ -41547,7 +41853,8 @@ async function runStage(store, options, phaseId, stageId, spec, notes) {
|
|
|
41547
41853
|
if (options.mode === "prepare" || options.dryRun)
|
|
41548
41854
|
notes.push(...renderLedger(ledger4));
|
|
41549
41855
|
if (ledger4.overLimit) {
|
|
41550
|
-
|
|
41856
|
+
const refusal = renderRefusal(ledger4, stageId);
|
|
41857
|
+
return out(EXIT_REFUSED2, [...notes, ...refusal], [], refusal[0]);
|
|
41551
41858
|
}
|
|
41552
41859
|
const providerAdvisory = providerBudgetAdvisory(agentProvider(), cap2);
|
|
41553
41860
|
const advisories = [
|
|
@@ -41715,7 +42022,7 @@ function saveOverflow(store, options, phaseId, type, field, text3) {
|
|
|
41715
42022
|
const relPath = `${phaseId}/${LOG_DIR}/overflow/${stamp2}-${String(overflowSeq)}-${type}-${field}.txt`;
|
|
41716
42023
|
try {
|
|
41717
42024
|
mkdirSync19(join100(store.runDir, phaseId, LOG_DIR, "overflow"), { recursive: true });
|
|
41718
|
-
|
|
42025
|
+
writeFileSync19(join100(store.runDir, relPath), text3, "utf8");
|
|
41719
42026
|
return `omitted text saved at ${relPath}`;
|
|
41720
42027
|
} catch (error) {
|
|
41721
42028
|
return `could not be saved: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -41804,7 +42111,7 @@ async function preconditionRefusal(store, options, phaseId, stageId, spec, notes
|
|
|
41804
42111
|
`refusing to dispatch ${phaseId}/${stageId} — precondition \`${precondition.id}\` is red.`,
|
|
41805
42112
|
` ${ran.detail}`,
|
|
41806
42113
|
`Fix it and run the same command again: the stage is still \`${stageStatusOf(store, phaseId, stageId)}\` and nothing was spent.`
|
|
41807
|
-
]);
|
|
42114
|
+
], [], `${phaseId}/${stageId}: precondition \`${precondition.id}\` is red — ${ran.detail}`);
|
|
41808
42115
|
}
|
|
41809
42116
|
return null;
|
|
41810
42117
|
}
|
|
@@ -41830,7 +42137,7 @@ function economyRefusal(store, options, phaseId, stageId, notes) {
|
|
|
41830
42137
|
`refusing to spawn — ${phaseId} is priced in \`host-tokens\` ` + `($${ceiling.toFixed(2)} is not dollars a spawn may`,
|
|
41831
42138
|
"spend) and this invocation is headless. Either run it in-session (tldrx next --prepare), or set the",
|
|
41832
42139
|
`phase to \`economy: metered-usd\` and re-price it (tldrx budget raise ${phaseId} <usd>).`
|
|
41833
|
-
]);
|
|
42140
|
+
], [], `${phaseId}/${stageId}: refusing to spawn — ${phaseId} is priced in \`host-tokens\` ($${ceiling.toFixed(2)}) and this invocation is headless`);
|
|
41834
42141
|
}
|
|
41835
42142
|
function budgetRefusal(store, options, phaseId, stageId, spec, notes) {
|
|
41836
42143
|
const stage2 = requireStage(store, phaseId, stageId);
|
|
@@ -41870,7 +42177,7 @@ function budgetRefusal(store, options, phaseId, stageId, spec, notes) {
|
|
|
41870
42177
|
...work.basis === "plan" ? [renderRemainingWork(work), ...remainingWorkContext(work)] : [],
|
|
41871
42178
|
`Run \`${fix}\` (add \`--take-from <phase>\` to move the money instead of adding it), ` + `lower budget_usd in the stage, or set on_exceed: warn.`,
|
|
41872
42179
|
`See the whole picture first: \`tldrx budget show --run ${store.runId}\`.`
|
|
41873
|
-
]);
|
|
42180
|
+
], [], `${phaseId}/${stageId}: phase ${phaseId} has $${phaseRemaining.toFixed(2)} left and the estimate is $${estimate.toFixed(2)}`);
|
|
41874
42181
|
}
|
|
41875
42182
|
warnOnce(store, options, phaseId, stageId, estimate, phaseRemaining, spec.tuning.attempts, notes);
|
|
41876
42183
|
return null;
|
|
@@ -41986,7 +42293,7 @@ async function runExecutor(store, options, phaseId, stageId, spec, notes, execut
|
|
|
41986
42293
|
mapStage2(store, phaseId, stageId, () => stageOnEntry);
|
|
41987
42294
|
store.save();
|
|
41988
42295
|
}
|
|
41989
|
-
return out(EXIT_USAGE3, [...notes, ...outcome.lines]);
|
|
42296
|
+
return out(EXIT_USAGE3, [...notes, ...outcome.lines], [], executorSignature(outcome));
|
|
41990
42297
|
}
|
|
41991
42298
|
claimEpicBranches(store, outcome.epicBranches, outcome.branchModel);
|
|
41992
42299
|
try {
|
|
@@ -42008,7 +42315,7 @@ async function runExecutor(store, options, phaseId, stageId, spec, notes, execut
|
|
|
42008
42315
|
if (outcome.refused === true) {
|
|
42009
42316
|
setStatus(store, phaseId, stageId, "ready");
|
|
42010
42317
|
store.save();
|
|
42011
|
-
return out(EXIT_REFUSED2, [...notes, ...outcome.lines]);
|
|
42318
|
+
return out(EXIT_REFUSED2, [...notes, ...outcome.lines], [], executorSignature(outcome));
|
|
42012
42319
|
}
|
|
42013
42320
|
if (!outcome.ok) {
|
|
42014
42321
|
return failStage(store, options, phaseId, stageId, outcome.error ?? "the executor failed", notes);
|
|
@@ -42140,7 +42447,7 @@ async function commitStage(store, options, phaseId, stageId, spec, notes) {
|
|
|
42140
42447
|
`${phaseId}/questions.md has ${unreadable.length} question(s) the parser cannot read ` + `(${unreadable.join(", ")}) — a heading must be \`## Qn · <title>\` with the ` + "`<!-- id: Qn | status: open | area: … | asked_by: … | asked_at: … -->` line under it.",
|
|
42141
42448
|
'As written they are invisible: the gate would read this file as "0 open" and sign itself.',
|
|
42142
42449
|
`Fix: \`tldrx questions lint --run ${store.runId} --fix\`, then \`tldrx next --commit\` again.`
|
|
42143
|
-
]);
|
|
42450
|
+
], [], `${phaseId}/questions.md has ${String(unreadable.length)} unreadable question(s): ${unreadable.join(", ")}`);
|
|
42144
42451
|
}
|
|
42145
42452
|
return await finishStage(store, options, phaseId, stageId, spec, notes);
|
|
42146
42453
|
}
|
|
@@ -42378,7 +42685,7 @@ function failStage(store, options, phaseId, stageId, reason, notes, tasksBefore)
|
|
|
42378
42685
|
...notes,
|
|
42379
42686
|
`${phaseId}/${stageId} failed: ${oneLine7(reason)}`,
|
|
42380
42687
|
`cost is recorded, not refunded — retry with \`tldrx next\`, or \`tldrx reject --note "…"\``
|
|
42381
|
-
]);
|
|
42688
|
+
], [], `${phaseId}/${stageId} failed: ${oneLine7(reason)}`);
|
|
42382
42689
|
}
|
|
42383
42690
|
function seedInputsOf(spec, stage2, ctx) {
|
|
42384
42691
|
if (!spec.seedInputs)
|
|
@@ -42541,11 +42848,11 @@ function priorOutputs(options) {
|
|
|
42541
42848
|
return [...out, ...blocks];
|
|
42542
42849
|
}
|
|
42543
42850
|
function readStageMd(planned) {
|
|
42544
|
-
return
|
|
42851
|
+
return readFileSync75(stageMdPath(planned.id, planned.source), "utf8");
|
|
42545
42852
|
}
|
|
42546
42853
|
function readOrEmpty4(path) {
|
|
42547
42854
|
try {
|
|
42548
|
-
return
|
|
42855
|
+
return readFileSync75(path, "utf8");
|
|
42549
42856
|
} catch {
|
|
42550
42857
|
return "";
|
|
42551
42858
|
}
|
|
@@ -42754,7 +43061,7 @@ function hostTokensNote(store, options, phaseId, stageId, notes) {
|
|
|
42754
43061
|
...notes,
|
|
42755
43062
|
`[tldrx] budget: refusing to start stage "${stageId}" — phase ${phaseId} is priced in \`host-tokens\` ` + `and has declared ${String(tokens3.spent)} of ${String(tokens3.ceiling)} allowed.`,
|
|
42756
43063
|
"Raise that phase's ceiling in budget.yml (under this economy the number is a TOKEN allowance), " + "or set `on_host_tokens_exceed: warn` to go back to a note."
|
|
42757
|
-
]);
|
|
43064
|
+
], [], `${phaseId}/${stageId}: phase ${phaseId} has declared ${String(tokens3.spent)} of ${String(tokens3.ceiling)} allowed host-tokens`);
|
|
42758
43065
|
}
|
|
42759
43066
|
function declaredTokensIn(run, phaseId) {
|
|
42760
43067
|
let tokens3 = 0;
|
|
@@ -42853,14 +43160,22 @@ function cacheSplit(cacheCreationTokens, cacheReadTokens) {
|
|
|
42853
43160
|
...reported(cacheReadTokens) ? { cache_read_input_tokens: cacheReadTokens } : {}
|
|
42854
43161
|
};
|
|
42855
43162
|
}
|
|
42856
|
-
function
|
|
43163
|
+
function executorSignature(outcome) {
|
|
43164
|
+
return outcome.signature ?? outcome.error ?? undefined;
|
|
43165
|
+
}
|
|
43166
|
+
function out(code, lines, stderr = [], signature) {
|
|
42857
43167
|
const held2 = lines.filter((line) => line.includes(NOT_RESTORED_MARK));
|
|
42858
43168
|
const ordered = held2.length === 0 ? lines : [...lines.filter((line) => !line.includes(NOT_RESTORED_MARK)), ...held2];
|
|
42859
|
-
return
|
|
43169
|
+
return {
|
|
43170
|
+
code,
|
|
43171
|
+
lines: ordered,
|
|
43172
|
+
...stderr.length === 0 ? {} : { stderr },
|
|
43173
|
+
...signature === undefined ? {} : { signature }
|
|
43174
|
+
};
|
|
42860
43175
|
}
|
|
42861
43176
|
|
|
42862
43177
|
// src/core/run/ship.ts
|
|
42863
|
-
import { existsSync as existsSync84, mkdtempSync as mkdtempSync2, readFileSync as
|
|
43178
|
+
import { existsSync as existsSync84, mkdtempSync as mkdtempSync2, readFileSync as readFileSync76, rmSync as rmSync11, writeFileSync as writeFileSync20 } from "node:fs";
|
|
42864
43179
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
42865
43180
|
import { dirname as dirname13, join as join101, resolve as resolve11 } from "node:path";
|
|
42866
43181
|
|
|
@@ -43600,7 +43915,7 @@ function lastHandoff(runDir2, phases) {
|
|
|
43600
43915
|
continue;
|
|
43601
43916
|
let text3;
|
|
43602
43917
|
try {
|
|
43603
|
-
text3 =
|
|
43918
|
+
text3 = readFileSync76(path, "utf8");
|
|
43604
43919
|
} catch {
|
|
43605
43920
|
continue;
|
|
43606
43921
|
}
|
|
@@ -43625,7 +43940,7 @@ function writeShipBody(store, branch, handoff, stories, repoNames) {
|
|
|
43625
43940
|
outcome: describeShipOutcome(store)
|
|
43626
43941
|
});
|
|
43627
43942
|
const path = join101(mkdtempSync2(join101(tmpdir2(), "tldrx-ship-")), "pr-body.md");
|
|
43628
|
-
|
|
43943
|
+
writeFileSync20(path, text3, "utf8");
|
|
43629
43944
|
return { path, bytes: Buffer.byteLength(text3, "utf8"), handoffRel: handoff.rel, open: rows.length };
|
|
43630
43945
|
}
|
|
43631
43946
|
function bodyRecipe(body) {
|
|
@@ -43702,6 +44017,9 @@ function clipForLedger(text3) {
|
|
|
43702
44017
|
const tail2 = text3.slice(-(LAST_LINE_CHARS - CLIP_HEAD_CHARS - 1));
|
|
43703
44018
|
return `${text3.slice(0, CLIP_HEAD_CHARS)}…${tail2}`;
|
|
43704
44019
|
}
|
|
44020
|
+
function comparandOf(signature, lines) {
|
|
44021
|
+
return signature ?? lines[lines.length - 1] ?? "";
|
|
44022
|
+
}
|
|
43705
44023
|
function relaunchVerdict(input) {
|
|
43706
44024
|
const { code } = input;
|
|
43707
44025
|
if (code === EXIT_OK5)
|
|
@@ -43724,14 +44042,14 @@ function relaunchVerdict(input) {
|
|
|
43724
44042
|
reason: `the --until-done ${String(input.of)} bound is spent after ${String(input.attempt)} attempts`
|
|
43725
44043
|
};
|
|
43726
44044
|
}
|
|
43727
|
-
if (input.
|
|
44045
|
+
if (input.previousComparand !== null && input.comparand === input.previousComparand) {
|
|
43728
44046
|
return {
|
|
43729
44047
|
relaunch: false,
|
|
43730
|
-
reason: "its
|
|
44048
|
+
reason: "its refusal is the same as the previous attempt's — a refusal that repeats " + "verbatim is not one a relaunch moves"
|
|
43731
44049
|
};
|
|
43732
44050
|
}
|
|
43733
44051
|
const family = code === EXIT_AGENT_FAILED3 ? "a stage failed past --retry-failed" : code === EXIT_REFUSED3 ? "a refusal with no money behind it" : "the loop ended with exit 1";
|
|
43734
|
-
return { relaunch: true, reason: `${family}: ${input.
|
|
44052
|
+
return { relaunch: true, reason: `${family}: ${input.comparand}` };
|
|
43735
44053
|
}
|
|
43736
44054
|
function budgetBlockedReason(fresh) {
|
|
43737
44055
|
for (let i = fresh.length - 1;i >= 0; i--) {
|
|
@@ -43757,11 +44075,11 @@ async function runAuto(options) {
|
|
|
43757
44075
|
};
|
|
43758
44076
|
let runId = options.runId;
|
|
43759
44077
|
let baseline = null;
|
|
43760
|
-
let
|
|
44078
|
+
let previousComparand = null;
|
|
43761
44079
|
for (let attempt = 1;; attempt++) {
|
|
43762
44080
|
let outcome;
|
|
43763
44081
|
try {
|
|
43764
|
-
outcome = await runAutoOnce({ ...options, runId, onLine: say }, { attempt, of: bound,
|
|
44082
|
+
outcome = await runAutoOnce({ ...options, runId, onLine: say }, { attempt, of: bound, previousComparand, baseline });
|
|
43765
44083
|
} catch (error) {
|
|
43766
44084
|
const line = `run auto threw: ${error instanceof Error ? error.message : String(error)}`;
|
|
43767
44085
|
say(line);
|
|
@@ -43770,10 +44088,11 @@ async function runAuto(options) {
|
|
|
43770
44088
|
runId = outcome.runId ?? runId;
|
|
43771
44089
|
baseline = outcome.baseline ?? baseline;
|
|
43772
44090
|
const lastLine2 = outcome.lines[outcome.lines.length - 1] ?? "";
|
|
44091
|
+
const comparand = comparandOf(outcome.signature, outcome.lines);
|
|
43773
44092
|
const verdict2 = outcome.verdict ?? relaunchVerdict({
|
|
43774
44093
|
code: outcome.code,
|
|
43775
|
-
|
|
43776
|
-
|
|
44094
|
+
comparand,
|
|
44095
|
+
previousComparand,
|
|
43777
44096
|
held: null,
|
|
43778
44097
|
attempt,
|
|
43779
44098
|
of: bound,
|
|
@@ -43799,11 +44118,12 @@ async function runAuto(options) {
|
|
|
43799
44118
|
exit: outcome.code,
|
|
43800
44119
|
attempt,
|
|
43801
44120
|
of: bound,
|
|
43802
|
-
last_line: clipForLedger(lastLine2)
|
|
44121
|
+
last_line: clipForLedger(lastLine2),
|
|
44122
|
+
comparand: clipForLedger(comparand)
|
|
43803
44123
|
}
|
|
43804
44124
|
});
|
|
43805
44125
|
say(`relaunching run auto after exit ${String(outcome.code)} — ${verdict2.reason} ` + `(relaunch ${String(attempt)} of ${String(bound)}, --until-done)`);
|
|
43806
|
-
|
|
44126
|
+
previousComparand = comparand;
|
|
43807
44127
|
}
|
|
43808
44128
|
}
|
|
43809
44129
|
async function runAutoOnce(options, supervision) {
|
|
@@ -43814,16 +44134,17 @@ async function runAutoOnce(options, supervision) {
|
|
|
43814
44134
|
};
|
|
43815
44135
|
let resolved = null;
|
|
43816
44136
|
let baseline = supervision?.baseline ?? null;
|
|
43817
|
-
const leave = (code, held2 = null) => ({
|
|
44137
|
+
const leave = (code, held2 = null, signature) => ({
|
|
43818
44138
|
code,
|
|
43819
44139
|
lines,
|
|
44140
|
+
...signature === undefined ? {} : { signature },
|
|
43820
44141
|
runId: resolved?.runId ?? null,
|
|
43821
44142
|
runDir: resolved?.runDir ?? null,
|
|
43822
44143
|
baseline,
|
|
43823
44144
|
verdict: supervision === undefined ? null : relaunchVerdict({
|
|
43824
44145
|
code,
|
|
43825
|
-
|
|
43826
|
-
|
|
44146
|
+
comparand: comparandOf(signature, lines),
|
|
44147
|
+
previousComparand: supervision.previousComparand,
|
|
43827
44148
|
held: held2,
|
|
43828
44149
|
attempt: supervision.attempt,
|
|
43829
44150
|
of: supervision.of,
|
|
@@ -43894,10 +44215,10 @@ async function runAutoOnce(options, supervision) {
|
|
|
43894
44215
|
await notifier.send(statusNotification(notifyCtx(), text3, stillBlocking(runDir2), gate2, cutInputs(stageIdOf()), gate2 === null ? null : gateStories(runDir2, gate2.phase)), stageIdOf());
|
|
43895
44216
|
})();
|
|
43896
44217
|
}, options.notifyEveryMs);
|
|
43897
|
-
const finish = async (code, spentUsd, held2 = null) => {
|
|
44218
|
+
const finish = async (code, spentUsd, held2 = null, signature) => {
|
|
43898
44219
|
if (heartbeat !== null)
|
|
43899
44220
|
clearInterval(heartbeat);
|
|
43900
|
-
const outcome = leave(code, held2 ?? budgetBlockedReason(readEvents3(log).slice(attemptStart)));
|
|
44221
|
+
const outcome = leave(code, held2 ?? budgetBlockedReason(readEvents3(log).slice(attemptStart)), signature);
|
|
43901
44222
|
if (outcome.verdict?.relaunch === true)
|
|
43902
44223
|
return outcome;
|
|
43903
44224
|
if (notifier !== null) {
|
|
@@ -43919,7 +44240,7 @@ async function runAutoOnce(options, supervision) {
|
|
|
43919
44240
|
};
|
|
43920
44241
|
const heldForeignWork = () => {
|
|
43921
44242
|
try {
|
|
43922
|
-
return notRestoredSummary(
|
|
44243
|
+
return notRestoredSummary(readFileSync77(join102(runDir2, "events.jsonl"), "utf8"));
|
|
43923
44244
|
} catch {
|
|
43924
44245
|
return null;
|
|
43925
44246
|
}
|
|
@@ -44142,7 +44463,7 @@ async function runAutoOnce(options, supervision) {
|
|
|
44142
44463
|
if (outcome.code === EXIT_AGENT_FAILED3 && retryBound > 0) {
|
|
44143
44464
|
say(`stopped: ${String(consecutiveFailures)} consecutive stage failures at ${cursorBefore} ` + `— the --retry-failed ${String(retryBound)} bound is spent, and the exit code is still ` + "5, the failure's own");
|
|
44144
44465
|
}
|
|
44145
|
-
return await finish(outcome.code, spentByLoop);
|
|
44466
|
+
return await finish(outcome.code, spentByLoop, null, outcome.signature);
|
|
44146
44467
|
}
|
|
44147
44468
|
const after = RunStore.open(runDir2);
|
|
44148
44469
|
if (fresh.length === 0 && `${after.run.cursor.phase}/${after.run.cursor.stage}` === cursorBefore) {
|
|
@@ -44491,7 +44812,7 @@ function event5(options, runId, payload2) {
|
|
|
44491
44812
|
return { ts: options.at, run: runId, stage: null, type: "run.attended", actor: options.actor, cost_usd: 0, payload: payload2 };
|
|
44492
44813
|
}
|
|
44493
44814
|
// src/core/status/initQuestionsItem.ts
|
|
44494
|
-
import { existsSync as existsSync85, readFileSync as
|
|
44815
|
+
import { existsSync as existsSync85, readFileSync as readFileSync78 } from "node:fs";
|
|
44495
44816
|
import { join as join103 } from "node:path";
|
|
44496
44817
|
var MAX_LISTED = 5;
|
|
44497
44818
|
function initQuestionsItem(root2) {
|
|
@@ -44500,7 +44821,7 @@ function initQuestionsItem(root2) {
|
|
|
44500
44821
|
return null;
|
|
44501
44822
|
let open;
|
|
44502
44823
|
try {
|
|
44503
|
-
open = openBlocks(parseQuestions(
|
|
44824
|
+
open = openBlocks(parseQuestions(readFileSync78(path, "utf8")).blocks);
|
|
44504
44825
|
} catch {
|
|
44505
44826
|
return null;
|
|
44506
44827
|
}
|
|
@@ -44520,7 +44841,7 @@ function initQuestionsItem(root2) {
|
|
|
44520
44841
|
}
|
|
44521
44842
|
|
|
44522
44843
|
// src/core/status/seedSplitItems.ts
|
|
44523
|
-
import { closeSync as closeSync2, existsSync as existsSync87, openSync as openSync2, readSync, readdirSync as readdirSync27, readFileSync as
|
|
44844
|
+
import { closeSync as closeSync2, existsSync as existsSync87, openSync as openSync2, readSync, readdirSync as readdirSync27, readFileSync as readFileSync79, statSync as statSync23 } from "node:fs";
|
|
44524
44845
|
import { basename as basename14, join as join105, relative as relative19 } from "node:path";
|
|
44525
44846
|
|
|
44526
44847
|
// src/core/seed/triageInventory.ts
|
|
@@ -44720,7 +45041,7 @@ function proposedSplits(root2) {
|
|
|
44720
45041
|
try {
|
|
44721
45042
|
if (!statSync23(dir).isDirectory() || !existsSync87(path))
|
|
44722
45043
|
continue;
|
|
44723
|
-
const file = readSplitFile(parseYaml(
|
|
45044
|
+
const file = readSplitFile(parseYaml(readFileSync79(path, "utf8")));
|
|
44724
45045
|
if (file.status !== "proposed" && file.status !== "applying")
|
|
44725
45046
|
continue;
|
|
44726
45047
|
found.push({ path, rel: toRel(root2, path), dir, file });
|
|
@@ -44788,7 +45109,7 @@ function seedDocuments2(dir, file) {
|
|
|
44788
45109
|
const inventory = join105(dir, "inventory.json");
|
|
44789
45110
|
if (existsSync87(inventory)) {
|
|
44790
45111
|
try {
|
|
44791
|
-
const doc = JSON.parse(
|
|
45112
|
+
const doc = JSON.parse(readFileSync79(inventory, "utf8"));
|
|
44792
45113
|
const rels2 = (doc.documents ?? []).map((row2) => row2.rel).filter((rel) => typeof rel === "string");
|
|
44793
45114
|
if (rels2.length > 0)
|
|
44794
45115
|
return rels2;
|
|
@@ -44824,7 +45145,7 @@ function cachedStatuses(dir) {
|
|
|
44824
45145
|
if (!existsSync87(inventory))
|
|
44825
45146
|
return out2;
|
|
44826
45147
|
try {
|
|
44827
|
-
const doc = JSON.parse(
|
|
45148
|
+
const doc = JSON.parse(readFileSync79(inventory, "utf8"));
|
|
44828
45149
|
for (const row2 of doc.documents ?? []) {
|
|
44829
45150
|
if (typeof row2.rel === "string" && typeof row2.adrStatus === "string")
|
|
44830
45151
|
out2.set(row2.rel, row2.adrStatus);
|
|
@@ -45141,7 +45462,7 @@ function resolveWorkspaceRoot(explicit, start = process.cwd()) {
|
|
|
45141
45462
|
return findWorkspaceRoot2(start) ?? resolve12(start);
|
|
45142
45463
|
}
|
|
45143
45464
|
// src/core/experts/stageCoverage.ts
|
|
45144
|
-
import { existsSync as existsSync89, readdirSync as readdirSync28, readFileSync as
|
|
45465
|
+
import { existsSync as existsSync89, readdirSync as readdirSync28, readFileSync as readFileSync80, statSync as statSync24 } from "node:fs";
|
|
45145
45466
|
import { join as join107 } from "node:path";
|
|
45146
45467
|
function stagesLoadingExperts(root2) {
|
|
45147
45468
|
const repos = workspaceRepos(root2);
|
|
@@ -45201,7 +45522,7 @@ function readStage(root2, id) {
|
|
|
45201
45522
|
if (!existsSync89(path))
|
|
45202
45523
|
return null;
|
|
45203
45524
|
try {
|
|
45204
|
-
const doc = parseYaml(
|
|
45525
|
+
const doc = parseYaml(readFileSync80(path, "utf8"));
|
|
45205
45526
|
return isRecord(doc) ? doc : null;
|
|
45206
45527
|
} catch {
|
|
45207
45528
|
return null;
|
|
@@ -45212,7 +45533,7 @@ function workspaceRepos(root2) {
|
|
|
45212
45533
|
if (!existsSync89(path))
|
|
45213
45534
|
return [];
|
|
45214
45535
|
try {
|
|
45215
|
-
const doc = parseYaml(
|
|
45536
|
+
const doc = parseYaml(readFileSync80(path, "utf8"));
|
|
45216
45537
|
const list = isRecord(doc) && Array.isArray(doc.repos) ? doc.repos : [];
|
|
45217
45538
|
return list.map((row2) => isRecord(row2) && typeof row2.name === "string" ? row2.name : "").filter((name) => name !== "");
|
|
45218
45539
|
} catch {
|
|
@@ -45305,7 +45626,7 @@ function expertListJson(experts, loads) {
|
|
|
45305
45626
|
})), null, 2);
|
|
45306
45627
|
}
|
|
45307
45628
|
// src/core/experts/createExpert.ts
|
|
45308
|
-
import { mkdirSync as mkdirSync20, existsSync as existsSync90, writeFileSync as
|
|
45629
|
+
import { mkdirSync as mkdirSync20, existsSync as existsSync90, writeFileSync as writeFileSync21 } from "node:fs";
|
|
45309
45630
|
import { join as join108 } from "node:path";
|
|
45310
45631
|
var EXPERT_NAME_RE = /^[a-z0-9-]{1,32}$/;
|
|
45311
45632
|
function planAreas(options) {
|
|
@@ -45362,8 +45683,8 @@ async function createExpert(options) {
|
|
|
45362
45683
|
repos
|
|
45363
45684
|
});
|
|
45364
45685
|
mkdirSync20(dir, { recursive: true });
|
|
45365
|
-
|
|
45366
|
-
|
|
45686
|
+
writeFileSync21(expertPath, body, "utf8");
|
|
45687
|
+
writeFileSync21(competenciesPath, "# Written by `tldrx expert create` (spec §2.6). `level` is computed, never hand-set.\n" + stringifyYaml(buildCompetenciesDocument(options.name, areas)), "utf8");
|
|
45367
45688
|
if (kind === "role")
|
|
45368
45689
|
mkdirSync20(join108(dir, KNOWLEDGE_DIRNAME), { recursive: true });
|
|
45369
45690
|
return {
|
|
@@ -45562,7 +45883,7 @@ function sharedCitationWarnings(shared) {
|
|
|
45562
45883
|
return shared.map((row2) => `warning: shared citation ${row2.src} by ${row2.experts.join(",")} — check for contradiction`);
|
|
45563
45884
|
}
|
|
45564
45885
|
// src/core/training/mineRuns.ts
|
|
45565
|
-
import { existsSync as existsSync91, readFileSync as
|
|
45886
|
+
import { existsSync as existsSync91, readFileSync as readFileSync81, readdirSync as readdirSync29, statSync as statSync25 } from "node:fs";
|
|
45566
45887
|
import { join as join110, relative as relative20 } from "node:path";
|
|
45567
45888
|
var MAX_RUN_FILES = 24;
|
|
45568
45889
|
var MAX_RUN_BYTES = 64 * 1024;
|
|
@@ -45599,7 +45920,7 @@ function mineRuns(options) {
|
|
|
45599
45920
|
}
|
|
45600
45921
|
let text3;
|
|
45601
45922
|
try {
|
|
45602
|
-
text3 =
|
|
45923
|
+
text3 = readFileSync81(candidate.abs, "utf8");
|
|
45603
45924
|
} catch {
|
|
45604
45925
|
notRead.push(candidate.path);
|
|
45605
45926
|
continue;
|
|
@@ -45663,7 +45984,7 @@ function runRepos(path) {
|
|
|
45663
45984
|
if (!existsSync91(path))
|
|
45664
45985
|
return [];
|
|
45665
45986
|
try {
|
|
45666
|
-
const doc = parseYaml(
|
|
45987
|
+
const doc = parseYaml(readFileSync81(path, "utf8"));
|
|
45667
45988
|
const repos = doc?.repos;
|
|
45668
45989
|
if (!Array.isArray(repos))
|
|
45669
45990
|
return [];
|
|
@@ -46383,7 +46704,7 @@ function estimateNextStage(root2, runId) {
|
|
|
46383
46704
|
function runRemainingOf(store, phaseId, stageId, work) {
|
|
46384
46705
|
const stages = [];
|
|
46385
46706
|
let staticUsd = 0;
|
|
46386
|
-
let
|
|
46707
|
+
let usd3 = 0;
|
|
46387
46708
|
let done = 0;
|
|
46388
46709
|
for (const entry of flatten(store.run)) {
|
|
46389
46710
|
if (isTerminal(entry.stage.status)) {
|
|
@@ -46393,9 +46714,9 @@ function runRemainingOf(store, phaseId, stageId, work) {
|
|
|
46393
46714
|
stages.push(`${entry.phase.id}/${entry.stage.id}`);
|
|
46394
46715
|
staticUsd += entry.stage.budget_usd;
|
|
46395
46716
|
const isCursor = entry.phase.id === phaseId && entry.stage.id === stageId;
|
|
46396
|
-
|
|
46717
|
+
usd3 += isCursor && work.basis === "plan" ? work.usd : entry.stage.budget_usd;
|
|
46397
46718
|
}
|
|
46398
|
-
return { stages, done, staticUsd: round10(staticUsd), usd: round10(
|
|
46719
|
+
return { stages, done, staticUsd: round10(staticUsd), usd: round10(usd3) };
|
|
46399
46720
|
}
|
|
46400
46721
|
function renderEstimate(estimate) {
|
|
46401
46722
|
const lines = [
|
|
@@ -46901,7 +47222,7 @@ var statusCommand = {
|
|
|
46901
47222
|
};
|
|
46902
47223
|
|
|
46903
47224
|
// src/core/seed/runTriage.ts
|
|
46904
|
-
import { mkdirSync as mkdirSync21, readFileSync as
|
|
47225
|
+
import { mkdirSync as mkdirSync21, readFileSync as readFileSync82, writeFileSync as writeFileSync22, existsSync as existsSync93 } from "node:fs";
|
|
46905
47226
|
import { isAbsolute as isAbsolute10, join as join111, relative as relative21, resolve as resolve15 } from "node:path";
|
|
46906
47227
|
|
|
46907
47228
|
// src/core/seed/renderInventory.ts
|
|
@@ -47161,8 +47482,8 @@ async function runTriage(options) {
|
|
|
47161
47482
|
});
|
|
47162
47483
|
const outDir = resolveOut(options);
|
|
47163
47484
|
mkdirSync21(outDir, { recursive: true });
|
|
47164
|
-
|
|
47165
|
-
|
|
47485
|
+
writeFileSync22(join111(outDir, INVENTORY_MD), renderInventory(inventory, options.seedPath, options.at), "utf8");
|
|
47486
|
+
writeFileSync22(join111(outDir, INVENTORY_JSON), inventoryJson(inventory, options.seedPath, options.at), "utf8");
|
|
47166
47487
|
if (options.propose !== true) {
|
|
47167
47488
|
const where = display(options.root, outDir);
|
|
47168
47489
|
const lines = options.json === true ? [inventoryJson(inventory, options.seedPath, options.at).replace(/\n$/, "")] : [
|
|
@@ -47318,8 +47639,8 @@ async function propose(options, workspaceCommands, inventory, seed, outDir) {
|
|
|
47318
47639
|
created_at: options.at,
|
|
47319
47640
|
...validation.proposal
|
|
47320
47641
|
};
|
|
47321
|
-
|
|
47322
|
-
|
|
47642
|
+
writeFileSync22(join111(outDir, SPLIT_YML), emitSplitYaml(file), "utf8");
|
|
47643
|
+
writeFileSync22(join111(outDir, SPLIT_MD), renderSplitMarkdown(file, where), "utf8");
|
|
47323
47644
|
const over = metered && costUsd > ceiling + 0.000000001;
|
|
47324
47645
|
return {
|
|
47325
47646
|
code: EXIT_OK9,
|
|
@@ -47344,7 +47665,7 @@ function readTriageResult(outDir) {
|
|
|
47344
47665
|
}
|
|
47345
47666
|
let doc;
|
|
47346
47667
|
try {
|
|
47347
|
-
doc = JSON.parse(
|
|
47668
|
+
doc = JSON.parse(readFileSync82(path, "utf8"));
|
|
47348
47669
|
} catch (error) {
|
|
47349
47670
|
throw new PendingError(`${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
47350
47671
|
}
|
|
@@ -47385,7 +47706,7 @@ function round28(n) {
|
|
|
47385
47706
|
}
|
|
47386
47707
|
|
|
47387
47708
|
// src/core/seed/applySplit.ts
|
|
47388
|
-
import { existsSync as existsSync94, readFileSync as
|
|
47709
|
+
import { existsSync as existsSync94, readFileSync as readFileSync83, writeFileSync as writeFileSync23 } from "node:fs";
|
|
47389
47710
|
import { dirname as dirname15, isAbsolute as isAbsolute11, join as join112, relative as relative22, resolve as resolve16 } from "node:path";
|
|
47390
47711
|
var EXIT_OK10 = 0;
|
|
47391
47712
|
var EXIT_USAGE7 = 1;
|
|
@@ -47397,7 +47718,7 @@ function applySplit(options) {
|
|
|
47397
47718
|
}
|
|
47398
47719
|
let file;
|
|
47399
47720
|
try {
|
|
47400
|
-
file = readSplitFile(parseYaml(
|
|
47721
|
+
file = readSplitFile(parseYaml(readFileSync83(path, "utf8")));
|
|
47401
47722
|
} catch (error) {
|
|
47402
47723
|
return fail2(EXIT_USAGE7, [error instanceof SplitError ? error.message : message5(error)]);
|
|
47403
47724
|
}
|
|
@@ -47438,7 +47759,7 @@ function applySplit(options) {
|
|
|
47438
47759
|
}
|
|
47439
47760
|
const created = [];
|
|
47440
47761
|
const stamp2 = (status) => {
|
|
47441
|
-
|
|
47762
|
+
writeFileSync23(path, emitSplitYaml({
|
|
47442
47763
|
...file,
|
|
47443
47764
|
...validation.proposal,
|
|
47444
47765
|
status,
|
|
@@ -47483,11 +47804,11 @@ function applySplit(options) {
|
|
|
47483
47804
|
applied_at: rfc33393(options.now),
|
|
47484
47805
|
created_runs: created
|
|
47485
47806
|
};
|
|
47486
|
-
|
|
47807
|
+
writeFileSync23(path, emitSplitYaml(applied), "utf8");
|
|
47487
47808
|
const outRel = reference(options.root, dirname15(path));
|
|
47488
47809
|
const markdown = join112(dirname15(path), SPLIT_MD);
|
|
47489
47810
|
if (existsSync94(markdown))
|
|
47490
|
-
|
|
47811
|
+
writeFileSync23(markdown, renderSplitMarkdown(applied, outRel), "utf8");
|
|
47491
47812
|
return {
|
|
47492
47813
|
code: EXIT_OK10,
|
|
47493
47814
|
created,
|
|
@@ -47522,7 +47843,7 @@ function splitUniverse(splitPath, file) {
|
|
|
47522
47843
|
const inventory = join112(dirname15(splitPath), "inventory.json");
|
|
47523
47844
|
if (existsSync94(inventory)) {
|
|
47524
47845
|
try {
|
|
47525
|
-
const doc = JSON.parse(
|
|
47846
|
+
const doc = JSON.parse(readFileSync83(inventory, "utf8"));
|
|
47526
47847
|
const rels2 = (doc.documents ?? []).map((row2) => row2.rel).filter((rel) => typeof rel === "string");
|
|
47527
47848
|
if (rels2.length > 0)
|
|
47528
47849
|
return new Set(rels2);
|
|
@@ -47565,7 +47886,7 @@ function message5(error) {
|
|
|
47565
47886
|
}
|
|
47566
47887
|
|
|
47567
47888
|
// src/core/seed/answerSplitQuestion.ts
|
|
47568
|
-
import { existsSync as existsSync95, readFileSync as
|
|
47889
|
+
import { existsSync as existsSync95, readFileSync as readFileSync84, writeFileSync as writeFileSync24 } from "node:fs";
|
|
47569
47890
|
import { dirname as dirname16, isAbsolute as isAbsolute12, join as join113, relative as relative23, resolve as resolve17 } from "node:path";
|
|
47570
47891
|
var EXIT_OK11 = 0;
|
|
47571
47892
|
var EXIT_USAGE8 = 1;
|
|
@@ -47579,7 +47900,7 @@ function answerSplitQuestion(options) {
|
|
|
47579
47900
|
return { code: EXIT_NOT_FOUND9, lines: [`no such file: ${options.splitPath}`] };
|
|
47580
47901
|
let file;
|
|
47581
47902
|
try {
|
|
47582
|
-
file = readSplitFile(parseYaml(
|
|
47903
|
+
file = readSplitFile(parseYaml(readFileSync84(path, "utf8")));
|
|
47583
47904
|
} catch (error) {
|
|
47584
47905
|
return { code: EXIT_USAGE8, lines: [error instanceof SplitError ? error.message : message6(error)] };
|
|
47585
47906
|
}
|
|
@@ -47613,10 +47934,10 @@ function answerSplitQuestion(options) {
|
|
|
47613
47934
|
...validation.proposal,
|
|
47614
47935
|
questions: questions.map((question) => question.id === options.id ? { ...question, answer: trimmed } : question)
|
|
47615
47936
|
};
|
|
47616
|
-
|
|
47937
|
+
writeFileSync24(path, emitSplitYaml(answered), "utf8");
|
|
47617
47938
|
const markdown = join113(dirname16(path), SPLIT_MD);
|
|
47618
47939
|
if (existsSync95(markdown)) {
|
|
47619
|
-
|
|
47940
|
+
writeFileSync24(markdown, renderSplitMarkdown(answered, reference2(options.root, dirname16(path))), "utf8");
|
|
47620
47941
|
}
|
|
47621
47942
|
const open = answered.questions.filter((question) => question.answer === undefined).length;
|
|
47622
47943
|
return {
|
|
@@ -47637,7 +47958,7 @@ function message6(error) {
|
|
|
47637
47958
|
}
|
|
47638
47959
|
|
|
47639
47960
|
// src/core/seed/checkSeed.ts
|
|
47640
|
-
import { mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync3, rmSync as rmSync12, writeFileSync as
|
|
47961
|
+
import { mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync3, rmSync as rmSync12, writeFileSync as writeFileSync25 } from "node:fs";
|
|
47641
47962
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
47642
47963
|
import { join as join114 } from "node:path";
|
|
47643
47964
|
var MAX_SEED_BULLET_CHARS = 200;
|
|
@@ -47983,7 +48304,7 @@ function importerVerdict(seed, preset, workspace) {
|
|
|
47983
48304
|
const headings2 = allSeedHeadings(seed.documents);
|
|
47984
48305
|
const indexPath = join114(temp, ...phase.split("/"), SEED_INDEX);
|
|
47985
48306
|
mkdirSync22(join114(temp, phase), { recursive: true });
|
|
47986
|
-
|
|
48307
|
+
writeFileSync25(indexPath, renderSeedIndex("seed-check", seed, phase), "utf8");
|
|
47987
48308
|
const handoff = renderSeedHandoff({
|
|
47988
48309
|
runId: "seed-check",
|
|
47989
48310
|
stageId: stage2?.id ?? "what",
|
|
@@ -48024,7 +48345,7 @@ function budgetView(preset, ceiling, storyCount) {
|
|
|
48024
48345
|
storyCount: Math.max(storyCount, 1),
|
|
48025
48346
|
budgetUsd,
|
|
48026
48347
|
maxBudgetUsd: Math.min(budgetUsd, plan.perAgentMax),
|
|
48027
|
-
agentCap: (share = 1) =>
|
|
48348
|
+
agentCap: (share = 1) => round23(budgetUsd * share),
|
|
48028
48349
|
attempts: buildStage.attempts
|
|
48029
48350
|
};
|
|
48030
48351
|
build = {
|
|
@@ -48056,7 +48377,7 @@ function renderSeedCheck(report3) {
|
|
|
48056
48377
|
lines.push(` ${stage2.id.padEnd(7)} ${`$${stage2.perAttemptUsd.toFixed(2)}`.padStart(8)} per attempt` + ` (${stage2.phase} holds $${stage2.phaseUsd.toFixed(2)} for ${String(stage2.attempts)} attempt(s))`);
|
|
48057
48378
|
}
|
|
48058
48379
|
if (b.build !== null) {
|
|
48059
|
-
lines.push(` per story, ${String(b.build.storyCount)} story(ies), no plan price yet: developer cap $${b.build.developerUsd.toFixed(2)}` + ` per attempt, reviewer cap $${b.build.reviewerUsd.toFixed(2)} (#244/#289 — a priced story gets` + " max(price x 3, $4.00) at dispatch; raise --budget if the cap does not cover the largest story)");
|
|
48380
|
+
lines.push(` per story, ${String(b.build.storyCount)} story(ies), no plan price yet: developer cap $${b.build.developerUsd.toFixed(2)}` + ` per attempt, reviewer cap $${b.build.reviewerUsd.toFixed(2)} (#244/#289 — a priced story gets` + " max(price x scale x 3, $4.00) at dispatch, scale being 1 only while the plan's prices sum inside" + " the stage (#281); raise --budget if the cap does not cover the largest story)");
|
|
48060
48381
|
}
|
|
48061
48382
|
}
|
|
48062
48383
|
const stories = report3.stories.length;
|
|
@@ -48282,7 +48603,7 @@ function scopeRepos(names, repoNames) {
|
|
|
48282
48603
|
}
|
|
48283
48604
|
|
|
48284
48605
|
// src/cli/commands/answer.ts
|
|
48285
|
-
import { readFileSync as
|
|
48606
|
+
import { readFileSync as readFileSync85 } from "node:fs";
|
|
48286
48607
|
var QUESTION_ID_RE = /^Q\d{1,6}$/;
|
|
48287
48608
|
var answerCommand2 = {
|
|
48288
48609
|
name: "answer",
|
|
@@ -48397,7 +48718,7 @@ function locateQuestion(store, qid, status) {
|
|
|
48397
48718
|
const path = join115(store.runDir, phase.id, "questions.md");
|
|
48398
48719
|
if (!existsSync96(path))
|
|
48399
48720
|
continue;
|
|
48400
|
-
const block2 = parseQuestions(
|
|
48721
|
+
const block2 = parseQuestions(readFileSync85(path, "utf8")).blocks.find((b) => b.id === qid);
|
|
48401
48722
|
if (block2 !== undefined && block2.metadata?.status === status)
|
|
48402
48723
|
return { path, block: block2 };
|
|
48403
48724
|
}
|
|
@@ -48405,7 +48726,7 @@ function locateQuestion(store, qid, status) {
|
|
|
48405
48726
|
}
|
|
48406
48727
|
|
|
48407
48728
|
// src/cli/commands/approve.ts
|
|
48408
|
-
import { existsSync as existsSync97, readFileSync as
|
|
48729
|
+
import { existsSync as existsSync97, readFileSync as readFileSync86 } from "node:fs";
|
|
48409
48730
|
import { relative as relative24 } from "node:path";
|
|
48410
48731
|
var approveCommand2 = {
|
|
48411
48732
|
name: "approve",
|
|
@@ -48490,7 +48811,7 @@ function readAgentEvidence(root2, store, override) {
|
|
|
48490
48811
|
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");
|
|
48491
48812
|
return { kind: "stop", exit: EXIT_GATE_REFUSED };
|
|
48492
48813
|
}
|
|
48493
|
-
const text3 =
|
|
48814
|
+
const text3 = readFileSync86(path, "utf8");
|
|
48494
48815
|
const gate2 = `${entry.phase.id}/${entry.stage.id}`;
|
|
48495
48816
|
const validation = validateEvidence(text3, toSrcContext(loadWorkspace(root2), store.runDir, { epicRefs: true }), { gate: gate2 });
|
|
48496
48817
|
const refusals = validation.issues.filter((issue) => issue.kind === "verdict");
|
|
@@ -48750,7 +49071,7 @@ function budgetRaise(argv) {
|
|
|
48750
49071
|
...phase,
|
|
48751
49072
|
stages: phase.stages.map((stage2) => stage2.id !== stageId ? stage2 : {
|
|
48752
49073
|
...stage2,
|
|
48753
|
-
budget_usd:
|
|
49074
|
+
budget_usd: round23(stage2.budget_usd + amountUsd)
|
|
48754
49075
|
})
|
|
48755
49076
|
})
|
|
48756
49077
|
}));
|
|
@@ -48773,7 +49094,7 @@ function budgetRaise(argv) {
|
|
|
48773
49094
|
...stageId === null || stageBefore === null ? {} : {
|
|
48774
49095
|
stage: stageId,
|
|
48775
49096
|
stage_budget_before: stageBefore,
|
|
48776
|
-
stage_budget_after:
|
|
49097
|
+
stage_budget_after: round23(stageBefore + amountUsd)
|
|
48777
49098
|
},
|
|
48778
49099
|
note: stringFlag(args, "note") ?? ""
|
|
48779
49100
|
}
|
|
@@ -48899,7 +49220,7 @@ function stageRaiseLines(phaseId, stageId, before, amountUsd) {
|
|
|
48899
49220
|
];
|
|
48900
49221
|
}
|
|
48901
49222
|
return [
|
|
48902
|
-
`${phaseId}/${stageId} budget_usd $${before.toFixed(2)} → $${
|
|
49223
|
+
`${phaseId}/${stageId} budget_usd $${before.toFixed(2)} → $${round23(before + amountUsd).toFixed(2)} ` + `(+$${amountUsd.toFixed(2)}) — every per-story and reviewer cap is derived from this figure.`
|
|
48903
49224
|
];
|
|
48904
49225
|
}
|
|
48905
49226
|
|
|
@@ -49062,7 +49383,7 @@ function resolveIncludingFinished(root2, wanted) {
|
|
|
49062
49383
|
}
|
|
49063
49384
|
|
|
49064
49385
|
// src/cli/commands/gate.ts
|
|
49065
|
-
import { existsSync as existsSync98, mkdirSync as mkdirSync23, writeFileSync as
|
|
49386
|
+
import { existsSync as existsSync98, mkdirSync as mkdirSync23, writeFileSync as writeFileSync26 } from "node:fs";
|
|
49066
49387
|
import { dirname as dirname17, relative as relative25 } from "node:path";
|
|
49067
49388
|
var VALUE_FLAGS5 = ["run", "root"];
|
|
49068
49389
|
var gateCommand = {
|
|
@@ -49111,7 +49432,7 @@ function template(argv) {
|
|
|
49111
49432
|
touchesAudited: countDeclaredTouches(ctx)
|
|
49112
49433
|
};
|
|
49113
49434
|
mkdirSync23(dirname17(path), { recursive: true });
|
|
49114
|
-
|
|
49435
|
+
writeFileSync26(path, renderEvidenceTemplate(input), "utf8");
|
|
49115
49436
|
const lines = describeEvidenceTemplate(relative25(root2, path), input);
|
|
49116
49437
|
process.stdout.write(`${lines.join(`
|
|
49117
49438
|
`)}
|
|
@@ -49123,7 +49444,7 @@ function template(argv) {
|
|
|
49123
49444
|
}
|
|
49124
49445
|
|
|
49125
49446
|
// src/core/run/reopenStory.ts
|
|
49126
|
-
import { existsSync as existsSync99, readFileSync as
|
|
49447
|
+
import { existsSync as existsSync99, readFileSync as readFileSync87, writeFileSync as writeFileSync27 } from "node:fs";
|
|
49127
49448
|
import { join as join116 } from "node:path";
|
|
49128
49449
|
var EXIT_OK12 = 0;
|
|
49129
49450
|
var EXIT_REFUSED8 = 2;
|
|
@@ -49228,7 +49549,7 @@ function reopenStory(options) {
|
|
|
49228
49549
|
}
|
|
49229
49550
|
let patched;
|
|
49230
49551
|
try {
|
|
49231
|
-
const text3 =
|
|
49552
|
+
const text3 = readFileSync87(path, "utf8");
|
|
49232
49553
|
patched = progress.implicit ? updateImplicitPlan(text3, { status: REOPENED_TO }) : updateStoryFront(text3, { status: REOPENED_TO });
|
|
49233
49554
|
} catch (error) {
|
|
49234
49555
|
if (error instanceof StoryWriteError) {
|
|
@@ -49245,7 +49566,7 @@ function reopenStory(options) {
|
|
|
49245
49566
|
" nothing was written — the note is the only free text in it, so it is almost certainly too long"
|
|
49246
49567
|
]);
|
|
49247
49568
|
}
|
|
49248
|
-
|
|
49569
|
+
writeFileSync27(path, patched, "utf8");
|
|
49249
49570
|
store.append(event7);
|
|
49250
49571
|
const kept = [
|
|
49251
49572
|
" its branch is kept, so the next developer starts from the commits the last one made " + "(the worktree is reopened from that branch if the build had removed it)",
|
|
@@ -49275,6 +49596,7 @@ function reopenStory(options) {
|
|
|
49275
49596
|
` signed by ${options.actor}: ${options.note}`,
|
|
49276
49597
|
" no developer will be spawned: the next Build turn runs the dod, the review and the merge " + "over the commits already on the story branch",
|
|
49277
49598
|
" it is not a shortcut past either gate — a red dod BLOCKS the story, and so does a branch " + "that carries no commit its epic has not already got; the refusal says which",
|
|
49599
|
+
" one named exception: work that is ALREADY on the epic with a review that never completed is " + "settled review-only — nothing merged, the dod on the epic head, the reviewer over the recorded range",
|
|
49278
49600
|
" the record will say the branch was taken as it stands and name you, so nothing reads as " + "though a developer delivered it",
|
|
49279
49601
|
...kept
|
|
49280
49602
|
]
|
|
@@ -49330,7 +49652,7 @@ function refuse4(lines) {
|
|
|
49330
49652
|
}
|
|
49331
49653
|
|
|
49332
49654
|
// src/core/run/widenStory.ts
|
|
49333
|
-
import { existsSync as existsSync100, readFileSync as
|
|
49655
|
+
import { existsSync as existsSync100, readFileSync as readFileSync88, writeFileSync as writeFileSync28 } from "node:fs";
|
|
49334
49656
|
import { join as join117 } from "node:path";
|
|
49335
49657
|
var EXIT_OK13 = 0;
|
|
49336
49658
|
var EXIT_REFUSED9 = 2;
|
|
@@ -49410,7 +49732,7 @@ function widenStory(options) {
|
|
|
49410
49732
|
if (!existsSync100(path)) {
|
|
49411
49733
|
return refuse5([`${id} is \`${row2.status}\` but ${path} is not on disk — refusing to write a file that is not there`]);
|
|
49412
49734
|
}
|
|
49413
|
-
const text3 =
|
|
49735
|
+
const text3 = readFileSync88(path, "utf8");
|
|
49414
49736
|
const before = touchesIn(text3, progress.implicit);
|
|
49415
49737
|
if (before === null) {
|
|
49416
49738
|
return refuse5([
|
|
@@ -49466,7 +49788,7 @@ function widenStory(options) {
|
|
|
49466
49788
|
" nothing was written — the note is the only free text in it, so it is almost certainly too long"
|
|
49467
49789
|
]);
|
|
49468
49790
|
}
|
|
49469
|
-
|
|
49791
|
+
writeFileSync28(path, patched, "utf8");
|
|
49470
49792
|
store.append(event7);
|
|
49471
49793
|
return {
|
|
49472
49794
|
code: EXIT_OK13,
|
|
@@ -49790,11 +50112,11 @@ var shipCommand = {
|
|
|
49790
50112
|
import { join as join120 } from "node:path";
|
|
49791
50113
|
|
|
49792
50114
|
// src/core/plan/syncDod.ts
|
|
49793
|
-
import { existsSync as existsSync102, readdirSync as readdirSync30, readFileSync as
|
|
50115
|
+
import { existsSync as existsSync102, readdirSync as readdirSync30, readFileSync as readFileSync90 } from "node:fs";
|
|
49794
50116
|
import { join as join119 } from "node:path";
|
|
49795
50117
|
|
|
49796
50118
|
// src/core/plan/workspaceHistory.ts
|
|
49797
|
-
import { existsSync as existsSync101, readFileSync as
|
|
50119
|
+
import { existsSync as existsSync101, readFileSync as readFileSync89, realpathSync } from "node:fs";
|
|
49798
50120
|
import { join as join118, relative as relative26, sep as sep10 } from "node:path";
|
|
49799
50121
|
var WORKSPACE_REL = `${PROJECT_FRAMEWORK_DIR}/workspace.yml`;
|
|
49800
50122
|
var MAX_VERSIONS = 50;
|
|
@@ -49802,7 +50124,7 @@ async function readWorkspaceHistory(root2) {
|
|
|
49802
50124
|
const documents = [];
|
|
49803
50125
|
const live2 = join118(root2, WORKSPACE_REL);
|
|
49804
50126
|
if (existsSync101(live2)) {
|
|
49805
|
-
const parsed = parse(
|
|
50127
|
+
const parsed = parse(readFileSync89(live2, "utf8"));
|
|
49806
50128
|
if (parsed !== null)
|
|
49807
50129
|
documents.push(parsed);
|
|
49808
50130
|
}
|
|
@@ -49892,7 +50214,7 @@ function planSyncDod(input) {
|
|
|
49892
50214
|
};
|
|
49893
50215
|
}
|
|
49894
50216
|
function syncOne(path, name, input) {
|
|
49895
|
-
const text3 =
|
|
50217
|
+
const text3 = readFileSync90(path, "utf8");
|
|
49896
50218
|
const id = name.replace(/\.md$/, "");
|
|
49897
50219
|
const rel = `${STORIES_DIR}/${name}`;
|
|
49898
50220
|
const repo = readStory(text3).repo ?? "";
|
|
@@ -50487,7 +50809,7 @@ function refusal(input, model2, share, expected) {
|
|
|
50487
50809
|
];
|
|
50488
50810
|
}
|
|
50489
50811
|
// src/core/training/ambientModel.ts
|
|
50490
|
-
import { readFileSync as
|
|
50812
|
+
import { readFileSync as readFileSync91 } from "node:fs";
|
|
50491
50813
|
import { join as join122 } from "node:path";
|
|
50492
50814
|
function ambientModelFiles(root2, home) {
|
|
50493
50815
|
return [
|
|
@@ -50510,7 +50832,7 @@ function resolveAmbientModel(input) {
|
|
|
50510
50832
|
function modelIn(path) {
|
|
50511
50833
|
let parsed;
|
|
50512
50834
|
try {
|
|
50513
|
-
parsed = JSON.parse(
|
|
50835
|
+
parsed = JSON.parse(readFileSync91(path, "utf8"));
|
|
50514
50836
|
} catch {
|
|
50515
50837
|
return null;
|
|
50516
50838
|
}
|
|
@@ -50582,11 +50904,11 @@ function noEvidenceNote(costUsd, added, warnings) {
|
|
|
50582
50904
|
return lines;
|
|
50583
50905
|
}
|
|
50584
50906
|
// src/core/training/rescoreExperts.ts
|
|
50585
|
-
import { readFileSync as
|
|
50907
|
+
import { readFileSync as readFileSync94 } from "node:fs";
|
|
50586
50908
|
import { relative as relative27 } from "node:path";
|
|
50587
50909
|
|
|
50588
50910
|
// src/core/training/competenciesWrite.ts
|
|
50589
|
-
import { existsSync as existsSync104, mkdirSync as mkdirSync24, readFileSync as
|
|
50911
|
+
import { existsSync as existsSync104, mkdirSync as mkdirSync24, readFileSync as readFileSync92, writeFileSync as writeFileSync29 } from "node:fs";
|
|
50590
50912
|
import { dirname as dirname18, join as join124 } from "node:path";
|
|
50591
50913
|
var HEADER = "# Written by `tldrx expert train` (spec §2.6). `level` is computed, never hand-set.\n";
|
|
50592
50914
|
|
|
@@ -50596,7 +50918,7 @@ function readDocument(root2, expert) {
|
|
|
50596
50918
|
const path = join124(expertDir(root2, expert), COMPETENCIES_FILE);
|
|
50597
50919
|
if (!existsSync104(path))
|
|
50598
50920
|
throw new CompetenciesError(`${path} is missing — \`tldrx expert create\` writes it`);
|
|
50599
|
-
const text3 =
|
|
50921
|
+
const text3 = readFileSync92(path, "utf8");
|
|
50600
50922
|
const parsed = parseYaml(text3);
|
|
50601
50923
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
50602
50924
|
throw new CompetenciesError(`${path} is not a mapping`);
|
|
@@ -50656,7 +50978,7 @@ function writeCompetencies(options) {
|
|
|
50656
50978
|
doc.status = options.status;
|
|
50657
50979
|
doc.last_trained = options.lastTrained;
|
|
50658
50980
|
mkdirSync24(dirname18(path), { recursive: true });
|
|
50659
|
-
|
|
50981
|
+
writeFileSync29(path, serialize(loaded, doc), "utf8");
|
|
50660
50982
|
return { ...write2, warnings };
|
|
50661
50983
|
}
|
|
50662
50984
|
function rejectBadEvidence(expert, areaId2, evidence) {
|
|
@@ -50695,7 +51017,7 @@ function recomputeCompetencies(options) {
|
|
|
50695
51017
|
if (next === loaded.text)
|
|
50696
51018
|
return { path, written: false, areas: recomputed, warnings };
|
|
50697
51019
|
mkdirSync24(dirname18(path), { recursive: true });
|
|
50698
|
-
|
|
51020
|
+
writeFileSync29(path, next, "utf8");
|
|
50699
51021
|
return { path, written: true, areas: recomputed, warnings };
|
|
50700
51022
|
}
|
|
50701
51023
|
function headerOf(text3) {
|
|
@@ -50709,7 +51031,7 @@ function str11(value) {
|
|
|
50709
51031
|
}
|
|
50710
51032
|
|
|
50711
51033
|
// src/core/training/trainingLog.ts
|
|
50712
|
-
import { appendFileSync as
|
|
51034
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync105, mkdirSync as mkdirSync25, readFileSync as readFileSync93, statSync as statSync27 } from "node:fs";
|
|
50713
51035
|
import { dirname as dirname19, join as join125 } from "node:path";
|
|
50714
51036
|
var TRAINING_EVENT_TYPES = [
|
|
50715
51037
|
"agent.spawned",
|
|
@@ -50745,7 +51067,7 @@ class TrainingLog {
|
|
|
50745
51067
|
throw new Error(`training record is ${String(bytes3)} bytes (max ${String(MAX_LINE_BYTES2)})`);
|
|
50746
51068
|
const before = this.sizeBytes;
|
|
50747
51069
|
mkdirSync25(dirname19(this.path), { recursive: true });
|
|
50748
|
-
|
|
51070
|
+
appendFileSync4(this.path, `${line}
|
|
50749
51071
|
`, "utf8");
|
|
50750
51072
|
const after = this.sizeBytes;
|
|
50751
51073
|
if (after < before + bytes3) {
|
|
@@ -50756,7 +51078,7 @@ class TrainingLog {
|
|
|
50756
51078
|
if (!existsSync105(this.path))
|
|
50757
51079
|
return [];
|
|
50758
51080
|
const out2 = [];
|
|
50759
|
-
for (const line of
|
|
51081
|
+
for (const line of readFileSync93(this.path, "utf8").split(`
|
|
50760
51082
|
`)) {
|
|
50761
51083
|
if (line.trim() === "")
|
|
50762
51084
|
continue;
|
|
@@ -50906,7 +51228,7 @@ function rescoreExperts(options) {
|
|
|
50906
51228
|
}
|
|
50907
51229
|
const shape = minesRunRecord ? RUNS_SHAPE : LIGHT_SHAPE;
|
|
50908
51230
|
const scope = knowledgeScopeFor(options.root, expert, area);
|
|
50909
|
-
const parsed = parseKnowledgeFile(
|
|
51231
|
+
const parsed = parseKnowledgeFile(readFileSync94(file.path, "utf8"), srcCtx, shape, scope);
|
|
50910
51232
|
if (!parsed.ok) {
|
|
50911
51233
|
const errors2 = knowledgeErrors(parsed).length;
|
|
50912
51234
|
skip(`the file does not validate under today's rules — ${String(errors2)} problem(s), so it is not evidence`);
|
|
@@ -50993,7 +51315,7 @@ function rescoreJson(rows) {
|
|
|
50993
51315
|
return JSON.stringify(rows, null, 2);
|
|
50994
51316
|
}
|
|
50995
51317
|
// src/core/training/selectFiles.ts
|
|
50996
|
-
import { existsSync as existsSync107, readFileSync as
|
|
51318
|
+
import { existsSync as existsSync107, readFileSync as readFileSync95, statSync as statSync29 } from "node:fs";
|
|
50997
51319
|
import { join as join126 } from "node:path";
|
|
50998
51320
|
var MAX_SCANNED_FILES2 = 4000;
|
|
50999
51321
|
var MAX_SCAN_BYTES2 = 512 * 1024;
|
|
@@ -51133,7 +51455,7 @@ function readDomains(root2, repos) {
|
|
|
51133
51455
|
const path = join126(root2, PROJECT_FRAMEWORK_DIR, "map", repo, "domains.md");
|
|
51134
51456
|
if (!existsSync107(path))
|
|
51135
51457
|
continue;
|
|
51136
|
-
for (const line of
|
|
51458
|
+
for (const line of readFileSync95(path, "utf8").split(`
|
|
51137
51459
|
`)) {
|
|
51138
51460
|
if (!line.startsWith("- "))
|
|
51139
51461
|
continue;
|
|
@@ -51162,7 +51484,7 @@ function readCommunities(root2, repos, keywords) {
|
|
|
51162
51484
|
}
|
|
51163
51485
|
let text3;
|
|
51164
51486
|
try {
|
|
51165
|
-
text3 =
|
|
51487
|
+
text3 = readFileSync95(path, "utf8");
|
|
51166
51488
|
} catch {
|
|
51167
51489
|
notes.push(`${repo}: graph.json could not be read`);
|
|
51168
51490
|
continue;
|
|
@@ -51241,7 +51563,7 @@ function matches2(label2, keywords) {
|
|
|
51241
51563
|
function contentHits(abs, keywords) {
|
|
51242
51564
|
let text3;
|
|
51243
51565
|
try {
|
|
51244
|
-
text3 =
|
|
51566
|
+
text3 = readFileSync95(abs, "utf8").toLowerCase();
|
|
51245
51567
|
} catch {
|
|
51246
51568
|
return [];
|
|
51247
51569
|
}
|
|
@@ -51252,7 +51574,7 @@ function readCapped2(abs, remaining2) {
|
|
|
51252
51574
|
try {
|
|
51253
51575
|
if (statSync29(abs).size > MAX_SCAN_BYTES2)
|
|
51254
51576
|
return null;
|
|
51255
|
-
text3 =
|
|
51577
|
+
text3 = readFileSync95(abs, "utf8");
|
|
51256
51578
|
} catch {
|
|
51257
51579
|
return null;
|
|
51258
51580
|
}
|
|
@@ -51825,7 +52147,7 @@ function skipNoteLines(expert, area, reason) {
|
|
|
51825
52147
|
return [`${expert}/${area}: skipped — ${first2 ?? ""}`, ...rest.map((line) => ` ${line}`)];
|
|
51826
52148
|
}
|
|
51827
52149
|
// src/core/training/strayWrite.ts
|
|
51828
|
-
import { existsSync as existsSync108, mkdirSync as mkdirSync26, readdirSync as readdirSync33, readFileSync as
|
|
52150
|
+
import { existsSync as existsSync108, mkdirSync as mkdirSync26, readdirSync as readdirSync33, readFileSync as readFileSync96, renameSync as renameSync4, rmdirSync as rmdirSync2, unlinkSync, writeFileSync as writeFileSync30 } from "node:fs";
|
|
51829
52151
|
import { dirname as dirname20, join as join128, relative as relative28, resolve as resolve19 } from "node:path";
|
|
51830
52152
|
function findStrayWrite(probe2) {
|
|
51831
52153
|
const root2 = resolve19(probe2.root);
|
|
@@ -51848,7 +52170,7 @@ function recoverStrayWrite(stray, dest) {
|
|
|
51848
52170
|
renameSync4(stray.path, dest);
|
|
51849
52171
|
} catch {
|
|
51850
52172
|
try {
|
|
51851
|
-
|
|
52173
|
+
writeFileSync30(dest, readFileSync96(stray.path, "utf8"), "utf8");
|
|
51852
52174
|
unlinkSync(stray.path);
|
|
51853
52175
|
} catch {
|
|
51854
52176
|
return { stray, recovered: existsSync108(dest), leftBehind: stray.path };
|
|
@@ -51897,7 +52219,7 @@ function describeStrayRecovery(root2, recovery, rel) {
|
|
|
51897
52219
|
return lines;
|
|
51898
52220
|
}
|
|
51899
52221
|
// src/core/training/runTraining.ts
|
|
51900
|
-
import { existsSync as existsSync109, mkdirSync as mkdirSync27, readFileSync as
|
|
52222
|
+
import { existsSync as existsSync109, mkdirSync as mkdirSync27, readFileSync as readFileSync97, renameSync as renameSync5, rmSync as rmSync13, writeFileSync as writeFileSync31 } from "node:fs";
|
|
51901
52223
|
import { homedir as homedir3 } from "node:os";
|
|
51902
52224
|
import { join as join129, relative as relative29 } from "node:path";
|
|
51903
52225
|
var EXIT_OK14 = 0;
|
|
@@ -52079,7 +52401,7 @@ async function trainWithPreflight(options, said2) {
|
|
|
52079
52401
|
const previous = prompts.map((task2) => ({
|
|
52080
52402
|
path: join129(dir, task2.final),
|
|
52081
52403
|
partial: join129(dir, task2.output),
|
|
52082
|
-
content: existsSync109(join129(dir, task2.final)) ?
|
|
52404
|
+
content: existsSync109(join129(dir, task2.final)) ? readFileSync97(join129(dir, task2.final), "utf8") : null
|
|
52083
52405
|
}));
|
|
52084
52406
|
const tasks = [];
|
|
52085
52407
|
for (const task2 of prompts) {
|
|
@@ -52193,7 +52515,7 @@ async function trainWithPreflight(options, said2) {
|
|
|
52193
52515
|
" nothing was written to competencies.yml and the status is unchanged"
|
|
52194
52516
|
]);
|
|
52195
52517
|
}
|
|
52196
|
-
const text3 =
|
|
52518
|
+
const text3 = readFileSync97(abs, "utf8");
|
|
52197
52519
|
const shape = task2.key === CODE_TASK ? LIGHT_SHAPE : RUNS_SHAPE;
|
|
52198
52520
|
let parsed = parseKnowledgeFile(text3, srcCtx, shape, scope);
|
|
52199
52521
|
if (!parsed.ok && options.run === "headless") {
|
|
@@ -52364,14 +52686,14 @@ async function repairRound(input) {
|
|
|
52364
52686
|
};
|
|
52365
52687
|
}
|
|
52366
52688
|
if (!existsSync109(abs)) {
|
|
52367
|
-
|
|
52689
|
+
writeFileSync31(abs, input.rejectedText, "utf8");
|
|
52368
52690
|
return {
|
|
52369
52691
|
lines: [opened, " the repair sub-agent removed the file instead of rewriting it"],
|
|
52370
52692
|
spend,
|
|
52371
52693
|
parsed: null
|
|
52372
52694
|
};
|
|
52373
52695
|
}
|
|
52374
|
-
const parsed = parseKnowledgeFile(
|
|
52696
|
+
const parsed = parseKnowledgeFile(readFileSync97(abs, "utf8"), input.srcCtx, input.shape, input.scope);
|
|
52375
52697
|
const left = knowledgeErrors(parsed).length;
|
|
52376
52698
|
return {
|
|
52377
52699
|
lines: [
|
|
@@ -52401,11 +52723,11 @@ function quarantine(abs, header2 = []) {
|
|
|
52401
52723
|
try {
|
|
52402
52724
|
const body = header2.length === 0 ? null : `${header2.join(`
|
|
52403
52725
|
`)}
|
|
52404
|
-
${
|
|
52726
|
+
${readFileSync97(abs, "utf8")}`;
|
|
52405
52727
|
rmSync13(kept, { force: true });
|
|
52406
52728
|
renameSync5(abs, kept);
|
|
52407
52729
|
if (body !== null)
|
|
52408
|
-
|
|
52730
|
+
writeFileSync31(kept, body, "utf8");
|
|
52409
52731
|
} catch {
|
|
52410
52732
|
return abs;
|
|
52411
52733
|
}
|
|
@@ -52435,7 +52757,7 @@ function rollback(previous) {
|
|
|
52435
52757
|
quarantine(entry.partial);
|
|
52436
52758
|
if (entry.content !== null) {
|
|
52437
52759
|
mkdirSync27(join129(entry.path, ".."), { recursive: true });
|
|
52438
|
-
|
|
52760
|
+
writeFileSync31(entry.path, entry.content, "utf8");
|
|
52439
52761
|
continue;
|
|
52440
52762
|
}
|
|
52441
52763
|
if (existsSync109(entry.path))
|
|
@@ -52445,7 +52767,7 @@ function rollback(previous) {
|
|
|
52445
52767
|
function setExpertStatus(path, status) {
|
|
52446
52768
|
if (!existsSync109(path))
|
|
52447
52769
|
return;
|
|
52448
|
-
const lines =
|
|
52770
|
+
const lines = readFileSync97(path, "utf8").split(`
|
|
52449
52771
|
`);
|
|
52450
52772
|
if ((lines[0] ?? "").trimEnd() !== "---")
|
|
52451
52773
|
return;
|
|
@@ -52457,7 +52779,7 @@ function setExpertStatus(path, status) {
|
|
|
52457
52779
|
continue;
|
|
52458
52780
|
const comment = line.indexOf("#", line.indexOf(":") + 1);
|
|
52459
52781
|
lines[i] = comment === -1 ? `status: ${status}` : `status: ${status} ${line.slice(comment)}`;
|
|
52460
|
-
|
|
52782
|
+
writeFileSync31(path, lines.join(`
|
|
52461
52783
|
`), "utf8");
|
|
52462
52784
|
return;
|
|
52463
52785
|
}
|
|
@@ -52925,7 +53247,7 @@ function message8(error) {
|
|
|
52925
53247
|
import { isAbsolute as isAbsolute13, join as join135 } from "node:path";
|
|
52926
53248
|
|
|
52927
53249
|
// src/core/dashboard/model.ts
|
|
52928
|
-
import { existsSync as existsSync111, readFileSync as
|
|
53250
|
+
import { existsSync as existsSync111, readFileSync as readFileSync98, readdirSync as readdirSync34 } from "node:fs";
|
|
52929
53251
|
import { basename as basename17, join as join131 } from "node:path";
|
|
52930
53252
|
// src/core/replay/renderReplay.ts
|
|
52931
53253
|
function renderReplay(loaded) {
|
|
@@ -53819,7 +54141,7 @@ function readWaves(path, unreadable) {
|
|
|
53819
54141
|
}
|
|
53820
54142
|
function read2(path, unreadable, label2) {
|
|
53821
54143
|
try {
|
|
53822
|
-
return
|
|
54144
|
+
return readFileSync98(path, "utf8");
|
|
53823
54145
|
} catch {
|
|
53824
54146
|
unreadable.push(label2);
|
|
53825
54147
|
return null;
|
|
@@ -55701,7 +56023,7 @@ function dashWavesView(model2) {
|
|
|
55701
56023
|
return head2 + blocks.join("");
|
|
55702
56024
|
}
|
|
55703
56025
|
// src/core/dashboard/writeStatic.ts
|
|
55704
|
-
import { mkdirSync as mkdirSync28, writeFileSync as
|
|
56026
|
+
import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync32 } from "node:fs";
|
|
55705
56027
|
import { join as join132 } from "node:path";
|
|
55706
56028
|
var DEFAULT_OUT_DIR = join132(PROJECT_FRAMEWORK_DIR, "cache", "dashboard");
|
|
55707
56029
|
var INDEX_FILE = "index.html";
|
|
@@ -55710,7 +56032,7 @@ function writeStaticDashboard(root2, outDir, generatedAt, now = new Date) {
|
|
|
55710
56032
|
const html = renderDashboard(model2);
|
|
55711
56033
|
const path = join132(outDir, INDEX_FILE);
|
|
55712
56034
|
mkdirSync28(outDir, { recursive: true });
|
|
55713
|
-
|
|
56035
|
+
writeFileSync32(path, html, "utf8");
|
|
55714
56036
|
return {
|
|
55715
56037
|
path,
|
|
55716
56038
|
bytes: Buffer.byteLength(html, "utf8"),
|
|
@@ -56252,10 +56574,10 @@ function option2(argv, name) {
|
|
|
56252
56574
|
}
|
|
56253
56575
|
|
|
56254
56576
|
// src/cli/commands/retro.ts
|
|
56255
|
-
import { writeFileSync as
|
|
56577
|
+
import { writeFileSync as writeFileSync34 } from "node:fs";
|
|
56256
56578
|
import { join as join137 } from "node:path";
|
|
56257
56579
|
// src/core/retro/applyPractices.ts
|
|
56258
|
-
import { existsSync as existsSync113, mkdirSync as mkdirSync29, readFileSync as
|
|
56580
|
+
import { existsSync as existsSync113, mkdirSync as mkdirSync29, readFileSync as readFileSync99, writeFileSync as writeFileSync33 } from "node:fs";
|
|
56259
56581
|
import { dirname as dirname21, join as join136 } from "node:path";
|
|
56260
56582
|
var PRACTICES_FILE = "practices.md";
|
|
56261
56583
|
var HEADER2 = [
|
|
@@ -56274,7 +56596,7 @@ function applyPractices(root2, run2, proposals, now = new Date) {
|
|
|
56274
56596
|
if (proposals.length === 0) {
|
|
56275
56597
|
return { path, appended: false, reason: "there are no practice proposals to apply" };
|
|
56276
56598
|
}
|
|
56277
|
-
const existing = existsSync113(path) ?
|
|
56599
|
+
const existing = existsSync113(path) ? readFileSync99(path, "utf8") : "";
|
|
56278
56600
|
const stamp4 = `— run ${run2}`;
|
|
56279
56601
|
if (existing.includes(stamp4)) {
|
|
56280
56602
|
return { path, appended: false, reason: `practices.md already carries a block for run ${run2}` };
|
|
@@ -56286,7 +56608,7 @@ function applyPractices(root2, run2, proposals, now = new Date) {
|
|
|
56286
56608
|
`) ? existing : `${existing}
|
|
56287
56609
|
`;
|
|
56288
56610
|
mkdirSync29(dirname21(path), { recursive: true });
|
|
56289
|
-
|
|
56611
|
+
writeFileSync33(path, `${base2}
|
|
56290
56612
|
${block2}`, "utf8");
|
|
56291
56613
|
return { path, appended: true, reason: null };
|
|
56292
56614
|
}
|
|
@@ -56440,7 +56762,7 @@ var retroCommand = {
|
|
|
56440
56762
|
}
|
|
56441
56763
|
const report3 = buildRetro(loaded);
|
|
56442
56764
|
const path = join137(loaded.dir, RETRO_FILE);
|
|
56443
|
-
|
|
56765
|
+
writeFileSync34(path, report3.markdown, "utf8");
|
|
56444
56766
|
const lines = [
|
|
56445
56767
|
`wrote ${path}`,
|
|
56446
56768
|
` ${report3.facts.length} fact(s) · ${report3.practices.length} practice proposal(s) ` + `· ${report3.stages.length} proposed stage(s)`
|
|
@@ -56823,7 +57145,7 @@ function theOneOpenRun() {
|
|
|
56823
57145
|
}
|
|
56824
57146
|
}
|
|
56825
57147
|
// src/core/watch/watchViews.ts
|
|
56826
|
-
import { existsSync as existsSync114, readdirSync as readdirSync36, readFileSync as
|
|
57148
|
+
import { existsSync as existsSync114, readdirSync as readdirSync36, readFileSync as readFileSync100 } from "node:fs";
|
|
56827
57149
|
import { join as join138 } from "node:path";
|
|
56828
57150
|
function watchersDir(runDir2) {
|
|
56829
57151
|
return join138(runDir2, WATCH_PHASE, WATCHERS_DIR);
|
|
@@ -56835,7 +57157,7 @@ function loadCards(runDir2, ctx) {
|
|
|
56835
57157
|
const cards = [];
|
|
56836
57158
|
for (const name of readdirSync36(dir).filter((n) => n.endsWith(".md")).sort()) {
|
|
56837
57159
|
const id = name.slice(0, -".md".length);
|
|
56838
|
-
const text5 =
|
|
57160
|
+
const text5 = readFileSync100(join138(dir, name), "utf8");
|
|
56839
57161
|
cards.push({
|
|
56840
57162
|
id,
|
|
56841
57163
|
text: text5,
|
|
@@ -57596,7 +57918,7 @@ function quote4(value) {
|
|
|
57596
57918
|
return JSON.stringify(value);
|
|
57597
57919
|
}
|
|
57598
57920
|
// src/core/adapters/processConfig.ts
|
|
57599
|
-
import { existsSync as existsSync116, readFileSync as
|
|
57921
|
+
import { existsSync as existsSync116, readFileSync as readFileSync101 } from "node:fs";
|
|
57600
57922
|
import { join as join140 } from "node:path";
|
|
57601
57923
|
var PROCESS_FILE2 = "process.yml";
|
|
57602
57924
|
var DISABLED = {
|
|
@@ -57615,7 +57937,7 @@ function readTicketToolConfig(root2) {
|
|
|
57615
57937
|
return DISABLED;
|
|
57616
57938
|
let doc;
|
|
57617
57939
|
try {
|
|
57618
|
-
doc = parseYaml(
|
|
57940
|
+
doc = parseYaml(readFileSync101(path, "utf8"));
|
|
57619
57941
|
} catch {
|
|
57620
57942
|
return { ...DISABLED, path };
|
|
57621
57943
|
}
|
|
@@ -57777,7 +58099,7 @@ function strip(url) {
|
|
|
57777
58099
|
return at === -1 ? url : url.slice(at);
|
|
57778
58100
|
}
|
|
57779
58101
|
// src/core/adapters/collect.ts
|
|
57780
|
-
import { existsSync as existsSync117, readdirSync as readdirSync37, readFileSync as
|
|
58102
|
+
import { existsSync as existsSync117, readdirSync as readdirSync37, readFileSync as readFileSync102 } from "node:fs";
|
|
57781
58103
|
import { join as join141 } from "node:path";
|
|
57782
58104
|
var PLAN_PHASE3 = "03-plan";
|
|
57783
58105
|
function collectMirrorItems(planDir, runId) {
|
|
@@ -57785,7 +58107,7 @@ function collectMirrorItems(planDir, runId) {
|
|
|
57785
58107
|
const skipped = [];
|
|
57786
58108
|
for (const name of markdownIn4(join141(planDir, EPICS_DIR))) {
|
|
57787
58109
|
const path = join141(planDir, EPICS_DIR, name);
|
|
57788
|
-
const text6 =
|
|
58110
|
+
const text6 = readFileSync102(path, "utf8");
|
|
57789
58111
|
const parsed = validateEpicFile(text6);
|
|
57790
58112
|
const epic = parsed.epic;
|
|
57791
58113
|
if (epic === null) {
|
|
@@ -57818,7 +58140,7 @@ function collectMirrorItems(planDir, runId) {
|
|
|
57818
58140
|
}
|
|
57819
58141
|
for (const name of markdownIn4(join141(planDir, STORIES_DIR))) {
|
|
57820
58142
|
const path = join141(planDir, STORIES_DIR, name);
|
|
57821
|
-
const text6 =
|
|
58143
|
+
const text6 = readFileSync102(path, "utf8");
|
|
57822
58144
|
const parsed = validateStoryFile(text6);
|
|
57823
58145
|
const story = parsed.story;
|
|
57824
58146
|
if (story === null) {
|
|
@@ -57863,7 +58185,7 @@ function reason(issues) {
|
|
|
57863
58185
|
return first2.path === "" ? first2.message : `${first2.path}: ${first2.message}`;
|
|
57864
58186
|
}
|
|
57865
58187
|
// src/core/adapters/sync.ts
|
|
57866
|
-
import { readFileSync as
|
|
58188
|
+
import { readFileSync as readFileSync103, writeFileSync as writeFileSync35 } from "node:fs";
|
|
57867
58189
|
function planSync(options) {
|
|
57868
58190
|
const collected = collectMirrorItems(options.planDir, options.runId);
|
|
57869
58191
|
const actions = new Map;
|
|
@@ -57907,14 +58229,14 @@ async function syncTickets(options) {
|
|
|
57907
58229
|
return { results, skipped: collected.skipped, created, updated, dryRun: options.dryRun };
|
|
57908
58230
|
}
|
|
57909
58231
|
function writeBack(item, ref, externalStatus, sync) {
|
|
57910
|
-
const before2 =
|
|
58232
|
+
const before2 = readFileSync103(item.path, "utf8");
|
|
57911
58233
|
const after = applyExternal(before2, {
|
|
57912
58234
|
external: ref,
|
|
57913
58235
|
...sync === "two-way" ? { externalStatus } : {}
|
|
57914
58236
|
});
|
|
57915
58237
|
if (after === before2)
|
|
57916
58238
|
return;
|
|
57917
|
-
|
|
58239
|
+
writeFileSync35(item.path, after, "utf8");
|
|
57918
58240
|
}
|
|
57919
58241
|
// src/core/adapters/statusTable.ts
|
|
57920
58242
|
var DONE_LIKE = ["done", "closed", "resolved", "complete", "completed", "shipped"];
|
|
@@ -58159,7 +58481,7 @@ function renderSync(runId, provider, config, outcome) {
|
|
|
58159
58481
|
}
|
|
58160
58482
|
|
|
58161
58483
|
// src/cli/commands/interview.ts
|
|
58162
|
-
import { existsSync as existsSync120, readFileSync as
|
|
58484
|
+
import { existsSync as existsSync120, readFileSync as readFileSync106 } from "node:fs";
|
|
58163
58485
|
import { join as join144 } from "node:path";
|
|
58164
58486
|
|
|
58165
58487
|
// src/core/interview/lineReader.ts
|
|
@@ -58273,7 +58595,7 @@ function renderNextSteps(hasRun) {
|
|
|
58273
58595
|
}
|
|
58274
58596
|
|
|
58275
58597
|
// src/core/interview/runInterview.ts
|
|
58276
|
-
import { existsSync as existsSync118, readFileSync as
|
|
58598
|
+
import { existsSync as existsSync118, readFileSync as readFileSync104 } from "node:fs";
|
|
58277
58599
|
|
|
58278
58600
|
// src/core/interview/reply.ts
|
|
58279
58601
|
var LETTER_RE = /^[A-Ea-e]$/;
|
|
@@ -58314,7 +58636,7 @@ async function interview(options) {
|
|
|
58314
58636
|
const { questionsPath, out: out2, ctx } = options;
|
|
58315
58637
|
if (!existsSync118(questionsPath))
|
|
58316
58638
|
throw new InterviewError(`no questions file at ${questionsPath}`);
|
|
58317
|
-
const blocks = openBlocks(parseQuestions(
|
|
58639
|
+
const blocks = openBlocks(parseQuestions(readFileSync104(questionsPath, "utf8")).blocks);
|
|
58318
58640
|
const answered = [];
|
|
58319
58641
|
let skipped = 0;
|
|
58320
58642
|
let quit = false;
|
|
@@ -58375,7 +58697,7 @@ function renderInterviewSummary(result2) {
|
|
|
58375
58697
|
}
|
|
58376
58698
|
|
|
58377
58699
|
// src/core/init/processAnswers.ts
|
|
58378
|
-
import { existsSync as existsSync119, readFileSync as
|
|
58700
|
+
import { existsSync as existsSync119, readFileSync as readFileSync105 } from "node:fs";
|
|
58379
58701
|
|
|
58380
58702
|
// src/core/init/githubProject.ts
|
|
58381
58703
|
import { join as join143 } from "node:path";
|
|
@@ -58425,7 +58747,7 @@ function hasProcessAnswer(answers) {
|
|
|
58425
58747
|
function collectProcessAnswers(questionsPath, answered) {
|
|
58426
58748
|
if (answered.length === 0 || !existsSync119(questionsPath))
|
|
58427
58749
|
return NO_PROCESS_ANSWERS;
|
|
58428
|
-
const titles = new Map(parseQuestions(
|
|
58750
|
+
const titles = new Map(parseQuestions(readFileSync105(questionsPath, "utf8")).blocks.map((block2) => [block2.id, block2.title]));
|
|
58429
58751
|
const find = (title) => answered.find((answer2) => titles.get(answer2.q) === title);
|
|
58430
58752
|
const methodology = find(METHODOLOGY_QUESTION);
|
|
58431
58753
|
const ticket = find(TICKET_QUESTION);
|
|
@@ -58463,7 +58785,7 @@ async function applyProcessAnswers(input) {
|
|
|
58463
58785
|
}
|
|
58464
58786
|
assertEnum(methodology, METHODOLOGIES, "methodology");
|
|
58465
58787
|
assertEnum(ticketTool, TICKET_TOOLS, "ticket_tool.kind");
|
|
58466
|
-
const before2 = existsSync119(path) ?
|
|
58788
|
+
const before2 = existsSync119(path) ? readFileSync105(path, "utf8") : null;
|
|
58467
58789
|
const seed = before2 ?? PROCESS_HEADER + stringifyYaml(buildProcessDocument({
|
|
58468
58790
|
methodology: null,
|
|
58469
58791
|
approver: await gitUserName(input.runner, input.root),
|
|
@@ -58700,11 +59022,11 @@ No open questions.
|
|
|
58700
59022
|
return "no-open-questions";
|
|
58701
59023
|
}
|
|
58702
59024
|
function countOpen(path) {
|
|
58703
|
-
return openBlocks(parseQuestions(
|
|
59025
|
+
return openBlocks(parseQuestions(readFileSync106(path, "utf8")).blocks).length;
|
|
58704
59026
|
}
|
|
58705
59027
|
|
|
58706
59028
|
// src/cli/commands/questions.ts
|
|
58707
|
-
import { existsSync as existsSync121, readFileSync as
|
|
59029
|
+
import { existsSync as existsSync121, readFileSync as readFileSync107, writeFileSync as writeFileSync36 } from "node:fs";
|
|
58708
59030
|
import { join as join145 } from "node:path";
|
|
58709
59031
|
var VALUE_FLAGS15 = ["run", "root", "area"];
|
|
58710
59032
|
var questionsCommand = {
|
|
@@ -58761,7 +59083,7 @@ function lint(argv) {
|
|
|
58761
59083
|
const needSource = [];
|
|
58762
59084
|
for (const path of files) {
|
|
58763
59085
|
const rel = path.slice(store.runDir.length + 1);
|
|
58764
|
-
const text6 =
|
|
59086
|
+
const text6 = readFileSync107(path, "utf8");
|
|
58765
59087
|
const readable = parseQuestions(text6).blocks.length;
|
|
58766
59088
|
const lost = unreadableQuestionHeadings(text6);
|
|
58767
59089
|
if (lost.length === 0) {
|
|
@@ -58782,7 +59104,7 @@ function lint(argv) {
|
|
|
58782
59104
|
askedBy: currentActor(),
|
|
58783
59105
|
askedAt: nowRfc3339()
|
|
58784
59106
|
});
|
|
58785
|
-
|
|
59107
|
+
writeFileSync36(path, result2.text, "utf8");
|
|
58786
59108
|
fixed += result2.converted.length;
|
|
58787
59109
|
needSource.push(...result2.needSource);
|
|
58788
59110
|
lines.push(` FIX ${rel} — converted ${result2.converted.join(", ")} to the §2.7 grammar`);
|
|
@@ -58926,11 +59248,11 @@ function renderFinale(ink) {
|
|
|
58926
59248
|
}
|
|
58927
59249
|
|
|
58928
59250
|
// src/core/learn/engine.ts
|
|
58929
|
-
import { existsSync as existsSync124, readdirSync as readdirSync38, readFileSync as
|
|
59251
|
+
import { existsSync as existsSync124, readdirSync as readdirSync38, readFileSync as readFileSync108 } from "node:fs";
|
|
58930
59252
|
import { join as join148 } from "node:path";
|
|
58931
59253
|
|
|
58932
59254
|
// src/core/learn/sandbox.ts
|
|
58933
|
-
import { chmodSync, existsSync as existsSync123, mkdirSync as mkdirSync30, rmSync as rmSync14, writeFileSync as
|
|
59255
|
+
import { chmodSync, existsSync as existsSync123, mkdirSync as mkdirSync30, rmSync as rmSync14, writeFileSync as writeFileSync37 } from "node:fs";
|
|
58934
59256
|
import { dirname as dirname22, join as join147, resolve as resolve20 } from "node:path";
|
|
58935
59257
|
import { homedir as homedir4 } from "node:os";
|
|
58936
59258
|
function defaultSandboxRoot(home = homedir4()) {
|
|
@@ -59002,14 +59324,14 @@ function claudeShimScript(selfCmd, scriptPath) {
|
|
|
59002
59324
|
`);
|
|
59003
59325
|
}
|
|
59004
59326
|
function writeClaudeShim(sandbox, selfCmd) {
|
|
59005
|
-
|
|
59327
|
+
writeFileSync37(sandbox.claudeBin, claudeShimScript(selfCmd, sandbox.scriptPath), "utf8");
|
|
59006
59328
|
chmodSync(sandbox.claudeBin, 493);
|
|
59007
59329
|
}
|
|
59008
59330
|
function shellQuote3(text6) {
|
|
59009
59331
|
return `'${text6.replaceAll("'", `'\\''`)}'`;
|
|
59010
59332
|
}
|
|
59011
59333
|
function writeScript(sandbox, script) {
|
|
59012
|
-
|
|
59334
|
+
writeFileSync37(sandbox.scriptPath, stringifyScript(script), "utf8");
|
|
59013
59335
|
rmSync14(`${sandbox.scriptPath}.tally.json`, { force: true });
|
|
59014
59336
|
}
|
|
59015
59337
|
function sandboxEnv(sandbox, base2 = process.env) {
|
|
@@ -59074,7 +59396,7 @@ async function makeToyRepo(dir) {
|
|
|
59074
59396
|
for (const [rel, content] of Object.entries(TOY_FILES)) {
|
|
59075
59397
|
const path = join147(dir, rel);
|
|
59076
59398
|
mkdirSync30(dirname22(path), { recursive: true });
|
|
59077
|
-
|
|
59399
|
+
writeFileSync37(path, content, "utf8");
|
|
59078
59400
|
}
|
|
59079
59401
|
await git3(dir, ["init", "-q"]);
|
|
59080
59402
|
await git3(dir, ["symbolic-ref", "HEAD", "refs/heads/main"]);
|
|
@@ -59200,7 +59522,7 @@ function newestRunId(sandbox) {
|
|
|
59200
59522
|
}
|
|
59201
59523
|
function runIsFinished(runDir2) {
|
|
59202
59524
|
try {
|
|
59203
|
-
const status = /^status:\s*(\S+)/m.exec(
|
|
59525
|
+
const status = /^status:\s*(\S+)/m.exec(readFileSync108(join148(runDir2, "run.yml"), "utf8"))?.[1] ?? "";
|
|
59204
59526
|
return isFinished(status);
|
|
59205
59527
|
} catch {
|
|
59206
59528
|
return false;
|
|
@@ -59241,7 +59563,7 @@ function sandboxHas(sandbox, rel) {
|
|
|
59241
59563
|
}
|
|
59242
59564
|
|
|
59243
59565
|
// src/core/learn/chapters.ts
|
|
59244
|
-
import { existsSync as existsSync125, mkdirSync as mkdirSync31, readdirSync as readdirSync39, readFileSync as
|
|
59566
|
+
import { existsSync as existsSync125, mkdirSync as mkdirSync31, readdirSync as readdirSync39, readFileSync as readFileSync109, writeFileSync as writeFileSync38 } from "node:fs";
|
|
59245
59567
|
import { dirname as dirname23, join as join149 } from "node:path";
|
|
59246
59568
|
var LEARN_RUN_SLUG = "bulk-pricing";
|
|
59247
59569
|
var WATCH_PHASE_DIR = "05-watch";
|
|
@@ -60169,7 +60491,7 @@ var CHAPTER_6 = {
|
|
|
60169
60491
|
return;
|
|
60170
60492
|
const path = join149(sandbox.workspace, PROJECT_WORK_DIR, run2, ".agent", "watch", "evidence.md");
|
|
60171
60493
|
mkdirSync31(dirname23(path), { recursive: true });
|
|
60172
|
-
|
|
60494
|
+
writeFileSync38(path, evidenceNote2(), "utf8");
|
|
60173
60495
|
},
|
|
60174
60496
|
steps: [
|
|
60175
60497
|
{
|
|
@@ -60492,11 +60814,11 @@ var CHAPTERS = [
|
|
|
60492
60814
|
];
|
|
60493
60815
|
function writeInto(path, content) {
|
|
60494
60816
|
mkdirSync31(dirname23(path), { recursive: true });
|
|
60495
|
-
|
|
60817
|
+
writeFileSync38(path, content, "utf8");
|
|
60496
60818
|
}
|
|
60497
60819
|
function read3(sandbox, rel) {
|
|
60498
60820
|
try {
|
|
60499
|
-
return
|
|
60821
|
+
return readFileSync109(join149(sandbox.workspace, rel), "utf8");
|
|
60500
60822
|
} catch {
|
|
60501
60823
|
return "";
|
|
60502
60824
|
}
|
|
@@ -60527,14 +60849,14 @@ function runIdBySlug(sandbox, slug) {
|
|
|
60527
60849
|
}
|
|
60528
60850
|
|
|
60529
60851
|
// src/core/learn/progress.ts
|
|
60530
|
-
import { existsSync as existsSync126, mkdirSync as mkdirSync32, readFileSync as
|
|
60852
|
+
import { existsSync as existsSync126, mkdirSync as mkdirSync32, readFileSync as readFileSync110, writeFileSync as writeFileSync39 } from "node:fs";
|
|
60531
60853
|
import { dirname as dirname24 } from "node:path";
|
|
60532
60854
|
var NO_PROGRESS = { version: 1, completed: [], updatedAt: null };
|
|
60533
60855
|
function readProgress(path) {
|
|
60534
60856
|
if (!existsSync126(path))
|
|
60535
60857
|
return NO_PROGRESS;
|
|
60536
60858
|
try {
|
|
60537
|
-
const doc = JSON.parse(
|
|
60859
|
+
const doc = JSON.parse(readFileSync110(path, "utf8"));
|
|
60538
60860
|
const completed = doc.completed;
|
|
60539
60861
|
if (!Array.isArray(completed))
|
|
60540
60862
|
return NO_PROGRESS;
|
|
@@ -60551,7 +60873,7 @@ function readProgress(path) {
|
|
|
60551
60873
|
}
|
|
60552
60874
|
function writeProgress(path, progress) {
|
|
60553
60875
|
mkdirSync32(dirname24(path), { recursive: true });
|
|
60554
|
-
|
|
60876
|
+
writeFileSync39(path, `${JSON.stringify(progress, null, 2)}
|
|
60555
60877
|
`, "utf8");
|
|
60556
60878
|
}
|
|
60557
60879
|
function markComplete(progress, chapter, at) {
|