tldr-experts 0.28.0 → 0.28.1

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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.28.1 — 2026-09-15
4
+
5
+ ### Fixed
6
+
7
+ - **The note a story was reopened with now reaches the developer and the reviewer (closes #322).**
8
+ On a field run a person reopened a story with a note naming the exact gap two reviews had
9
+ refused it for; the next developer made a docstring-only commit and a reviewer approved it, and
10
+ neither turn had been shown the note. It was written only into `story.reopened` and read back
11
+ for a report line and #308's no-diff check — while the refusal for a `--for-fix` with no note
12
+ told the operator the note "is what scopes the fix round, and what the reviewer reads". Measured
13
+ before the fix: 0 of the post-reopen developer and reviewer prompts contained the note, for a
14
+ plain reopen and for `--for-fix`, and after a plain reopen the developer prompt had no
15
+ `## Previous attempt` either, so it was told nothing about why the story came back. Both
16
+ prompts now carry `## Why this story was reopened` — who signed it, whether it is a fix round,
17
+ the note verbatim — read off the same ledger value #308 already reads, so the prompt and the
18
+ refusal cannot name different notes; a story nobody reopened renders nothing new. The
19
+ reviewer's copy tells it that a diff leaving the named gap as it was is `changes`, but that
20
+ instruction is **prompt-advisory**: the framework does not enforce a `changes` verdict against
21
+ the note, and an `approve` is still accepted as the reviewer's judgement.
22
+ - **`tldrx note --help` says where an operator note goes, and where it does not (closes #151,
23
+ docs only).** An `operator_note` is read by `run status`, `replay` and the dashboard and by no
24
+ prompt — so a note an owner wrote for the next stage's agents silently reached nobody. The gap
25
+ was the unwritten contract, not a missing reader: the help and the CLI reference now say the
26
+ note is for people and name `.agent/<stage>/dispatch-notes.md` as the channel that reaches
27
+ agents.
28
+
3
29
  ## 0.28.0 — 2026-09-15
4
30
 
5
31
  ### Added
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.28.1 | 2026-09-15 | `beta` | Two fixes so a reopened story's reason reaches the agents that need it. The developer and reviewer prompts now carry `## Why this story was reopened` — who signed it, whether it's a fix round, the note verbatim — off the same ledger value #308 reads, instead of the note reaching only a report line and #308's check (closes #322). `tldrx note --help` now says an operator note reaches no agent and names `dispatch-notes.md` as the channel that does (closes #151). |
338
339
  | 0.28.0 | 2026-09-15 | `beta` | Four changes that keep an unattended run moving instead of stranding on a person. A story whose merge-up to its epic conflicts in at most three of its own touched files gets one automated conflict turn instead of a person, guarded against committing conflict markers (#286). `story reopen` on a blocked story now releases every dependent it alone was holding, in the same command (#312). Seed's `Recommended:` line and the loop's question parser now share one grammar, so a citation-only recommendation no longer parks a run that `seed check` had already passed (#323). And an `absent:` needle over `facts.yml` no longer trips on a fact's own recording metadata, while an `auto` gate refused only by failed checks gets one automatic re-run before waiting for a person (#231). |
339
340
  | 0.27.0 | 2026-09-14 | `beta` | Five changes for unattended runs, from a planning audit and live field measurement. The `plan` check now refuses a plan with more waves than the framework carries per run (unless `waves.yml` names a `wave_cap_reason`), refuses a story scheduled before its `depends_on` allows, and flags a dod command siblings carry that one story doesn't (#316–#319). `tldrx run auto --rebalance-finished` moves a blocked phase's exact shortfall out of a finished phase's unspent ceiling before refusing, through `budget raise --take-from` itself, so the run ceiling never grows and no grant is ever assumed; every money refusal now names the unspent total and the exact command to fix it (#314). `tldrx ship` fetches and merges the base into the epic and re-runs the `done` stories' DoD before opening a PR, so a stale or red epic refuses instead of shipping a PR that comes back red (#315). A reviewer's budget floor rises from $1.00 to $2.00, matching what completed reviews actually cost, so a review no longer dies mid-diff on a floor that was never measured (#307). And a developer whose DoD goes red gets the story's next attempt instead of blocking on the first miss, with the kept output handed to the next attempt and the bound counted from `events.jsonl` so it holds across process restarts; a refused developer, a cap death, or a broken dod command still blocks immediately (#313). |
340
341
  | 0.26.1 | 2026-09-14 | `beta` | Three refusals that stop a bad state from settling quietly. A Build fix round whose developer lands no diff is refused before the DoD instead of closing `done` on an unmoved tree, because `commitIfDirty` hands back the old head sha on a clean commit and the existing "no commit to review" gate never fires for it — `workSince` is now asked on the success path too, for any story a person put back with a note. `tldrx ship` refuses an epic carrying a story the reviewer marked `changes`: it asks the ledger's `lastMerge` for every story not `done` and exits 2 naming the story, the merge and the handoff's reason, instead of opening a PR whose "Not done" list hid rejected code already merged into the diff. And a stage-failure line keeps the branch it names when the worktree path is wider than the line: the refusal puts the branches before the path, and `oneLine` keeps a line's head and tail instead of dropping the identifiers that sat at the end. |
@@ -22,7 +22,7 @@ import {
22
22
  validateRunBudget,
23
23
  wouldExceed,
24
24
  wouldExceedHostTokens
25
- } from "./chunk-xhd77h0p.js";
25
+ } from "./chunk-e618k6te.js";
26
26
  import {
27
27
  EventLog
28
28
  } from "./chunk-abswp1vt.js";
@@ -9,7 +9,7 @@ import {
9
9
  spentBasis,
10
10
  tallyOf,
11
11
  validateRunBudget
12
- } from "./chunk-xhd77h0p.js";
12
+ } from "./chunk-e618k6te.js";
13
13
  import {
14
14
  EventLog,
15
15
  OUTCOME_NOT_RECORDED,
@@ -20,14 +20,14 @@ import {
20
20
  runSnapshot,
21
21
  statusWithOutcome,
22
22
  whatIsWaiting
23
- } from "./chunk-kx45kt9n.js";
23
+ } from "./chunk-fjeefnqr.js";
24
24
  import {
25
25
  expertsDir,
26
26
  loadExperts,
27
27
  pathsIntersect,
28
28
  readExpertDomain,
29
29
  stackExpertNames
30
- } from "./chunk-xhd77h0p.js";
30
+ } from "./chunk-e618k6te.js";
31
31
  import {
32
32
  isFinished
33
33
  } from "./chunk-abswp1vt.js";
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  bar,
4
4
  runSnapshot
5
- } from "./chunk-kx45kt9n.js";
6
- import"./chunk-xhd77h0p.js";
5
+ } from "./chunk-fjeefnqr.js";
6
+ import"./chunk-e618k6te.js";
7
7
  import"./chunk-abswp1vt.js";
8
8
  import"./chunk-veffvzw5.js";
9
9
  import"./chunk-hws2gnxj.js";
package/dist/tldrx.js CHANGED
@@ -16588,7 +16588,8 @@ var ENTRIES = [
16588
16588
  notes: [
16589
16589
  "It appends exactly one `operator_note` event and touches NOTHING else: run.yml and budget.yml are byte-identical across the call, no gate is signed or revoked, no cursor moves and no money is spent.",
16590
16590
  "It exists because there was no honest carrier for a maintenance action at the moment it happened. The alternatives people reached for were a FUTURE gate note (late, and attached to a decision the note is not about) and `tldrx reject` (which undoes work).",
16591
- "The note shows up in `tldrx run status` (the last few) and in `tldrx replay` (every one, in place)."
16591
+ "The note shows up in `tldrx run status` (the last few) and in `tldrx replay` (every one, in place).",
16592
+ "It is for PEOPLE reading `tldrx run status`, `tldrx replay` and the dashboard, and it never reaches an agent: no prompt reads `operator_note`. Context meant for the next agent goes in `tldrx-work/<run>/.agent/<stage>/dispatch-notes.md` (for one Build story, `.agent/<stage>/<story>/dispatch-notes.md`), beside that stage's `prompt.md`, which the next prompt inlines under `## Dispatch notes`."
16592
16593
  ]
16593
16594
  },
16594
16595
  {
@@ -26904,6 +26905,45 @@ function testFastRule(fast, full) {
26904
26905
  ]
26905
26906
  ];
26906
26907
  }
26908
+ var REOPEN_NOTE_HEADING = "## Why this story was reopened";
26909
+ function reopenNoteLines(reopen) {
26910
+ const kind = reopen.fix ? "`tldrx story reopen --for-fix`: a done story reopened to land ONE named defect" : "`tldrx story reopen`: another run of attempts";
26911
+ const fence = fenceFor(reopen.note);
26912
+ return [
26913
+ REOPEN_NOTE_HEADING,
26914
+ "",
26915
+ `A person (${reopen.actor === "" ? "actor not recorded" : reopen.actor}) put this story back with ${kind}.`,
26916
+ "Their note, verbatim:",
26917
+ "",
26918
+ `${fence}text`,
26919
+ reopen.note.replace(/\n$/, ""),
26920
+ fence,
26921
+ ""
26922
+ ];
26923
+ }
26924
+ function developerReopenSection(reopen) {
26925
+ if (reopen === undefined || reopen === null)
26926
+ return [];
26927
+ return [
26928
+ ...reopenNoteLines(reopen),
26929
+ "This note is part of your brief. The acceptance criteria above still decide the story, and the",
26930
+ "note names what the person found missing or wrong against them: change what it names. A diff",
26931
+ "that leaves the named gap as it was is not an answer to it, and the reviewer is shown this note.",
26932
+ ""
26933
+ ];
26934
+ }
26935
+ function reviewerReopenSection(reopen) {
26936
+ if (reopen === undefined || reopen === null)
26937
+ return [];
26938
+ return [
26939
+ ...reopenNoteLines(reopen),
26940
+ "Judge the diff against this note as well as against the acceptance criteria: it names the gap",
26941
+ "this attempt exists to close. If the diff leaves that gap as it was — it does not touch what the",
26942
+ "note names, or it changes only comments or docs where the note asks for behaviour — the story is",
26943
+ "not done: return `changes` and cite the note.",
26944
+ ""
26945
+ ];
26946
+ }
26907
26947
  var CONFLICT_TURN_HEADING = "## Merge in progress — resolve it first";
26908
26948
  function conflictTurnSection(parts) {
26909
26949
  const turn = parts.conflictTurn;
@@ -26996,6 +27036,7 @@ function buildDeveloperPrompt(parts) {
26996
27036
  "",
26997
27037
  ...dispatchNotesSection(parts.dispatchNotes),
26998
27038
  ...projectSkillsSection(parts.projectSkills),
27039
+ ...developerReopenSection(parts.reopenNote),
26999
27040
  ...conflictTurnSection(parts),
27000
27041
  "## Investigate",
27001
27042
  "",
@@ -27141,6 +27182,7 @@ function buildReviewerPrompt(parts) {
27141
27182
  "",
27142
27183
  ...story.acceptance.map((item) => `- ${item}`),
27143
27184
  "",
27185
+ ...reviewerReopenSection(parts.reopenNote),
27144
27186
  "## Definition of Done — already re-run by the facilitator",
27145
27187
  "",
27146
27188
  ...parts.dodResults.length === 0 ? ["- (no dod commands)"] : parts.dodResults.map((r) => dodRefused(r) ? `- \`${r.command}\` → REFUSED, never ran` : `- \`${r.command}\` → exit ${String(r.exitCode ?? "?")}`),
@@ -39182,6 +39224,7 @@ function reviewerPromptFor(parts) {
39182
39224
  branch: parts.branch,
39183
39225
  epicBranch: parts.epicBranch,
39184
39226
  diffBase: parts.diffBase,
39227
+ reopenNote: parts.reopenNote,
39185
39228
  worktree: parts.worktree,
39186
39229
  conventions: renderConventions(parts.root, [parts.story.story.repo]),
39187
39230
  dodResults: parts.dod.map((r) => ({
@@ -41887,6 +41930,7 @@ class BuildSession {
41887
41930
  reviewerPrompt(story, dod, refusal, diffBase) {
41888
41931
  return reviewerPromptFor({
41889
41932
  diffBase,
41933
+ reopenNote: this.reopenFor(story.planned),
41890
41934
  runDir: this.ctx.runDir,
41891
41935
  root: this.ctx.root,
41892
41936
  runId: this.ctx.runId,
@@ -42475,6 +42519,7 @@ class BuildSession {
42475
42519
  planNote: this.plan.implicit ? story.planned.note ?? IMPLICIT_STORY_NOTE : undefined,
42476
42520
  previousAttempt: story.previousAttempt,
42477
42521
  previousAttemptKind: story.previousAttemptKind,
42522
+ reopenNote: this.reopenFor(story.planned),
42478
42523
  notInWorktree: story.notInWorktree,
42479
42524
  ...story.conflictTurn === undefined ? {} : { conflictTurn: story.conflictTurn },
42480
42525
  dispatchNotes: this.dispatchNotesFor(story.planned.story.id).body,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tldr-experts",
3
- "version": "0.28.0",
3
+ "version": "0.28.1",
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.28.0",
5
+ "version": "0.28.1",
6
6
  "author": {
7
7
  "name": "Alan Martinez"
8
8
  }