tldr-experts 0.22.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 CHANGED
@@ -1,5 +1,60 @@
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
+
3
58
  ## 0.22.0 — 2026-09-14
4
59
 
5
60
  ### Fixed
package/README.md CHANGED
@@ -335,6 +335,7 @@ 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. |
338
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. |
339
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`. |
340
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`. |
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 reason = errors2[0] ?? (typeof doc.subtype === "string" ? doc.subtype : "") ?? "";
20656
- const suffix = reason === "" ? "" : `: ${String(reason)}`;
20657
- return `${name} exited ${exitCode} with is_error=${String(doc.is_error === true)}${suffix}`;
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
- function asIsNotAheadReason(branch, epicBranch, ahead) {
26456
- return ahead === null ? `${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" : `${AS_IS_NOT_AHEAD_MARK}: \`${branch}\` carries no commit \`${epicBranch}\` has not already ` + "got, so there is no work to settle. Commit the fix to the story branch first, then reopen it " + "--as-is. Nothing was merged";
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)
@@ -29008,7 +29024,7 @@ function renderReviewLog(outcome) {
29008
29024
  `- Repo: \`${outcome.repo}\` · wave ${outcome.wave} · epic ${outcome.epic}`,
29009
29025
  `- Branch: \`${outcome.branch}\` → \`${outcome.epicBranch}\` (${mergeWord(outcome)})`,
29010
29026
  `- 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."],
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."],
29012
29028
  ...outcome.developerError === null ? [] : [`- Developer: **FAILED** — ${outcome.developerError}`],
29013
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)],
29014
29030
  ...outcome.budgetDeath == null ? [] : [`- Developer: died on its per-story cap — ${outcome.budgetDeath}; the tree held work, ` + "so the Definition of Done below decided"],
@@ -36610,6 +36626,7 @@ function readReviewLedger(runDir, storyId) {
36610
36626
  blockedWithNothingRun: false,
36611
36627
  reopened: null,
36612
36628
  asIs: null,
36629
+ lastMerge: null,
36613
36630
  fixRound: null,
36614
36631
  formatRetries: 0,
36615
36632
  formatRefusal: null,
@@ -36631,6 +36648,7 @@ function readReviewLedger(runDir, storyId) {
36631
36648
  let lastDodOutputPath = null;
36632
36649
  let reopened = null;
36633
36650
  let asIs = null;
36651
+ let lastMerge = null;
36634
36652
  let fixRound = null;
36635
36653
  let formatRetries = 0;
36636
36654
  let formatRefusal = null;
@@ -36698,6 +36716,13 @@ function readReviewLedger(runDir, storyId) {
36698
36716
  commit2 = payload.commit;
36699
36717
  if (typeof payload.epic_base === "string" && payload.epic_base !== "")
36700
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
+ }
36701
36726
  if (payload.status === "done")
36702
36727
  fixRound = null;
36703
36728
  asIs = null;
@@ -36761,6 +36786,7 @@ function readReviewLedger(runDir, storyId) {
36761
36786
  blockedWithNothingRun,
36762
36787
  reopened,
36763
36788
  asIs,
36789
+ lastMerge,
36764
36790
  fixRound,
36765
36791
  formatRetries,
36766
36792
  formatRefusal,
@@ -39430,7 +39456,22 @@ class BuildSession {
39430
39456
  };
39431
39457
  }
39432
39458
  const ahead = await commitsBetween(story.repoDir, story.epicBranch, story.branch);
39433
- if (ahead === null || ahead === 0) {
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) {
39434
39475
  return {
39435
39476
  story,
39436
39477
  cost: 0,
@@ -39468,6 +39509,27 @@ class BuildSession {
39468
39509
  }
39469
39510
  return { story, cost: 0, dod, commit: commit2, failure: null, developerError: null, before };
39470
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
+ }
39471
39533
  async buildHalf(planned) {
39472
39534
  const asIs = this.asIsFor(planned);
39473
39535
  if (asIs !== null)
@@ -39606,6 +39668,10 @@ class BuildSession {
39606
39668
  await this.block(story, "the story produced no commit to review", half.cost, dod);
39607
39669
  return "settled";
39608
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
+ }
39609
39675
  const update = await updateStoryBase({
39610
39676
  storyId: story.planned.story.id,
39611
39677
  repoDir: story.repoDir,
@@ -39690,6 +39756,21 @@ class BuildSession {
39690
39756
  if (review.verdict === "fixlist") {
39691
39757
  const rel = this.writeFixlistFor(story, review, commit2, epicBase);
39692
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
+ }
39693
39774
  await this.settle(story, "review", {
39694
39775
  dod,
39695
39776
  commit: commit2,
@@ -40492,12 +40573,13 @@ class BuildSession {
40492
40573
  ...outcome.permissionRefused == null ? {} : { permission_refused: outcome.permissionRefused },
40493
40574
  ...outcome.budgetDeath == null ? {} : { budget_death: outcome.budgetDeath },
40494
40575
  ...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 }
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 }
40496
40578
  });
40497
40579
  if (pruning && (rescued === null || rescued.worktree === null)) {
40498
40580
  await removeWorktree(story.repoDir, story.worktree);
40499
40581
  }
40500
- this.lines.push(` ${status === "done" ? "✓" : "·"} ${id} → \`${status}\`` + (outcome.reason === null ? "" : ` (${outcome.reason})`) + (outcome.asIs == null ? "" : ` — ${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)}`));
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)}`));
40501
40583
  }
40502
40584
  writeLog(outcome) {
40503
40585
  const dir = join99(this.ctx.runDir, BUILD_PHASE, LOG_DIR);
@@ -49275,6 +49357,7 @@ function reopenStory(options) {
49275
49357
  ` signed by ${options.actor}: ${options.note}`,
49276
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",
49277
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",
49278
49361
  " the record will say the branch was taken as it stands and name you, so nothing reads as " + "though a developer delivered it",
49279
49362
  ...kept
49280
49363
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tldr-experts",
3
- "version": "0.22.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.22.0",
5
+ "version": "0.23.0",
6
6
  "author": {
7
7
  "name": "Alan Martinez"
8
8
  }