tldr-experts 0.18.2 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- package/README.md +1 -0
- package/dist/hooks/answer-capture.js +9 -9
- package/dist/hooks/budget-gate.js +9 -9
- package/dist/hooks/{chunk-c1h38et6.js → chunk-0sy3tmfg.js} +2 -3
- package/dist/hooks/{chunk-afttzftj.js → chunk-53wgfn01.js} +6 -6
- package/dist/hooks/{chunk-8ak5kwd5.js → chunk-5stgwp3g.js} +1 -1
- package/dist/hooks/{chunk-gx6qa496.js → chunk-6z5rmj0b.js} +3 -2
- package/dist/hooks/{chunk-4y3pk4n2.js → chunk-bznt6jb2.js} +1 -1
- package/dist/hooks/{chunk-vf992pfw.js → chunk-ezfvp5kx.js} +1 -1
- package/dist/hooks/{chunk-wx1jce1t.js → chunk-j5d0nard.js} +2 -2
- package/dist/hooks/{chunk-kwaqdq9w.js → chunk-jpp137mb.js} +3 -3
- package/dist/hooks/{chunk-2caxb41h.js → chunk-ka7768vx.js} +1 -1
- package/dist/hooks/{chunk-x00a2vaz.js → chunk-m1s8a6s0.js} +2 -2
- package/dist/hooks/{chunk-1v05a468.js → chunk-n5bxz2cw.js} +1 -1
- package/dist/hooks/{chunk-pday823f.js → chunk-qzsxhsyj.js} +1 -1
- package/dist/hooks/{chunk-2ym87z6d.js → chunk-t44983nq.js} +1 -1
- package/dist/hooks/{chunk-nf2j945p.js → chunk-tjx8g39f.js} +3 -3
- package/dist/hooks/{chunk-hwsng0xq.js → chunk-yre2scxn.js} +1 -1
- package/dist/hooks/claim-sources.js +5 -5
- package/dist/hooks/dod-gate.js +8 -8
- package/dist/hooks/no-reask.js +8 -8
- package/dist/hooks/session-start.js +13 -13
- package/dist/hooks/statusline.js +9 -9
- package/dist/tldrx.js +271 -77
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/dist/tldrx.js
CHANGED
|
@@ -7290,6 +7290,7 @@ var EVENT_TYPES = [
|
|
|
7290
7290
|
"questions.policy_changed",
|
|
7291
7291
|
"story.reopened",
|
|
7292
7292
|
"story.base_fastforwarded",
|
|
7293
|
+
"story.base_updated",
|
|
7293
7294
|
"story.review_retried",
|
|
7294
7295
|
"story.work_rescued",
|
|
7295
7296
|
"story.touches_widened",
|
|
@@ -8361,6 +8362,16 @@ function withoutSrcToken(line) {
|
|
|
8361
8362
|
function hasSrcMarker(line) {
|
|
8362
8363
|
return line.includes(SRC_MARKER);
|
|
8363
8364
|
}
|
|
8365
|
+
function unclosedSrcMarker(text) {
|
|
8366
|
+
const open = text.lastIndexOf(SRC_MARKER);
|
|
8367
|
+
if (open < 0)
|
|
8368
|
+
return null;
|
|
8369
|
+
return text.includes("]", open + SRC_MARKER.length) ? null : open;
|
|
8370
|
+
}
|
|
8371
|
+
function foldsUnclosedSrcMarker(line) {
|
|
8372
|
+
const token = parseSrcToken(line);
|
|
8373
|
+
return token !== null && token.raw.indexOf(SRC_MARKER, SRC_MARKER.length) >= 0;
|
|
8374
|
+
}
|
|
8364
8375
|
function classifySrc(src, repos) {
|
|
8365
8376
|
const raw = src;
|
|
8366
8377
|
if (raw === "")
|
|
@@ -10405,8 +10416,7 @@ function splitArgv(command) {
|
|
|
10405
10416
|
if (/[\n\r]/.test(command))
|
|
10406
10417
|
return null;
|
|
10407
10418
|
const argv = [];
|
|
10408
|
-
const
|
|
10409
|
-
for (const match of command.matchAll(pattern)) {
|
|
10419
|
+
for (const match of command.matchAll(TOKEN_RE)) {
|
|
10410
10420
|
if (match[1] !== undefined || match[2] !== undefined) {
|
|
10411
10421
|
argv.push(match[1] ?? match[2] ?? "");
|
|
10412
10422
|
continue;
|
|
@@ -10418,6 +10428,21 @@ function splitArgv(command) {
|
|
|
10418
10428
|
}
|
|
10419
10429
|
return argv.length === 0 ? null : argv;
|
|
10420
10430
|
}
|
|
10431
|
+
var TOKEN_RE = /"([^"]*)"|'([^']*)'|([^\s"']+)/g;
|
|
10432
|
+
var SEPARATOR_RE = /\|\||&&|2>&1|>>|\$\((?!\()|[|&;<>`]/;
|
|
10433
|
+
function unquotedShellSeparator(command) {
|
|
10434
|
+
if (/[\n\r]/.test(command))
|
|
10435
|
+
return `
|
|
10436
|
+
`;
|
|
10437
|
+
for (const match of command.matchAll(TOKEN_RE)) {
|
|
10438
|
+
if (match[1] !== undefined || match[2] !== undefined)
|
|
10439
|
+
continue;
|
|
10440
|
+
const found = SEPARATOR_RE.exec(match[3] ?? "");
|
|
10441
|
+
if (found !== null)
|
|
10442
|
+
return found[0];
|
|
10443
|
+
}
|
|
10444
|
+
return null;
|
|
10445
|
+
}
|
|
10421
10446
|
|
|
10422
10447
|
class DodCommandRefused extends Error {
|
|
10423
10448
|
}
|
|
@@ -11300,7 +11325,12 @@ function clean(text) {
|
|
|
11300
11325
|
const stripped = text.replace(GROUNDING_TAG_RE, "").replace(/\s+/g, " ").replace(/\*\*/g, "").trim();
|
|
11301
11326
|
if (stripped.length <= MAX_CLAIM_CHARS)
|
|
11302
11327
|
return stripped;
|
|
11303
|
-
|
|
11328
|
+
const cut = stripped.slice(0, MAX_CLAIM_CHARS - 1);
|
|
11329
|
+
const open = unclosedSrcMarker(cut);
|
|
11330
|
+
if (open === null)
|
|
11331
|
+
return `${cut.trimEnd()}…`;
|
|
11332
|
+
const kept = cut.slice(0, open).trimEnd();
|
|
11333
|
+
return kept === "" ? stripped : `${kept}…`;
|
|
11304
11334
|
}
|
|
11305
11335
|
|
|
11306
11336
|
// src/core/distill/aidlcQuestions.ts
|
|
@@ -13892,7 +13922,7 @@ function createRunLocked(options) {
|
|
|
13892
13922
|
write(temp, handoffPath, handoff, written);
|
|
13893
13923
|
const check = validateHandoff(handoff, toSrcContext(workspace, temp));
|
|
13894
13924
|
if (!check.ok) {
|
|
13895
|
-
throw new NewRunError(`seeded handoff is invalid: ${describeHandoff(check.missingSections, check.emptySections, check.unsourced, check.unresolved)}`);
|
|
13925
|
+
throw new NewRunError(`seeded handoff is invalid: ${describeHandoff(check.missingSections, check.emptySections, check.unsourced, check.unresolved, handoff)}`);
|
|
13896
13926
|
}
|
|
13897
13927
|
}
|
|
13898
13928
|
let factsYaml = null;
|
|
@@ -13926,7 +13956,7 @@ function createRunLocked(options) {
|
|
|
13926
13956
|
const srcCtx = toSrcContext(workspace, temp);
|
|
13927
13957
|
const handoffCheck = validateHandoff(handoff, srcCtx);
|
|
13928
13958
|
if (!handoffCheck.ok) {
|
|
13929
|
-
throw new NewRunError(`distilled handoff is invalid: ${describeHandoff(handoffCheck.missingSections, handoffCheck.emptySections, handoffCheck.unsourced, handoffCheck.unresolved)}`);
|
|
13959
|
+
throw new NewRunError(`distilled handoff is invalid: ${describeHandoff(handoffCheck.missingSections, handoffCheck.emptySections, handoffCheck.unsourced, handoffCheck.unresolved, handoff)}`);
|
|
13930
13960
|
}
|
|
13931
13961
|
if (result2.conflicts.length > 0) {
|
|
13932
13962
|
const text = renderQuestions(runId, phaseId, at, result2.conflicts);
|
|
@@ -14096,7 +14126,7 @@ function assertValid(what, validation) {
|
|
|
14096
14126
|
const first = validation.issues[0];
|
|
14097
14127
|
throw new NewRunError(`refusing to create a run with an invalid ${what}: ${first?.path ?? ""} ${first?.message ?? ""}`);
|
|
14098
14128
|
}
|
|
14099
|
-
function describeHandoff(missing, empty, unsourced, unresolved) {
|
|
14129
|
+
function describeHandoff(missing, empty, unsourced, unresolved, text = "") {
|
|
14100
14130
|
if (missing.length > 0)
|
|
14101
14131
|
return `missing section(s) ${missing.join(", ")}`;
|
|
14102
14132
|
if (empty.length > 0) {
|
|
@@ -14104,7 +14134,24 @@ function describeHandoff(missing, empty, unsourced, unresolved) {
|
|
|
14104
14134
|
}
|
|
14105
14135
|
if (unsourced.length > 0)
|
|
14106
14136
|
return `unsourced bullet(s) on line(s) ${unsourced.join(", ")}`;
|
|
14107
|
-
|
|
14137
|
+
const first = unresolved[0];
|
|
14138
|
+
if (first === undefined)
|
|
14139
|
+
return "unknown problem";
|
|
14140
|
+
return `${first.message}${foldedCitationNote(text, first.line)}`;
|
|
14141
|
+
}
|
|
14142
|
+
var MAX_QUOTED_CHARS2 = 120;
|
|
14143
|
+
function foldedCitationNote(text, line) {
|
|
14144
|
+
if (text === "" || line < 1)
|
|
14145
|
+
return "";
|
|
14146
|
+
const raw = text.split(`
|
|
14147
|
+
`)[line - 1];
|
|
14148
|
+
if (raw === undefined || raw.trim() === "")
|
|
14149
|
+
return "";
|
|
14150
|
+
if (!foldsUnclosedSrcMarker(raw))
|
|
14151
|
+
return "";
|
|
14152
|
+
const trimmed = raw.trim();
|
|
14153
|
+
const quoted = trimmed.length <= MAX_QUOTED_CHARS2 ? trimmed : `${trimmed.slice(0, MAX_QUOTED_CHARS2)}…`;
|
|
14154
|
+
return ` — L${String(line)} carries an unterminated \`[src:\` citation, so the path above is two ` + `citations folded into one and is not a file to look for; close or remove the inline citation ` + `in the seed (shorter bullets are safest). L${String(line)} reads \`${quoted}\``;
|
|
14108
14155
|
}
|
|
14109
14156
|
function yymmdd(now) {
|
|
14110
14157
|
return now.toISOString().slice(2, 10).replace(/-/g, "");
|
|
@@ -25784,14 +25831,10 @@ async function mergeNoFf(cwd, branch, message4) {
|
|
|
25784
25831
|
detail: conflicts.length > 0 ? `conflict in ${conflicts.join(", ")}` : firstLine7(merged.stderr) || firstLine7(merged.stdout) || `git merge exited ${String(merged.exitCode)}`
|
|
25785
25832
|
};
|
|
25786
25833
|
}
|
|
25787
|
-
|
|
25788
|
-
|
|
25789
|
-
if (!result2.ok)
|
|
25790
|
-
return 0;
|
|
25791
|
-
const n = Number.parseInt(result2.stdout.trim(), 10);
|
|
25792
|
-
return Number.isFinite(n) ? n : 0;
|
|
25834
|
+
function uncountedCount(base, head) {
|
|
25835
|
+
return `\`git rev-list --count ${base}..${head}\` could not count what it carries`;
|
|
25793
25836
|
}
|
|
25794
|
-
async function
|
|
25837
|
+
async function commitsBetween(cwd, base, head) {
|
|
25795
25838
|
const result2 = await git(["rev-list", "--count", `${base}..${head}`], cwd);
|
|
25796
25839
|
if (!result2.ok)
|
|
25797
25840
|
return null;
|
|
@@ -25799,12 +25842,15 @@ async function commitsAhead(cwd, base, head) {
|
|
|
25799
25842
|
return Number.isFinite(n) ? n : null;
|
|
25800
25843
|
}
|
|
25801
25844
|
async function baseStateOf(cwd, branch, base) {
|
|
25802
|
-
const
|
|
25803
|
-
const
|
|
25845
|
+
const counted = await commitsBetween(cwd, base, branch);
|
|
25846
|
+
const countedBack = await commitsBetween(cwd, branch, base);
|
|
25847
|
+
const ahead = counted ?? 0;
|
|
25848
|
+
const behind = countedBack ?? 0;
|
|
25804
25849
|
return {
|
|
25805
25850
|
state: behind === 0 ? "current" : ahead === 0 ? "behind" : "diverged",
|
|
25806
25851
|
ahead,
|
|
25807
25852
|
behind,
|
|
25853
|
+
uncounted: counted === null ? uncountedCount(base, branch) : countedBack === null ? uncountedCount(branch, base) : null,
|
|
25808
25854
|
branchSha: await shaOf(cwd, branch),
|
|
25809
25855
|
baseSha: await shaOf(cwd, base)
|
|
25810
25856
|
};
|
|
@@ -26364,6 +26410,9 @@ function scopedNote(result2) {
|
|
|
26364
26410
|
function dodGreen(outcome) {
|
|
26365
26411
|
return outcome.dod.length > 0 && outcome.dod.every((r) => !dodRefused(r) && r.exitCode === 0 && !r.timedOut);
|
|
26366
26412
|
}
|
|
26413
|
+
function dodFailure(dod) {
|
|
26414
|
+
return dod.find((r) => dodRefused(r) || r.exitCode !== 0 || r.timedOut);
|
|
26415
|
+
}
|
|
26367
26416
|
function dodFailureReason(result2, repo) {
|
|
26368
26417
|
if (dodRefused(result2)) {
|
|
26369
26418
|
return `\`${result2.command}\` was REFUSED in repo ${repo} and never ran — ` + `${result2.refusedBecause ?? DOD_REFUSAL_FALLBACK}`;
|
|
@@ -26374,6 +26423,15 @@ function dodFailureReason(result2, repo) {
|
|
|
26374
26423
|
return `\`${result2.rendered ?? result2.command}\`${scopedNote(result2)} exited ${String(result2.exitCode ?? "?")} in repo ${repo}` + `${result2.timedOut ? " (timed out)" : ""} — ${result2.tail}` + (result2.outputPath === undefined ? "" : ` [src: ${result2.outputPath}:${String(result2.outputLine ?? 1)}]`);
|
|
26375
26424
|
}
|
|
26376
26425
|
|
|
26426
|
+
// src/core/build/developerGrants.ts
|
|
26427
|
+
var DEVELOPER_GIT_VERBS = ["add", "commit", "rm", "mv", "restore"];
|
|
26428
|
+
function developerGitGrants() {
|
|
26429
|
+
return DEVELOPER_GIT_VERBS.map((verb) => `Bash(git ${verb} *)`);
|
|
26430
|
+
}
|
|
26431
|
+
function isDeveloperGitVerb(verb) {
|
|
26432
|
+
return DEVELOPER_GIT_VERBS.includes(verb);
|
|
26433
|
+
}
|
|
26434
|
+
|
|
26377
26435
|
// src/core/build/prompts.ts
|
|
26378
26436
|
var MAX_TOUCHED_BYTES = 64 * 1024;
|
|
26379
26437
|
var MAX_TOUCHED_FILES = 24;
|
|
@@ -26403,6 +26461,10 @@ function testFastRule(fast, full) {
|
|
|
26403
26461
|
]
|
|
26404
26462
|
];
|
|
26405
26463
|
}
|
|
26464
|
+
function gitVerbList() {
|
|
26465
|
+
const names = DEVELOPER_GIT_VERBS.map((verb) => `\`git ${verb}\``);
|
|
26466
|
+
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1] ?? ""}`;
|
|
26467
|
+
}
|
|
26406
26468
|
function buildDeveloperPrompt(parts) {
|
|
26407
26469
|
const { story } = parts.story;
|
|
26408
26470
|
const extra = parts.story.extraInputs ?? [];
|
|
@@ -26472,7 +26534,9 @@ function buildDeveloperPrompt(parts) {
|
|
|
26472
26534
|
...parts.commands.length === 0 ? ["- (this repo declares no commands in workspace.yml — the DoD will run nothing)"] : parts.commands.map((command2) => `- \`${command2}\``),
|
|
26473
26535
|
"",
|
|
26474
26536
|
...parts.testFast === undefined ? [] : [...testFastRule(parts.testFast.fast, parts.testFast.full), ""],
|
|
26475
|
-
|
|
26537
|
+
`Commit with \`git add\` and \`git commit\`. The git verbs you hold are exactly ${gitVerbList()}.`,
|
|
26538
|
+
"`git restore <path>` is how to put a file back (there is no checkout in that list). Nothing",
|
|
26539
|
+
"else about git is yours to do.",
|
|
26476
26540
|
"",
|
|
26477
26541
|
"## Rules",
|
|
26478
26542
|
"",
|
|
@@ -28581,6 +28645,68 @@ function verdictReviewer(checkPayload, lastSpawn) {
|
|
|
28581
28645
|
return lastSpawn;
|
|
28582
28646
|
}
|
|
28583
28647
|
|
|
28648
|
+
// src/core/build/refusalKind.ts
|
|
28649
|
+
var MAX_SEPARATOR_RETRIES = 1;
|
|
28650
|
+
function classifyRefusal(command2) {
|
|
28651
|
+
const separator = unquotedShellSeparator(command2);
|
|
28652
|
+
if (separator !== null)
|
|
28653
|
+
return { kind: "separator", separator };
|
|
28654
|
+
const argv = command2.trim().split(/\s+/);
|
|
28655
|
+
if (argv[0] !== "git")
|
|
28656
|
+
return { kind: "unknown" };
|
|
28657
|
+
const verb = argv[1] ?? "";
|
|
28658
|
+
if (verb === "" || verb.startsWith("-"))
|
|
28659
|
+
return { kind: "unknown" };
|
|
28660
|
+
if (isDeveloperGitVerb(verb))
|
|
28661
|
+
return { kind: "unknown" };
|
|
28662
|
+
return { kind: "verb", verb, equivalent: grantedEquivalent(verb, argv.slice(2)) };
|
|
28663
|
+
}
|
|
28664
|
+
function grantedEquivalent(verb, args) {
|
|
28665
|
+
if (verb === "checkout") {
|
|
28666
|
+
const switching = args.some((a) => a === "-b" || a === "-B" || a === "--orphan" || a === "-t" || a === "--track");
|
|
28667
|
+
if (switching)
|
|
28668
|
+
return null;
|
|
28669
|
+
const hasTarget = args.some((a) => !a.startsWith("-")) || args.includes("--");
|
|
28670
|
+
return hasTarget ? "git restore <path>" : null;
|
|
28671
|
+
}
|
|
28672
|
+
if (verb === "reset") {
|
|
28673
|
+
const mode = args.some((a) => /^--(hard|soft|mixed|merge|keep)$/.test(a));
|
|
28674
|
+
if (mode)
|
|
28675
|
+
return null;
|
|
28676
|
+
if (args.includes("--"))
|
|
28677
|
+
return "git restore --staged <path>";
|
|
28678
|
+
const [first2, second] = args.filter((a) => !a.startsWith("-"));
|
|
28679
|
+
if (first2 === "HEAD" && second !== undefined)
|
|
28680
|
+
return "git restore --staged <path>";
|
|
28681
|
+
return null;
|
|
28682
|
+
}
|
|
28683
|
+
return null;
|
|
28684
|
+
}
|
|
28685
|
+
function refusalCure(kind) {
|
|
28686
|
+
switch (kind.kind) {
|
|
28687
|
+
case "separator":
|
|
28688
|
+
return "run each command alone — shell separators split a line into subcommands that each need their own grant";
|
|
28689
|
+
case "verb":
|
|
28690
|
+
return kind.equivalent === null ? `\`git ${kind.verb}\` is not granted` : `\`git ${kind.verb}\` is not granted; use \`${kind.equivalent}\``;
|
|
28691
|
+
case "unknown":
|
|
28692
|
+
return "";
|
|
28693
|
+
}
|
|
28694
|
+
}
|
|
28695
|
+
function withCure(sentence, command2) {
|
|
28696
|
+
const cure = refusalCure(classifyRefusal(command2));
|
|
28697
|
+
return cure === "" ? sentence : `${sentence}. The cure: ${cure}`;
|
|
28698
|
+
}
|
|
28699
|
+
function separatorCurePrefix(command2) {
|
|
28700
|
+
return [
|
|
28701
|
+
`Your previous command \`${command2}\` was refused because it chains commands. Run each command alone.`,
|
|
28702
|
+
"The permission layer splits a line at every shell separator (`&&`, `;`, `|`, `>`, `2>&1`, `$()`)",
|
|
28703
|
+
"and each fragment must match its own grant, so a compound line is refused even when every",
|
|
28704
|
+
"command in it is allowed on its own. This is the one retry: a second refusal blocks the story.",
|
|
28705
|
+
""
|
|
28706
|
+
].join(`
|
|
28707
|
+
`);
|
|
28708
|
+
}
|
|
28709
|
+
|
|
28584
28710
|
// src/core/build/review.ts
|
|
28585
28711
|
var VERDICT_WORDS = ["approve", "fixlist", "changes"];
|
|
28586
28712
|
var VERDICT_ENUM = VERDICT_WORDS.join("|");
|
|
@@ -28752,7 +28878,7 @@ function renderReviewLog(outcome) {
|
|
|
28752
28878
|
`- Branch: \`${outcome.branch}\` → \`${outcome.epicBranch}\` (${mergeWord(outcome)})`,
|
|
28753
28879
|
`- Commit: ${outcome.commit ?? "(none)"}`,
|
|
28754
28880
|
...outcome.developerError === null ? [] : [`- Developer: **FAILED** — ${outcome.developerError}`],
|
|
28755
|
-
...outcome.permissionRefused == null ? [] : [`- 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"],
|
|
28881
|
+
...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)],
|
|
28756
28882
|
...outcome.budgetDeath == null ? [] : [`- Developer: died on its per-story cap — ${outcome.budgetDeath}; the tree held work, ` + "so the Definition of Done below decided"],
|
|
28757
28883
|
"",
|
|
28758
28884
|
"## Definition of done",
|
|
@@ -29354,7 +29480,7 @@ function renderBuildHandoff(parts) {
|
|
|
29354
29480
|
"",
|
|
29355
29481
|
...notDone.length === 0 && (parts.carried ?? []).length === 0 && (parts.unreadableStories ?? []).length === 0 && (parts.foreignWork ?? []).length === 0 && (parts.notStarted ?? []).length === 0 && refusedButMeasured.length === 0 && diedButMeasured.length === 0 ? [`- none — every scheduled story reached \`done\` and no carried finding is unowned ` + `[src: absent:04-build/log]`] : [],
|
|
29356
29482
|
...notDone.map((o) => `- ${o.id} is \`${o.status}\` and needs a human: ${o.reason ?? "see the review"} ` + `[src: ${o.reviewRel}:1]`),
|
|
29357
|
-
...refusedButMeasured.map((o) => `- ${o.id}'s developer had \`${o.permissionRefused ?? ""}\` refused for approval by the agent's own ` + `permission layer; its tree held committed work, so the Definition of Done decided and the story is ` + `\`${o.status}\` — a person may still want to know the command it could not run [src: ${o.reviewRel}:1]`),
|
|
29483
|
+
...refusedButMeasured.map((o) => withCure(`- ${o.id}'s developer had \`${o.permissionRefused ?? ""}\` refused for approval by the agent's own ` + `permission layer; its tree held committed work, so the Definition of Done decided and the story is ` + `\`${o.status}\` — a person may still want to know the command it could not run`, o.permissionRefused ?? "") + ` [src: ${o.reviewRel}:1]`),
|
|
29358
29484
|
...diedButMeasured.map((o) => `- ${o.id}'s developer died on its per-story cap (${o.budgetDeath ?? ""}) with work in its tree, ` + `so the Definition of Done decided and the story is \`${o.status}\` — the plan priced this story ` + `below what it cost, which is worth knowing before the next one [src: ${o.reviewRel}:1]`),
|
|
29359
29485
|
...(parts.notStarted ?? []).map((row2) => `- ${row2.id} was scheduled and never started, and is still \`${row2.status}\` — ` + `${row2.reason} [src: ${row2.rel}:1]`),
|
|
29360
29486
|
...carriedBullets(parts.carried ?? []),
|
|
@@ -29769,9 +29895,9 @@ function quoteLine(text3, line) {
|
|
|
29769
29895
|
if (found === undefined || found.trim() === "")
|
|
29770
29896
|
return null;
|
|
29771
29897
|
const trimmed = found.trim();
|
|
29772
|
-
return trimmed.length <=
|
|
29898
|
+
return trimmed.length <= MAX_QUOTED_CHARS3 ? trimmed : `${trimmed.slice(0, MAX_QUOTED_CHARS3)}…`;
|
|
29773
29899
|
}
|
|
29774
|
-
var
|
|
29900
|
+
var MAX_QUOTED_CHARS3 = 120;
|
|
29775
29901
|
function describeHandoff2(rel, validation, text3 = "") {
|
|
29776
29902
|
if (validation.ok)
|
|
29777
29903
|
return [];
|
|
@@ -35532,6 +35658,10 @@ function storyWorktreePath(root2, repo, runId, storyId) {
|
|
|
35532
35658
|
async function refreshStoryBase(parts) {
|
|
35533
35659
|
const id = parts.storyId;
|
|
35534
35660
|
const base2 = await baseStateOf(parts.repoDir, parts.branch, parts.epicBranch);
|
|
35661
|
+
if (base2.uncounted !== null && base2.branchSha !== "") {
|
|
35662
|
+
parts.lines.push(` · ${id}: ${base2.uncounted} — \`${parts.branch}\` was left exactly as it is, ` + `and the dispatch below is on its current tip`);
|
|
35663
|
+
return;
|
|
35664
|
+
}
|
|
35535
35665
|
if (base2.state === "current")
|
|
35536
35666
|
return;
|
|
35537
35667
|
const where = relative14(parts.root, parts.worktree) || parts.worktree;
|
|
@@ -35646,6 +35776,24 @@ async function rescueUncommitted(parts) {
|
|
|
35646
35776
|
return { sha, branch: parts.branch, worktree: null, failure: null };
|
|
35647
35777
|
});
|
|
35648
35778
|
}
|
|
35779
|
+
async function updateStoryBase(parts) {
|
|
35780
|
+
const base2 = await baseStateOf(parts.repoDir, parts.branch, parts.epicBranch);
|
|
35781
|
+
if (base2.state === "current" && base2.uncounted === null)
|
|
35782
|
+
return { kind: "current", why: null };
|
|
35783
|
+
await assertWorktreeOn(parts.worktree, parts.branch, "story worktree");
|
|
35784
|
+
const from = await headSha(parts.worktree);
|
|
35785
|
+
const merged = await mergeNoFf(parts.worktree, parts.epicBranch, `sync(${parts.storyId}): \`${parts.epicBranch}\` moved under this story before it merged back`);
|
|
35786
|
+
if (!merged.ok)
|
|
35787
|
+
return { kind: "blocked", conflicts: merged.conflicts, detail: merged.detail };
|
|
35788
|
+
const to = await headSha(parts.worktree);
|
|
35789
|
+
if (to === from || to === "")
|
|
35790
|
+
return { kind: "current", why: base2.uncounted };
|
|
35791
|
+
return { kind: "updated", from, to, behind: base2.behind, uncounted: base2.uncounted };
|
|
35792
|
+
}
|
|
35793
|
+
function staleBaseConflict(branch, epicBranch, conflicts, detail, where) {
|
|
35794
|
+
const files = conflicts.length > 0 ? conflicts.join(", ") : detail;
|
|
35795
|
+
return `\`${epicBranch}\` moved since \`${branch}\` was cut, and bringing the story up to it ` + `CONFLICTS in ${files} — nothing was merged and nothing is half-applied. ` + `Resolve it in ${where} (\`git merge ${epicBranch}\`), then reopen the story`;
|
|
35796
|
+
}
|
|
35649
35797
|
|
|
35650
35798
|
// src/core/build/entryProbe.ts
|
|
35651
35799
|
import { accessSync, constants, existsSync as existsSync72, statSync as statSync20 } from "node:fs";
|
|
@@ -35892,7 +36040,7 @@ function asideBranchOf(epicBranch, ownerRunId) {
|
|
|
35892
36040
|
return `${epicBranch}${ASIDE_SEPARATOR}${ownerRunId}`;
|
|
35893
36041
|
}
|
|
35894
36042
|
function uncountedReason(base2, branch) {
|
|
35895
|
-
return
|
|
36043
|
+
return `${uncountedCount(base2, branch)}, and an uncounted branch is never deleted`;
|
|
35896
36044
|
}
|
|
35897
36045
|
async function releaseEpicBranch(parts) {
|
|
35898
36046
|
const { repoDir, branch } = parts;
|
|
@@ -35902,7 +36050,7 @@ async function releaseEpicBranch(parts) {
|
|
|
35902
36050
|
if (checkout !== null) {
|
|
35903
36051
|
return { kind: "kept", branch, reason: `it is checked out in ${checkout}` };
|
|
35904
36052
|
}
|
|
35905
|
-
const commits = await
|
|
36053
|
+
const commits = await commitsBetween(repoDir, parts.base, branch);
|
|
35906
36054
|
if (commits === null)
|
|
35907
36055
|
return { kind: "kept", branch, reason: uncountedReason(parts.base, branch) };
|
|
35908
36056
|
const sha = await shaOf(repoDir, branch);
|
|
@@ -38645,6 +38793,10 @@ async function rederiveImplicitPlan(ctx, workspace, parts, lines) {
|
|
|
38645
38793
|
const content = implicitPlanContent(parts);
|
|
38646
38794
|
const storyBranch = storyBranchOf(ctx.runId, IMPLICIT_STORY_ID);
|
|
38647
38795
|
const commits = await commitsBetween(repoDirOf(workspace, content.repo), content.branch, storyBranch);
|
|
38796
|
+
if (commits === null) {
|
|
38797
|
+
kept(uncountedCount(content.branch, storyBranch));
|
|
38798
|
+
return;
|
|
38799
|
+
}
|
|
38648
38800
|
if (commits > 0) {
|
|
38649
38801
|
kept(`\`${storyBranch}\` carries ${String(commits)} commit(s) beyond \`${content.branch}\``);
|
|
38650
38802
|
return;
|
|
@@ -39102,55 +39254,64 @@ class BuildSession {
|
|
|
39102
39254
|
};
|
|
39103
39255
|
}
|
|
39104
39256
|
const handed = await headSha(story.worktree);
|
|
39105
|
-
|
|
39106
|
-
const capDeath = developer.error !== null && diedOnCap(developer.error);
|
|
39257
|
+
let developer = await this.spawnDeveloper(story);
|
|
39107
39258
|
let budgetDeath = null;
|
|
39108
|
-
|
|
39109
|
-
|
|
39110
|
-
|
|
39111
|
-
|
|
39112
|
-
|
|
39113
|
-
|
|
39114
|
-
|
|
39115
|
-
|
|
39116
|
-
|
|
39117
|
-
story,
|
|
39118
|
-
|
|
39119
|
-
|
|
39120
|
-
|
|
39121
|
-
|
|
39122
|
-
|
|
39123
|
-
|
|
39124
|
-
|
|
39259
|
+
let spent = 0;
|
|
39260
|
+
let separatorRetries = 0;
|
|
39261
|
+
for (;; ) {
|
|
39262
|
+
spent = round25(spent + developer.cost);
|
|
39263
|
+
const capDeath = developer.error !== null && diedOnCap(developer.error);
|
|
39264
|
+
if (developer.error !== null) {
|
|
39265
|
+
const proven2 = capDeath && await workSince({
|
|
39266
|
+
workspaceRoot: this.workspace.root,
|
|
39267
|
+
repoDir: story.repoDir,
|
|
39268
|
+
worktree: story.worktree,
|
|
39269
|
+
since: handed
|
|
39270
|
+
});
|
|
39271
|
+
if (!proven2) {
|
|
39272
|
+
return {
|
|
39273
|
+
story,
|
|
39274
|
+
cost: spent,
|
|
39275
|
+
dod: [],
|
|
39276
|
+
commit: null,
|
|
39277
|
+
failure: null,
|
|
39278
|
+
developerError: developer.error,
|
|
39279
|
+
before
|
|
39280
|
+
};
|
|
39281
|
+
}
|
|
39282
|
+
budgetDeath = developer.error;
|
|
39283
|
+
this.capDeaths.set(story.planned.story.id, developer.error);
|
|
39125
39284
|
}
|
|
39126
|
-
|
|
39127
|
-
|
|
39128
|
-
}
|
|
39129
|
-
const spent = developer.cost;
|
|
39130
|
-
if (developer.refused !== null) {
|
|
39285
|
+
if (developer.refused === null)
|
|
39286
|
+
break;
|
|
39131
39287
|
const proven = await workSince({
|
|
39132
39288
|
workspaceRoot: this.workspace.root,
|
|
39133
39289
|
repoDir: story.repoDir,
|
|
39134
39290
|
worktree: story.worktree,
|
|
39135
39291
|
since: handed
|
|
39136
39292
|
});
|
|
39137
|
-
if (
|
|
39138
|
-
|
|
39139
|
-
|
|
39140
|
-
cost: spent,
|
|
39141
|
-
dod: [],
|
|
39142
|
-
commit: null,
|
|
39143
|
-
failure: permissionBlockReason(developer.refused),
|
|
39144
|
-
developerError: null,
|
|
39145
|
-
before
|
|
39146
|
-
};
|
|
39293
|
+
if (proven) {
|
|
39294
|
+
this.refusals.set(story.planned.story.id, developer.refused);
|
|
39295
|
+
break;
|
|
39147
39296
|
}
|
|
39148
|
-
|
|
39297
|
+
if (classifyRefusal(developer.refused).kind === "separator" && separatorRetries < MAX_SEPARATOR_RETRIES) {
|
|
39298
|
+
separatorRetries += 1;
|
|
39299
|
+
developer = await this.spawnDeveloper(story, { after: developer.refused });
|
|
39300
|
+
continue;
|
|
39301
|
+
}
|
|
39302
|
+
return {
|
|
39303
|
+
story,
|
|
39304
|
+
cost: spent,
|
|
39305
|
+
dod: [],
|
|
39306
|
+
commit: null,
|
|
39307
|
+
failure: permissionBlockReason(developer.refused, { retried: separatorRetries > 0 }),
|
|
39308
|
+
developerError: null,
|
|
39309
|
+
before
|
|
39310
|
+
};
|
|
39149
39311
|
}
|
|
39150
39312
|
const dod = await this.runDod(story);
|
|
39151
|
-
|
|
39152
|
-
|
|
39153
|
-
const failing = dod.find((r) => dodRefused(r) || r.exitCode !== 0 || r.timedOut);
|
|
39313
|
+
if (!this.dodProves(dod)) {
|
|
39314
|
+
const failing = dodFailure(dod);
|
|
39154
39315
|
const why = failing === undefined ? "the story declares no dod commands, so nothing could prove it" : dodFailureReason(failing, story.planned.story.repo);
|
|
39155
39316
|
return {
|
|
39156
39317
|
story,
|
|
@@ -39194,18 +39355,49 @@ class BuildSession {
|
|
|
39194
39355
|
await this.block(story, "the story produced no commit to review", half.cost, dod);
|
|
39195
39356
|
return "settled";
|
|
39196
39357
|
}
|
|
39358
|
+
const update = await updateStoryBase({
|
|
39359
|
+
storyId: story.planned.story.id,
|
|
39360
|
+
repoDir: story.repoDir,
|
|
39361
|
+
worktree: story.worktree,
|
|
39362
|
+
branch: story.branch,
|
|
39363
|
+
epicBranch: story.epicBranch
|
|
39364
|
+
});
|
|
39365
|
+
if (update.kind === "blocked") {
|
|
39366
|
+
await this.block(story, staleBaseConflict(story.branch, story.epicBranch, update.conflicts, update.detail, relative16(this.ctx.root, story.worktree) || story.worktree), half.cost, dod, { commit: commit2, conflicts: update.conflicts });
|
|
39367
|
+
return "settled";
|
|
39368
|
+
}
|
|
39369
|
+
let proven = dod;
|
|
39370
|
+
if (update.kind === "updated") {
|
|
39371
|
+
this.lines.push(` · ${story.planned.story.id}: \`${story.branch}\` was ${String(update.behind)} commit(s) behind ` + `\`${story.epicBranch}\`${update.uncounted === null ? "" : ` (${update.uncounted})`} — merged the epic ` + `into it (${update.from.slice(0, 7)} → ${update.to.slice(0, 7)}) and re-ran the DoD on the result`);
|
|
39372
|
+
this.ctx.emit("story.base_updated", {
|
|
39373
|
+
phase: this.ctx.phaseId,
|
|
39374
|
+
story: story.planned.story.id,
|
|
39375
|
+
repo: story.planned.story.repo,
|
|
39376
|
+
branch: story.branch,
|
|
39377
|
+
base: story.epicBranch,
|
|
39378
|
+
from: update.from,
|
|
39379
|
+
to: update.to,
|
|
39380
|
+
commits: update.behind
|
|
39381
|
+
});
|
|
39382
|
+
proven = await this.runDod(story);
|
|
39383
|
+
if (!this.dodProves(proven)) {
|
|
39384
|
+
const failing = dodFailure(proven);
|
|
39385
|
+
await this.block(story, `${failing === undefined ? "the story declares no dod commands, so nothing could prove it" : dodFailureReason(failing, story.planned.story.repo)}` + ` — re-run after \`${story.epicBranch}\` moved under \`${story.branch}\` ` + `(${update.from.slice(0, 7)} → ${update.to.slice(0, 7)}); nothing was merged`, half.cost, proven, { commit: commit2 });
|
|
39386
|
+
return "settled";
|
|
39387
|
+
}
|
|
39388
|
+
}
|
|
39197
39389
|
const carried = await commitsBetween(story.repoDir, story.epicBranch, story.branch);
|
|
39198
39390
|
const epicShaBefore = await fullShaOf(story.repoDir, story.epicBranch);
|
|
39199
39391
|
const merge = await this.mergeIntoEpic(story);
|
|
39200
39392
|
if (!merge.ok) {
|
|
39201
|
-
await this.block(story, `merge into \`${story.epicBranch}\` failed: ${merge.detail}`, half.cost,
|
|
39393
|
+
await this.block(story, `merge into \`${story.epicBranch}\` failed: ${merge.detail}`, half.cost, proven, {
|
|
39202
39394
|
commit: commit2,
|
|
39203
39395
|
conflicts: merge.conflicts
|
|
39204
39396
|
});
|
|
39205
39397
|
return "settled";
|
|
39206
39398
|
}
|
|
39207
39399
|
this.noteMerged(story, carried);
|
|
39208
|
-
return await this.reviewAndSettle(story,
|
|
39400
|
+
return await this.reviewAndSettle(story, proven, commit2, half.cost, carried, epicShaBefore);
|
|
39209
39401
|
}
|
|
39210
39402
|
async reviewAndSettle(story, dod, commit2, priorCost, carried, epicBase, supplied) {
|
|
39211
39403
|
if (supplied === undefined && this.ctx.attendedByHost) {
|
|
@@ -39481,9 +39673,8 @@ class BuildSession {
|
|
|
39481
39673
|
}
|
|
39482
39674
|
async pipelineFromDod(story, developerCost) {
|
|
39483
39675
|
const dod = await this.runDod(story);
|
|
39484
|
-
|
|
39485
|
-
|
|
39486
|
-
const failing = dod.find((r) => dodRefused(r) || r.exitCode !== 0 || r.timedOut);
|
|
39676
|
+
if (!this.dodProves(dod)) {
|
|
39677
|
+
const failing = dodFailure(dod);
|
|
39487
39678
|
await this.block(story, failing === undefined ? "the story declares no dod commands, so nothing could prove it" : dodFailureReason(failing, story.planned.story.repo), developerCost, dod);
|
|
39488
39679
|
return "settled";
|
|
39489
39680
|
}
|
|
@@ -39586,7 +39777,7 @@ class BuildSession {
|
|
|
39586
39777
|
advisories: this.advisories
|
|
39587
39778
|
});
|
|
39588
39779
|
}
|
|
39589
|
-
async spawnDeveloper(story) {
|
|
39780
|
+
async spawnDeveloper(story, retry = null) {
|
|
39590
39781
|
const cap2 = developerCap(this.capParts, story.planned.story.id, story.attempt);
|
|
39591
39782
|
const commands = this.repoCommands(story.planned.story.repo);
|
|
39592
39783
|
this.ctx.emit("agent.spawned", {
|
|
@@ -39595,10 +39786,11 @@ class BuildSession {
|
|
|
39595
39786
|
role: "developer",
|
|
39596
39787
|
model: this.model(),
|
|
39597
39788
|
effort: this.ctx.effort,
|
|
39598
|
-
max_budget_usd: cap2
|
|
39789
|
+
max_budget_usd: cap2,
|
|
39790
|
+
...retry === null ? {} : { retry: "separator-cure", retry_after: retry.after }
|
|
39599
39791
|
}, 0, "developer");
|
|
39600
39792
|
const agent = await spawnAgent({
|
|
39601
|
-
prompt: this.developerPrompt(story),
|
|
39793
|
+
prompt: (retry === null ? "" : separatorCurePrefix(retry.after)) + this.developerPrompt(story),
|
|
39602
39794
|
model: this.model(),
|
|
39603
39795
|
effort: this.ctx.effort,
|
|
39604
39796
|
maxBudgetUsd: cap2,
|
|
@@ -39640,6 +39832,9 @@ class BuildSession {
|
|
|
39640
39832
|
});
|
|
39641
39833
|
return { cost: round25(agent.costUsd), error, refused: null };
|
|
39642
39834
|
}
|
|
39835
|
+
dodProves(dod) {
|
|
39836
|
+
return dod.length === 0 ? dodIsSatisfiedEmpty(this.plan) : dodGreen({ dod });
|
|
39837
|
+
}
|
|
39643
39838
|
async runDod(story) {
|
|
39644
39839
|
const scoped = await this.scopedParts(story);
|
|
39645
39840
|
const results = await runStoryDod({
|
|
@@ -40008,7 +40203,7 @@ class BuildSession {
|
|
|
40008
40203
|
if (pruning && (rescued === null || rescued.worktree === null)) {
|
|
40009
40204
|
await removeWorktree(story.repoDir, story.worktree);
|
|
40010
40205
|
}
|
|
40011
|
-
this.lines.push(` ${status === "done" ? "✓" : "·"} ${id} → \`${status}\`` + (outcome.reason === null ? "" : ` (${outcome.reason})`) + (outcome.permissionRefused == null ? "" : ` —
|
|
40206
|
+
this.lines.push(` ${status === "done" ? "✓" : "·"} ${id} → \`${status}\`` + (outcome.reason === null ? "" : ` (${outcome.reason})`) + (outcome.permissionRefused == null ? "" : ` — ${withCure(`\`${outcome.permissionRefused}\` was refused for approval; the tree held committed work, so the DoD decided`, outcome.permissionRefused)}`));
|
|
40012
40207
|
}
|
|
40013
40208
|
writeLog(outcome) {
|
|
40014
40209
|
const dir = join100(this.ctx.runDir, BUILD_PHASE, LOG_DIR);
|
|
@@ -40691,15 +40886,12 @@ function developerTools(repoCommands, options = {}) {
|
|
|
40691
40886
|
...BASE_TOOLS,
|
|
40692
40887
|
...options.skills === true ? ["Skill"] : [],
|
|
40693
40888
|
...repoCommands.flatMap((command2) => bashGrantsFor(command2)),
|
|
40694
|
-
|
|
40695
|
-
"Bash(git commit *)",
|
|
40696
|
-
"Bash(git rm *)",
|
|
40697
|
-
"Bash(git mv *)",
|
|
40698
|
-
"Bash(git restore *)"
|
|
40889
|
+
...developerGitGrants()
|
|
40699
40890
|
];
|
|
40700
40891
|
}
|
|
40701
|
-
function permissionBlockReason(command2) {
|
|
40702
|
-
|
|
40892
|
+
function permissionBlockReason(command2, options = {}) {
|
|
40893
|
+
const base2 = `permission — \`${command2}\` was refused for approval by the agent's own permission layer, ` + "and a headless turn has nobody to approve it: the same allowance would refuse it again, " + "so this attempt was not repeated" + (options.retried === true ? " beyond the one re-spawn with the cure stated, which was refused too" : "");
|
|
40894
|
+
return withCure(base2, command2);
|
|
40703
40895
|
}
|
|
40704
40896
|
function diedOnCap(error) {
|
|
40705
40897
|
return error.includes("Reached maximum budget");
|
|
@@ -52045,6 +52237,8 @@ function bullet2(item, trail) {
|
|
|
52045
52237
|
return `${prefix5}story ${text3(payload2.story) || "?"}'s \`touches:\` WIDENED by ${actor}` + ` (${basisOf(payload2)})` + ` — +${pathList(payload2.paths)}` + ` (${String(lengthOf(payload2.before))} → ${String(lengthOf(payload2.after))} path(s))` + `${note(payload2.note)}`;
|
|
52046
52238
|
case "story.base_fastforwarded":
|
|
52047
52239
|
return `${prefix5}story ${text3(payload2.story) || "?"}'s base fast-forwarded to` + ` \`${text3(payload2.base) || "?"}\` — ${text3(payload2.from) || "?"} → ${text3(payload2.to) || "?"}` + ` (${String(payload2.commits ?? "?")} commit(s))`;
|
|
52240
|
+
case "story.base_updated":
|
|
52241
|
+
return `${prefix5}story ${text3(payload2.story) || "?"}'s base UPDATED to` + ` \`${text3(payload2.base) || "?"}\` before merging — ${text3(payload2.from) || "?"} →` + ` ${text3(payload2.to) || "?"} (${String(payload2.commits ?? "?")} commit(s) behind), DoD re-run`;
|
|
52048
52242
|
case "epic.released":
|
|
52049
52243
|
return `${prefix5}epic \`${text3(payload2.branch) || "?"}\` of run ${text3(payload2.owner) || "?"} ` + (text3(payload2.outcome) === "deleted" ? `deleted — no commit beyond \`${text3(payload2.base) || "?"}\`` : `renamed to \`${text3(payload2.renamed_to) || "?"}\` — ${String(payload2.commits ?? "?")} commit(s) ` + `beyond \`${text3(payload2.base) || "?"}\` survive there`) + ` (${text3(payload2.via) || "?"})`;
|
|
52050
52244
|
case "story.work_rescued":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tldr-experts",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.3",
|
|
4
4
|
"description": "tldr-experts: an evidence-first, file-based AI development framework - five stages, a gate on every one, and every claim cited or refused. Installs the `tldrx` (and `tldr-experts`) command. Beta.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alan Martinez",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$doc": "Shape verified from https://code.claude.com/docs/en/plugins.md (Quickstart > Create the plugin manifest). Fields used here: name, description, version, author.name. Only plugin.json goes inside .claude-plugin/; skills/, agents/ and hooks/ live at the plugin root.",
|
|
3
3
|
"name": "tldrx",
|
|
4
4
|
"description": "tldr-experts: an evidence-first, file-based AI development framework. Five stages, a gate on every one, every claim cited or refused. Beta.",
|
|
5
|
-
"version": "0.18.
|
|
5
|
+
"version": "0.18.3",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Alan Martinez"
|
|
8
8
|
}
|