tldr-experts 0.21.0 → 0.23.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 +91 -0
- package/README.md +2 -0
- package/dist/hooks/budget-gate.js +1 -1
- package/dist/hooks/{chunk-f5wkn522.js → chunk-1p5027fn.js} +3 -0
- package/dist/hooks/{chunk-6qnsx1tz.js → chunk-ah1pdax3.js} +1 -1
- package/dist/hooks/session-start.js +2 -2
- package/dist/hooks/statusline.js +2 -2
- package/dist/tldrx.js +207 -88
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.23.0 — 2026-09-14
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A fix list with nothing to fix now settles the story `done` instead of buying a developer round
|
|
8
|
+
(#295, first half).** Measured on a live unattended run (0.20.0): the reviewer signed a two-finding
|
|
9
|
+
fix list, both `docs` and both `defer-with-log`, and the story still parked at `review` — the Build
|
|
10
|
+
then spawned a fix round for it, which died four times on command-form refusals (#294) with nothing
|
|
11
|
+
to fix. The rule that a `fixlist` verdict parks the story was written when a fix list meant "here is
|
|
12
|
+
what the author must do next"; #255 then made all-deferred lists the normal outcome of a docs-only
|
|
13
|
+
review and never adjusted who settles the story. Now the count the operator line already prints —
|
|
14
|
+
findings still `fix-now` in the PARSED list, #255's routing included — decides: zero means the
|
|
15
|
+
reviewer signed and nothing is owed a developer, so the story is `done` with `verdict: fixlist` on
|
|
16
|
+
its record. The artifact is still written and its deferred findings still reach `retro.md`: settling
|
|
17
|
+
the story does not settle what it deferred. One `fix-now` finding parks it exactly as before.
|
|
18
|
+
- **`story reopen --as-is` can settle a story whose work is already on the epic and whose review
|
|
19
|
+
never completed — review-only, merging nothing (#295, second half).** Under merge-before-review
|
|
20
|
+
(#282) a story's diff can sit entirely on the epic while nothing has judged it: measured twice on
|
|
21
|
+
2026-09-13, a reviewer that died and a later attempt that died on a bare `echo $?` with no work.
|
|
22
|
+
#279's guard — the branch must carry a commit the epic has not got — is right for a branch with no
|
|
23
|
+
work and blind to this mirror image, where there is nothing to MERGE and something to SETTLE; the
|
|
24
|
+
operator's only moves were inventing a commit for `--as-is` to take, which corrupts the
|
|
25
|
+
measurement, or leaving the story blocked with its dependents (#280). The guard is not relaxed. A
|
|
26
|
+
new, named case sits beside it: the review ledger now keeps the story's last recorded merge
|
|
27
|
+
(`commit` + `epic_base`) across reopen boundaries — a reopen resets what counts against a story,
|
|
28
|
+
not what the epic holds — and `--as-is` over a branch that carries nothing beyond its epic asks
|
|
29
|
+
whether anything judged that merge. `n-a` and `error` mean nothing did, and the turn runs
|
|
30
|
+
review-only: the branch is brought up to the epic `--ff-only` (nothing of its own to lose), the
|
|
31
|
+
DoD runs on the epic head, the reviewer is handed the recorded range, and nothing is merged —
|
|
32
|
+
`task.done` carries `as_is_reason: review-only`, the review log's developer line says so and names
|
|
33
|
+
the commit it reviewed. A review that STANDS (`changes`, `approve`, `fixlist`) still refuses, and
|
|
34
|
+
the refusal now names that verdict beside the #279 sentence: what is owed is a fix, not a third
|
|
35
|
+
opinion on the same bytes — which is also why the #279 pin test (two `changes` over a reset
|
|
36
|
+
branch) is untouched. The issue proposed "any verdict but `approve`"; that would have flipped that
|
|
37
|
+
pin, so the boundary shipped is the one in the issue's title: the review never completed.
|
|
38
|
+
- **A turn's failure record can no longer borrow the word `success` from the provider (#296).**
|
|
39
|
+
MEASURED on two live unattended runs while the account sat over its usage limit: both a
|
|
40
|
+
developer and a reviewer died with `claude exited 1 with is_error=true: success`. That line is
|
|
41
|
+
two layers' verdicts concatenated and nothing reconciling them — our `is_error` beside the
|
|
42
|
+
provider's own `subtype`, which was the literal `"success"` — and the ONLY human-readable word
|
|
43
|
+
on it was the one that was wrong. Tomorrow's reader learns the story failed; the truth is it
|
|
44
|
+
never ran, which is exactly the direction §7 forbids an audit record to lie in. `describe()`
|
|
45
|
+
is reached only for a FAILURE, so a subtype asserting the turn succeeded cannot be that
|
|
46
|
+
failure's reason: it is now refused as a reason and reported as the contradiction it is —
|
|
47
|
+
`claude exited 1 with is_error=true: no reason named (the provider's own subtype said
|
|
48
|
+
"success")` — keeping BOTH facts rather than dropping either, because a record that hides the
|
|
49
|
+
disagreement is only marginally better than one that resolves it wrongly. A failure with no
|
|
50
|
+
reason anywhere now says `no reason named` instead of trailing off after the exit code. A
|
|
51
|
+
reason the provider DID name (`Reached maximum budget ($0.26)`) and a genuine error subtype
|
|
52
|
+
(`error_during_execution`) are printed exactly as before. **This does not classify the limit
|
|
53
|
+
and does not change any retry**: what `subtype`, `errors[]` or HTTP status either provider
|
|
54
|
+
emits on a quota limit is still unmeasured, and a detector built on a guessed signal is a
|
|
55
|
+
detector that never fires — so the record is fixed today and the cause stays honestly unnamed
|
|
56
|
+
until the raw document of a rate-limited turn is captured.
|
|
57
|
+
|
|
58
|
+
## 0.22.0 — 2026-09-14
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- **A developer may now READ its own tree, `git -C` is refused on purpose, and every cure says
|
|
63
|
+
WHY it is a cure (#287, #294).** Two issues, one edit, because they are the same defect seen
|
|
64
|
+
twice. The developer's git allowance held `add`, `commit`, `rm`, `mv`, `restore` and **no read
|
|
65
|
+
verb at all** — it could write its worktree and never look at it. Measured on a live
|
|
66
|
+
unattended run: a developer ran `git -C <worktree> log --oneline -5`, was refused, was
|
|
67
|
+
re-spawned with the cure, was refused again, and the story died — ~$5 for a command that
|
|
68
|
+
changes nothing. `status`, `log`, `diff` and `show` join the ONE constant the grant, the
|
|
69
|
+
prompt's list and the refusal classifier all read (`build/developerGrants.ts`), so all three
|
|
70
|
+
gained them in the same edit. **`-C <path>` (and `--git-dir`, `--work-tree`) stays ungranted
|
|
71
|
+
as a decision, and the code now says so**: `-C` points git at ANY directory — the epic
|
|
72
|
+
worktree, another story's worktree, the shared checkout — so granting it would turn a read
|
|
73
|
+
verb into a read of trees the story does not own, and a mutating verb into a write there. It
|
|
74
|
+
is its own refusal kind (`elsewhere`) whose cure is "drop `-C <path>` and run git from your
|
|
75
|
+
own worktree", never "ask for `-C`". The second half is the reason these shipped together:
|
|
76
|
+
the three cures we had — "run each command alone" (#278), "run `git log` from your own
|
|
77
|
+
worktree" (#287), "don't append `echo`" (#294) — all said WHAT and none said WHY, and an agent
|
|
78
|
+
that does not know why finds another way to do the same thing. #294 is the proof: three
|
|
79
|
+
consecutive developers on ONE story appended `; echo "EXIT:$?"` to a DoD command, each was
|
|
80
|
+
refused, and the cure kept telling them to separate the commands — but from their side the
|
|
81
|
+
`echo` was not an extra command, it was HOW you read an exit code, so they re-added it. A
|
|
82
|
+
`separator` classification now carries `capturing` (the chain exists only to capture the
|
|
83
|
+
outcome: an `echo` naming `$?`, or a redirect of the command's own output), and that cure, the
|
|
84
|
+
retry prefix and the developer prompt all append the same one clause, from one constant: the
|
|
85
|
+
facilitator re-runs the Definition of Done after you and records each command's exit code, so
|
|
86
|
+
the number the `echo` would print is written down whether you capture it or not. That
|
|
87
|
+
clause says only what this repo measures (`build/dodRunner.ts`) and only what holds for every
|
|
88
|
+
provider: whether an agent's own execution tool hands the exit code back to the model is a
|
|
89
|
+
property of the provider's CLI — measured for Claude Code's `Bash` tool, established nowhere
|
|
90
|
+
for `codex exec` — and the sentence goes into the prompt BOTH providers read, so asserting it
|
|
91
|
+
would have been this very bug one level up, a plausible explanation instead of a true one. The
|
|
92
|
+
developer prompt goldens moved for exactly those two paragraphs.
|
|
93
|
+
|
|
3
94
|
## 0.21.0 — 2026-09-13
|
|
4
95
|
|
|
5
96
|
### Fixed
|
package/README.md
CHANGED
|
@@ -335,6 +335,8 @@ back on the registry is 0.3.0.
|
|
|
335
335
|
|
|
336
336
|
| Version | Date | Status | Contains |
|
|
337
337
|
|---|---|---|---|
|
|
338
|
+
| 0.23.0 | 2026-09-14 | `beta` | `tldrx story reopen --as-is` now settles a story whose work is already merged into the epic but whose review never completed (`n-a` or `error` on its last recorded merge): a new, named review-only case that merges nothing and only routes the story to its reviewer, read from an additive ledger field; the existing refusal ("no commit the epic has not got") is untouched and now names the standing verdict when one exists (`changes`, `approve`, `fixlist` still refuse). A fix list whose findings are all deferred settles the story `done` instead of spawning a developer with nothing to fix, measured on a live run that paid for four such developers (#295). And a turn that died on a provider limit no longer records `success` borrowed from the provider's own subtype: the failure record says what the host saw (#296, first half — DETECTING the provider limit as its own non-execution kind is #298 and is NOT in this release). Minor release: `story reopen --as-is` behaves differently in a situation that exists today. |
|
|
339
|
+
| 0.22.0 | 2026-09-14 | `beta` | A developer may now READ its own tree — `git status`, `log`, `diff` and `show` join the one constant the grant, the developer prompt and the refusal classifier all read; measured on a live unattended run where a developer was refused `git -C <worktree> log` twice and the story died, ~$5 for a command that changes nothing. `-C <path>` (and `--git-dir`, `--work-tree`) stays ungranted as a decision with its own refusal kind, `elsewhere`, because it points git at trees the story does not own. And every refusal cure now says WHY it is a cure: three consecutive developers on one story re-appended `; echo "EXIT:$?"` to a DoD command because the cure said what to drop and never that the facilitator re-runs the Definition of Done itself and records each exit code (#287, #294). Minor release: the developer's git allowance grew and a new refusal kind was added. |
|
|
338
340
|
| 0.21.0 | 2026-09-13 | `beta` | `tldrx budget raise <phase> <usd> --stage <id>` now moves the stage's own `budget_usd` — the figure that actually sets a developer's and a reviewer's spawn ceiling — instead of the phase figure, which caps no spawn at all: measured on a live unattended run where raising the plan's per-story price and the phase ceiling moved a developer's cap by nothing, and only raising the stage figure moved it; a raise naming no `--stage` now says outright that it moved no spawn ceiling. And a reviewer a nearly-exhausted stage cannot fund is refused before it is spawned — the run that surfaced this handed a reviewer **$0.43**, which died before reading a line of diff and recorded `verdict: error`, parking the story with its dependents blocked; the refusal now costs $0 and records `verdict: n-a`, not an error the reviewer never formed (#244, #289). Minor release: a new flag, `--stage`, on `budget raise`. |
|
|
339
341
|
| 0.20.0 | 2026-09-13 | `beta` | `tldrx seed check <file\|dir>` runs a hand-written seed through the same importer chain `run new --seed` runs, plus the authoring rules four unattended runs paid to learn — bullets under 200 characters, `[src:]` citations that resolve, `dod` lines byte-equal to a declared command with no shell separator, `depends_on` wherever two stories touch one file, a `Recommended:` on every open question — and prints the budget split and per-story caps a run would set, creating no run and spending nothing; `tldrx install --claude` now writes a second managed skill, `tldrx-plan`, that reads `workspace.yml` and the repo tree and turns "I want X" into seeds that pass the same check, naming only declared tools (#291). And a permission refusal over a command the workspace's `commands:` could have granted now names the operator's cure on the first dead developer instead of the fourth, measured on a live unattended run where four developers died and two review rounds ran before the missing command turned out to be one `workspace.yml` line away (#285). Minor release: a new CLI verb, `tldrx seed check`. |
|
|
340
342
|
| 0.19.0 | 2026-09-13 | `beta` | `tldrx story reopen <id> --as-is --note "…"` settles a story a person finished by hand from its branch as it stands, spawning no developer — measured on a live unattended run where a hand-rebased branch had a green DoD and a clean merge-tree but no way to land, blocked by #271's since-the-spawn rule from a developer with nothing to do (#279). Everything after the missing developer is the ordinary path: the epic merges in first if it moved, the same DoD and reviewer judge the diff, the same merge lands it. The record never lies about who did the work — `story.reopened` carries `reason: as_is` with the actor and note, `task.done` carries `as_is`/`as_is_by`/`as_is_note`, and the review log names the branch and the signer. Two refusals are structural with no flag to skip either: a branch with no commit ahead of its epic, and a red DoD. The signature clears the moment a real developer attempt starts, so a requeued story after a `changes` verdict records a developer, not an as-is. Minor release: a new flag, `--as-is`, on `story reopen`. |
|
|
@@ -728,6 +728,9 @@ var KINDS = {
|
|
|
728
728
|
var MAX_FIXLIST_ROUNDS = STAGE_TUNING_DEFAULTS.fixlistRounds;
|
|
729
729
|
var FINDING_KINDS = ["correctness", "security", "docs", "style"];
|
|
730
730
|
|
|
731
|
+
// src/core/build/refusalKind.ts
|
|
732
|
+
var OUTCOME_ALREADY_REPORTED = "the facilitator re-runs the Definition of Done after you and records each command's exit " + "code, so the number the `echo` would print is measured and written down whether you " + "capture it or not";
|
|
733
|
+
|
|
731
734
|
// src/core/build/prompts.ts
|
|
732
735
|
var MAX_TOUCHED_BYTES = 64 * 1024;
|
|
733
736
|
var REVIEW_SCHEMA = {
|
|
@@ -20,14 +20,14 @@ import {
|
|
|
20
20
|
runSnapshot,
|
|
21
21
|
statusWithOutcome,
|
|
22
22
|
whatIsWaiting
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-ah1pdax3.js";
|
|
24
24
|
import {
|
|
25
25
|
expertsDir,
|
|
26
26
|
loadExperts,
|
|
27
27
|
pathsIntersect,
|
|
28
28
|
readExpertDomain,
|
|
29
29
|
stackExpertNames
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-1p5027fn.js";
|
|
31
31
|
import {
|
|
32
32
|
isFinished
|
|
33
33
|
} from "./chunk-6z5rmj0b.js";
|
package/dist/hooks/statusline.js
CHANGED
package/dist/tldrx.js
CHANGED
|
@@ -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 !== "");
|
|
@@ -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)
|
|
@@ -26477,7 +26493,23 @@ function dodFailureReason(result2, repo) {
|
|
|
26477
26493
|
}
|
|
26478
26494
|
|
|
26479
26495
|
// src/core/build/developerGrants.ts
|
|
26480
|
-
var DEVELOPER_GIT_VERBS = [
|
|
26496
|
+
var DEVELOPER_GIT_VERBS = [
|
|
26497
|
+
"add",
|
|
26498
|
+
"commit",
|
|
26499
|
+
"rm",
|
|
26500
|
+
"mv",
|
|
26501
|
+
"restore",
|
|
26502
|
+
"status",
|
|
26503
|
+
"log",
|
|
26504
|
+
"diff",
|
|
26505
|
+
"show"
|
|
26506
|
+
];
|
|
26507
|
+
var GIT_ELSEWHERE_OPTIONS = ["-C", "--git-dir", "--work-tree"];
|
|
26508
|
+
function gitElsewhereOption(argv) {
|
|
26509
|
+
const word = argv[1] ?? "";
|
|
26510
|
+
const flag = word.includes("=") ? word.slice(0, word.indexOf("=")) : word;
|
|
26511
|
+
return GIT_ELSEWHERE_OPTIONS.includes(flag) ? flag : null;
|
|
26512
|
+
}
|
|
26481
26513
|
function developerGitGrants() {
|
|
26482
26514
|
return DEVELOPER_GIT_VERBS.map((verb) => `Bash(git ${verb} *)`);
|
|
26483
26515
|
}
|
|
@@ -26502,6 +26534,94 @@ function slotForCommand(line) {
|
|
|
26502
26534
|
return SUBCOMMAND_RE.test(second) ? `${head} ${second}` : head;
|
|
26503
26535
|
}
|
|
26504
26536
|
|
|
26537
|
+
// src/core/build/refusalKind.ts
|
|
26538
|
+
var MAX_SEPARATOR_RETRIES = 1;
|
|
26539
|
+
function classifyRefusal(command2, declared) {
|
|
26540
|
+
const separator = unquotedShellSeparator(command2);
|
|
26541
|
+
if (separator !== null) {
|
|
26542
|
+
return { kind: "separator", separator, capturing: capturesOutcome(command2, separator) };
|
|
26543
|
+
}
|
|
26544
|
+
const argv = command2.trim().split(/\s+/);
|
|
26545
|
+
if (argv[0] !== "git")
|
|
26546
|
+
return undeclaredKind(command2, declared);
|
|
26547
|
+
const verb = argv[1] ?? "";
|
|
26548
|
+
const elsewhere = gitElsewhereOption(argv);
|
|
26549
|
+
if (elsewhere !== null)
|
|
26550
|
+
return { kind: "elsewhere", option: elsewhere };
|
|
26551
|
+
if (verb === "" || verb.startsWith("-"))
|
|
26552
|
+
return { kind: "unknown" };
|
|
26553
|
+
if (isDeveloperGitVerb(verb))
|
|
26554
|
+
return { kind: "unknown" };
|
|
26555
|
+
return { kind: "verb", verb, equivalent: grantedEquivalent(verb, argv.slice(2)) };
|
|
26556
|
+
}
|
|
26557
|
+
function undeclaredKind(command2, declared) {
|
|
26558
|
+
if (declared === undefined)
|
|
26559
|
+
return { kind: "unknown" };
|
|
26560
|
+
if (grantsCommand(declared, command2))
|
|
26561
|
+
return { kind: "unknown" };
|
|
26562
|
+
const slot = slotForCommand(command2);
|
|
26563
|
+
return slot === "" ? { kind: "unknown" } : { kind: "undeclared", slot };
|
|
26564
|
+
}
|
|
26565
|
+
function grantedEquivalent(verb, args) {
|
|
26566
|
+
if (verb === "checkout") {
|
|
26567
|
+
const switching = args.some((a) => a === "-b" || a === "-B" || a === "--orphan" || a === "-t" || a === "--track");
|
|
26568
|
+
if (switching)
|
|
26569
|
+
return null;
|
|
26570
|
+
const hasTarget = args.some((a) => !a.startsWith("-")) || args.includes("--");
|
|
26571
|
+
return hasTarget ? "git restore <path>" : null;
|
|
26572
|
+
}
|
|
26573
|
+
if (verb === "reset") {
|
|
26574
|
+
const mode = args.some((a) => /^--(hard|soft|mixed|merge|keep)$/.test(a));
|
|
26575
|
+
if (mode)
|
|
26576
|
+
return null;
|
|
26577
|
+
if (args.includes("--"))
|
|
26578
|
+
return "git restore --staged <path>";
|
|
26579
|
+
const [first, second] = args.filter((a) => !a.startsWith("-"));
|
|
26580
|
+
if (first === "HEAD" && second !== undefined)
|
|
26581
|
+
return "git restore --staged <path>";
|
|
26582
|
+
return null;
|
|
26583
|
+
}
|
|
26584
|
+
return null;
|
|
26585
|
+
}
|
|
26586
|
+
var EXIT_CODE_ECHO_RE = /(^|[;&|])\s*echo\b[^;&|]*\$\?/;
|
|
26587
|
+
function capturesOutcome(command2, separator) {
|
|
26588
|
+
if (EXIT_CODE_ECHO_RE.test(command2))
|
|
26589
|
+
return true;
|
|
26590
|
+
return separator === ">" || separator === ">>" || separator === "2>&1";
|
|
26591
|
+
}
|
|
26592
|
+
var OUTCOME_ALREADY_REPORTED = "the facilitator re-runs the Definition of Done after you and records each command's exit " + "code, so the number the `echo` would print is measured and written down whether you " + "capture it or not";
|
|
26593
|
+
function refusalCure(kind) {
|
|
26594
|
+
switch (kind.kind) {
|
|
26595
|
+
case "separator":
|
|
26596
|
+
return "run each command alone — shell separators split a line into subcommands that each need their own grant" + (kind.capturing ? `. The exit code is not lost by dropping it: ${OUTCOME_ALREADY_REPORTED}` : "");
|
|
26597
|
+
case "elsewhere":
|
|
26598
|
+
return `drop \`${kind.option} <path>\` and run git from your own worktree — \`${kind.option}\` ` + "points git at another directory, so it is refused on purpose rather than missing from the " + "allowance: it would reach trees this story does not own. Your working directory already is " + "the worktree, and the read verbs run there";
|
|
26599
|
+
case "verb":
|
|
26600
|
+
return kind.equivalent === null ? `\`git ${kind.verb}\` is not granted` : `\`git ${kind.verb}\` is not granted; use \`${kind.equivalent}\``;
|
|
26601
|
+
case "undeclared":
|
|
26602
|
+
return `nothing in .tldrx/workspace.yml's \`commands:\` grants \`${kind.slot}\` — a developer's ` + "grant is built from the declared commands, so no re-run and no reopen note can make this " + `line runnable. The operator's cure: add a \`commands:\` slot whose value is exactly ` + `\`${kind.slot}\` (a slot grants that string plus any arguments), or a longer prefix of the ` + "refused line if less should be granted";
|
|
26603
|
+
case "unknown":
|
|
26604
|
+
return "";
|
|
26605
|
+
}
|
|
26606
|
+
}
|
|
26607
|
+
function withCure(sentence, command2, declared) {
|
|
26608
|
+
const cure = refusalCure(classifyRefusal(command2, declared));
|
|
26609
|
+
return cure === "" ? sentence : `${sentence}. The cure: ${cure}`;
|
|
26610
|
+
}
|
|
26611
|
+
function separatorCurePrefix(command2) {
|
|
26612
|
+
const separator = unquotedShellSeparator(command2);
|
|
26613
|
+
const capturing = separator !== null && capturesOutcome(command2, separator);
|
|
26614
|
+
return [
|
|
26615
|
+
`Your previous command \`${command2}\` was refused because it chains commands. Run each command alone.`,
|
|
26616
|
+
"The permission layer splits a line at every shell separator (`&&`, `;`, `|`, `>`, `2>&1`, `$()`)",
|
|
26617
|
+
"and each fragment must match its own grant, so a compound line is refused even when every",
|
|
26618
|
+
"command in it is allowed on its own. This is the one retry: a second refusal blocks the story.",
|
|
26619
|
+
...capturing ? [`The exit code is not lost by dropping it: ${OUTCOME_ALREADY_REPORTED}.`] : [],
|
|
26620
|
+
""
|
|
26621
|
+
].join(`
|
|
26622
|
+
`);
|
|
26623
|
+
}
|
|
26624
|
+
|
|
26505
26625
|
// src/core/build/prompts.ts
|
|
26506
26626
|
var MAX_TOUCHED_BYTES = 64 * 1024;
|
|
26507
26627
|
var MAX_TOUCHED_FILES = 24;
|
|
@@ -26605,8 +26725,11 @@ function buildDeveloperPrompt(parts) {
|
|
|
26605
26725
|
"",
|
|
26606
26726
|
...parts.testFast === undefined ? [] : [...testFastRule(parts.testFast.fast, parts.testFast.full), ""],
|
|
26607
26727
|
`Commit with \`git add\` and \`git commit\`. The git verbs you hold are exactly ${gitVerbList()}.`,
|
|
26608
|
-
"`git restore <path>` is how to put a file back (there is no checkout in that list).
|
|
26609
|
-
"
|
|
26728
|
+
"`git restore <path>` is how to put a file back (there is no checkout in that list). The read",
|
|
26729
|
+
"verbs are yours so you can look at your own tree before you commit — run them from your working",
|
|
26730
|
+
"directory, which already IS this worktree. Never `-C <path>`: it points git at another directory,",
|
|
26731
|
+
"which is not yours to read or write, so it is refused on purpose. Nothing else about git is",
|
|
26732
|
+
"yours to do.",
|
|
26610
26733
|
"",
|
|
26611
26734
|
"## Rules",
|
|
26612
26735
|
"",
|
|
@@ -26619,8 +26742,9 @@ function buildDeveloperPrompt(parts) {
|
|
|
26619
26742
|
"- Run each Definition of Done command verbatim and alone: no redirection, pipes or chaining.",
|
|
26620
26743
|
" Shell separators (`>`, `>>`, `2>&1`, `<`, `|`, `;`, `&&`, `||`, `&`, `$()`) split a line into",
|
|
26621
26744
|
" subcommands, and each subcommand must match its own grant, so a compound line is refused",
|
|
26622
|
-
" even when the script itself is allowed.
|
|
26623
|
-
|
|
26745
|
+
" even when the script itself is allowed.",
|
|
26746
|
+
`- Do not append \`; echo $?\` or redirect a command's output to a file to capture its outcome.`,
|
|
26747
|
+
` The exit code is not lost by dropping it: ${OUTCOME_ALREADY_REPORTED}.`,
|
|
26624
26748
|
"",
|
|
26625
26749
|
"### Conventions",
|
|
26626
26750
|
"",
|
|
@@ -28715,78 +28839,6 @@ function verdictReviewer(checkPayload, lastSpawn) {
|
|
|
28715
28839
|
return lastSpawn;
|
|
28716
28840
|
}
|
|
28717
28841
|
|
|
28718
|
-
// src/core/build/refusalKind.ts
|
|
28719
|
-
var MAX_SEPARATOR_RETRIES = 1;
|
|
28720
|
-
function classifyRefusal(command2, declared) {
|
|
28721
|
-
const separator = unquotedShellSeparator(command2);
|
|
28722
|
-
if (separator !== null)
|
|
28723
|
-
return { kind: "separator", separator };
|
|
28724
|
-
const argv = command2.trim().split(/\s+/);
|
|
28725
|
-
if (argv[0] !== "git")
|
|
28726
|
-
return undeclaredKind(command2, declared);
|
|
28727
|
-
const verb = argv[1] ?? "";
|
|
28728
|
-
if (verb === "" || verb.startsWith("-"))
|
|
28729
|
-
return { kind: "unknown" };
|
|
28730
|
-
if (isDeveloperGitVerb(verb))
|
|
28731
|
-
return { kind: "unknown" };
|
|
28732
|
-
return { kind: "verb", verb, equivalent: grantedEquivalent(verb, argv.slice(2)) };
|
|
28733
|
-
}
|
|
28734
|
-
function undeclaredKind(command2, declared) {
|
|
28735
|
-
if (declared === undefined)
|
|
28736
|
-
return { kind: "unknown" };
|
|
28737
|
-
if (grantsCommand(declared, command2))
|
|
28738
|
-
return { kind: "unknown" };
|
|
28739
|
-
const slot = slotForCommand(command2);
|
|
28740
|
-
return slot === "" ? { kind: "unknown" } : { kind: "undeclared", slot };
|
|
28741
|
-
}
|
|
28742
|
-
function grantedEquivalent(verb, args) {
|
|
28743
|
-
if (verb === "checkout") {
|
|
28744
|
-
const switching = args.some((a) => a === "-b" || a === "-B" || a === "--orphan" || a === "-t" || a === "--track");
|
|
28745
|
-
if (switching)
|
|
28746
|
-
return null;
|
|
28747
|
-
const hasTarget = args.some((a) => !a.startsWith("-")) || args.includes("--");
|
|
28748
|
-
return hasTarget ? "git restore <path>" : null;
|
|
28749
|
-
}
|
|
28750
|
-
if (verb === "reset") {
|
|
28751
|
-
const mode = args.some((a) => /^--(hard|soft|mixed|merge|keep)$/.test(a));
|
|
28752
|
-
if (mode)
|
|
28753
|
-
return null;
|
|
28754
|
-
if (args.includes("--"))
|
|
28755
|
-
return "git restore --staged <path>";
|
|
28756
|
-
const [first2, second] = args.filter((a) => !a.startsWith("-"));
|
|
28757
|
-
if (first2 === "HEAD" && second !== undefined)
|
|
28758
|
-
return "git restore --staged <path>";
|
|
28759
|
-
return null;
|
|
28760
|
-
}
|
|
28761
|
-
return null;
|
|
28762
|
-
}
|
|
28763
|
-
function refusalCure(kind) {
|
|
28764
|
-
switch (kind.kind) {
|
|
28765
|
-
case "separator":
|
|
28766
|
-
return "run each command alone — shell separators split a line into subcommands that each need their own grant";
|
|
28767
|
-
case "verb":
|
|
28768
|
-
return kind.equivalent === null ? `\`git ${kind.verb}\` is not granted` : `\`git ${kind.verb}\` is not granted; use \`${kind.equivalent}\``;
|
|
28769
|
-
case "undeclared":
|
|
28770
|
-
return `nothing in .tldrx/workspace.yml's \`commands:\` grants \`${kind.slot}\` — a developer's ` + "grant is built from the declared commands, so no re-run and no reopen note can make this " + `line runnable. The operator's cure: add a \`commands:\` slot whose value is exactly ` + `\`${kind.slot}\` (a slot grants that string plus any arguments), or a longer prefix of the ` + "refused line if less should be granted";
|
|
28771
|
-
case "unknown":
|
|
28772
|
-
return "";
|
|
28773
|
-
}
|
|
28774
|
-
}
|
|
28775
|
-
function withCure(sentence, command2, declared) {
|
|
28776
|
-
const cure = refusalCure(classifyRefusal(command2, declared));
|
|
28777
|
-
return cure === "" ? sentence : `${sentence}. The cure: ${cure}`;
|
|
28778
|
-
}
|
|
28779
|
-
function separatorCurePrefix(command2) {
|
|
28780
|
-
return [
|
|
28781
|
-
`Your previous command \`${command2}\` was refused because it chains commands. Run each command alone.`,
|
|
28782
|
-
"The permission layer splits a line at every shell separator (`&&`, `;`, `|`, `>`, `2>&1`, `$()`)",
|
|
28783
|
-
"and each fragment must match its own grant, so a compound line is refused even when every",
|
|
28784
|
-
"command in it is allowed on its own. This is the one retry: a second refusal blocks the story.",
|
|
28785
|
-
""
|
|
28786
|
-
].join(`
|
|
28787
|
-
`);
|
|
28788
|
-
}
|
|
28789
|
-
|
|
28790
28842
|
// src/core/build/review.ts
|
|
28791
28843
|
var VERDICT_WORDS = ["approve", "fixlist", "changes"];
|
|
28792
28844
|
var VERDICT_ENUM = VERDICT_WORDS.join("|");
|
|
@@ -28972,7 +29024,7 @@ function renderReviewLog(outcome) {
|
|
|
28972
29024
|
`- Repo: \`${outcome.repo}\` · wave ${outcome.wave} · epic ${outcome.epic}`,
|
|
28973
29025
|
`- Branch: \`${outcome.branch}\` → \`${outcome.epicBranch}\` (${mergeWord(outcome)})`,
|
|
28974
29026
|
`- Commit: ${outcome.commit ?? "(none)"}`,
|
|
28975
|
-
...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."],
|
|
29027
|
+
...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."],
|
|
28976
29028
|
...outcome.developerError === null ? [] : [`- Developer: **FAILED** — ${outcome.developerError}`],
|
|
28977
29029
|
...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)],
|
|
28978
29030
|
...outcome.budgetDeath == null ? [] : [`- Developer: died on its per-story cap — ${outcome.budgetDeath}; the tree held work, ` + "so the Definition of Done below decided"],
|
|
@@ -36574,6 +36626,7 @@ function readReviewLedger(runDir, storyId) {
|
|
|
36574
36626
|
blockedWithNothingRun: false,
|
|
36575
36627
|
reopened: null,
|
|
36576
36628
|
asIs: null,
|
|
36629
|
+
lastMerge: null,
|
|
36577
36630
|
fixRound: null,
|
|
36578
36631
|
formatRetries: 0,
|
|
36579
36632
|
formatRefusal: null,
|
|
@@ -36595,6 +36648,7 @@ function readReviewLedger(runDir, storyId) {
|
|
|
36595
36648
|
let lastDodOutputPath = null;
|
|
36596
36649
|
let reopened = null;
|
|
36597
36650
|
let asIs = null;
|
|
36651
|
+
let lastMerge = null;
|
|
36598
36652
|
let fixRound = null;
|
|
36599
36653
|
let formatRetries = 0;
|
|
36600
36654
|
let formatRefusal = null;
|
|
@@ -36662,6 +36716,13 @@ function readReviewLedger(runDir, storyId) {
|
|
|
36662
36716
|
commit2 = payload.commit;
|
|
36663
36717
|
if (typeof payload.epic_base === "string" && payload.epic_base !== "")
|
|
36664
36718
|
epicBase = payload.epic_base;
|
|
36719
|
+
if (typeof payload.commit === "string" && payload.commit !== "" && typeof payload.epic_base === "string" && payload.epic_base !== "") {
|
|
36720
|
+
lastMerge = {
|
|
36721
|
+
commit: payload.commit,
|
|
36722
|
+
epicBase: payload.epic_base,
|
|
36723
|
+
verdict: typeof payload.verdict === "string" && payload.verdict !== "" ? payload.verdict : "n-a"
|
|
36724
|
+
};
|
|
36725
|
+
}
|
|
36665
36726
|
if (payload.status === "done")
|
|
36666
36727
|
fixRound = null;
|
|
36667
36728
|
asIs = null;
|
|
@@ -36725,6 +36786,7 @@ function readReviewLedger(runDir, storyId) {
|
|
|
36725
36786
|
blockedWithNothingRun,
|
|
36726
36787
|
reopened,
|
|
36727
36788
|
asIs,
|
|
36789
|
+
lastMerge,
|
|
36728
36790
|
fixRound,
|
|
36729
36791
|
formatRetries,
|
|
36730
36792
|
formatRefusal,
|
|
@@ -39394,7 +39456,22 @@ class BuildSession {
|
|
|
39394
39456
|
};
|
|
39395
39457
|
}
|
|
39396
39458
|
const ahead = await commitsBetween(story.repoDir, story.epicBranch, story.branch);
|
|
39397
|
-
if (ahead ===
|
|
39459
|
+
if (ahead === 0) {
|
|
39460
|
+
const merge = readReviewLedger(this.ctx.runDir, id).lastMerge;
|
|
39461
|
+
if (merge !== null && reviewNeverCompleted(merge.verdict)) {
|
|
39462
|
+
return await this.reviewOnlyHalf(planned, asIs, story, before, merge);
|
|
39463
|
+
}
|
|
39464
|
+
return {
|
|
39465
|
+
story,
|
|
39466
|
+
cost: 0,
|
|
39467
|
+
dod: [],
|
|
39468
|
+
commit: null,
|
|
39469
|
+
failure: asIsNotAheadReason(story.branch, story.epicBranch, ahead, merge === null ? undefined : merge),
|
|
39470
|
+
developerError: null,
|
|
39471
|
+
before
|
|
39472
|
+
};
|
|
39473
|
+
}
|
|
39474
|
+
if (ahead === null) {
|
|
39398
39475
|
return {
|
|
39399
39476
|
story,
|
|
39400
39477
|
cost: 0,
|
|
@@ -39432,6 +39509,27 @@ class BuildSession {
|
|
|
39432
39509
|
}
|
|
39433
39510
|
return { story, cost: 0, dod, commit: commit2, failure: null, developerError: null, before };
|
|
39434
39511
|
}
|
|
39512
|
+
async reviewOnlyHalf(planned, asIs, story, before, merge) {
|
|
39513
|
+
const id = planned.story.id;
|
|
39514
|
+
const reviewed = { commit: merge.commit, epicBase: merge.epicBase };
|
|
39515
|
+
this.asIsSettlements.set(id, { ...asIs, reason: "review-only", reviewed });
|
|
39516
|
+
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)}\``);
|
|
39517
|
+
await this.writes.run(() => this.refreshStoryBase(planned, story.repoDir, story.worktree, story.branch, story.epicBranch));
|
|
39518
|
+
const dod = await this.runDod(story);
|
|
39519
|
+
if (!this.dodProves(dod)) {
|
|
39520
|
+
const failing = dodFailure(dod);
|
|
39521
|
+
return {
|
|
39522
|
+
story,
|
|
39523
|
+
cost: 0,
|
|
39524
|
+
dod,
|
|
39525
|
+
commit: null,
|
|
39526
|
+
failure: failing === undefined ? "the story declares no dod commands, so nothing could prove it" : dodFailureReason(failing, planned.story.repo),
|
|
39527
|
+
developerError: null,
|
|
39528
|
+
before
|
|
39529
|
+
};
|
|
39530
|
+
}
|
|
39531
|
+
return { story, cost: 0, dod, commit: merge.commit, failure: null, developerError: null, before, reviewOnly: reviewed };
|
|
39532
|
+
}
|
|
39435
39533
|
async buildHalf(planned) {
|
|
39436
39534
|
const asIs = this.asIsFor(planned);
|
|
39437
39535
|
if (asIs !== null)
|
|
@@ -39570,6 +39668,10 @@ class BuildSession {
|
|
|
39570
39668
|
await this.block(story, "the story produced no commit to review", half.cost, dod);
|
|
39571
39669
|
return "settled";
|
|
39572
39670
|
}
|
|
39671
|
+
if (half.reviewOnly !== undefined) {
|
|
39672
|
+
this.noteMerged(story, null);
|
|
39673
|
+
return await this.reviewAndSettle(story, dod, half.reviewOnly.commit, half.cost, null, half.reviewOnly.epicBase);
|
|
39674
|
+
}
|
|
39573
39675
|
const update = await updateStoryBase({
|
|
39574
39676
|
storyId: story.planned.story.id,
|
|
39575
39677
|
repoDir: story.repoDir,
|
|
@@ -39654,6 +39756,21 @@ class BuildSession {
|
|
|
39654
39756
|
if (review.verdict === "fixlist") {
|
|
39655
39757
|
const rel = this.writeFixlistFor(story, review, commit2, epicBase);
|
|
39656
39758
|
const open2 = openFindings(review.fixlist).length;
|
|
39759
|
+
if (open2 === 0) {
|
|
39760
|
+
this.lines.push(` · ${story.planned.story.id}: ${FIXLIST_SETTLED_MARK} (${rel})`);
|
|
39761
|
+
await this.settle(story, "done", {
|
|
39762
|
+
dod,
|
|
39763
|
+
commit: commit2,
|
|
39764
|
+
merged: true,
|
|
39765
|
+
carried,
|
|
39766
|
+
epicBase,
|
|
39767
|
+
verdict: "fixlist",
|
|
39768
|
+
review,
|
|
39769
|
+
cost,
|
|
39770
|
+
reason: null
|
|
39771
|
+
});
|
|
39772
|
+
return "settled";
|
|
39773
|
+
}
|
|
39657
39774
|
await this.settle(story, "review", {
|
|
39658
39775
|
dod,
|
|
39659
39776
|
commit: commit2,
|
|
@@ -40456,12 +40573,13 @@ class BuildSession {
|
|
|
40456
40573
|
...outcome.permissionRefused == null ? {} : { permission_refused: outcome.permissionRefused },
|
|
40457
40574
|
...outcome.budgetDeath == null ? {} : { budget_death: outcome.budgetDeath },
|
|
40458
40575
|
...outcome.reviewerUnfunded == null ? {} : { reviewer_unfunded: outcome.reviewerUnfunded },
|
|
40459
|
-
...outcome.asIs == null ? {} : { as_is: true, as_is_by: outcome.asIs.actor, as_is_note: outcome.asIs.note }
|
|
40576
|
+
...outcome.asIs == null ? {} : { as_is: true, as_is_by: outcome.asIs.actor, as_is_note: outcome.asIs.note },
|
|
40577
|
+
...outcome.asIs?.reason === undefined ? {} : { as_is_reason: outcome.asIs.reason }
|
|
40460
40578
|
});
|
|
40461
40579
|
if (pruning && (rescued === null || rescued.worktree === null)) {
|
|
40462
40580
|
await removeWorktree(story.repoDir, story.worktree);
|
|
40463
40581
|
}
|
|
40464
|
-
this.lines.push(` ${status === "done" ? "✓" : "·"} ${id} → \`${status}\`` + (outcome.reason === null ? "" : ` (${outcome.reason})`) + (outcome.asIs == null ? "" : ` — ${AS_IS_MARK};
|
|
40582
|
+
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)}`));
|
|
40465
40583
|
}
|
|
40466
40584
|
writeLog(outcome) {
|
|
40467
40585
|
const dir = join99(this.ctx.runDir, BUILD_PHASE, LOG_DIR);
|
|
@@ -49239,6 +49357,7 @@ function reopenStory(options) {
|
|
|
49239
49357
|
` signed by ${options.actor}: ${options.note}`,
|
|
49240
49358
|
" 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",
|
|
49241
49359
|
" 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",
|
|
49360
|
+
" 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",
|
|
49242
49361
|
" the record will say the branch was taken as it stands and name you, so nothing reads as " + "though a developer delivered it",
|
|
49243
49362
|
...kept
|
|
49244
49363
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tldr-experts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
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.
|
|
5
|
+
"version": "0.23.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Alan Martinez"
|
|
8
8
|
}
|