@pmelab/gtd 42.0.0 → 44.0.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.
Files changed (62) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +96 -48
  3. package/claude/hooks/sketch.ts +1 -1
  4. package/dist/chunks/{reserved-D7MNY9QJ.mjs → reserved-DJk_6-ju.mjs} +75 -46
  5. package/dist/chunks/review-X7Jfz0dY.mjs +249 -0
  6. package/dist/chunks/{steering-oCDdQlpP.mjs → steering-D4LUQ-av.mjs} +95 -12
  7. package/dist/chunks/{tail-D2o2h5cc.mjs → tail-5w5mxGTq.mjs} +3 -3
  8. package/dist/gtd.bundle.mjs +488 -30
  9. package/dist/steering.mjs +2 -2
  10. package/dist/testkit.mjs +447 -27
  11. package/dist/web/main.js +42 -13
  12. package/dist/workflow-kit.mjs +61 -25
  13. package/dist/workflows/cleanup.mjs +2 -2
  14. package/dist/workflows/feature.mjs +7 -290
  15. package/dist/workflows/fix.mjs +3 -3
  16. package/dist/workflows/review.mjs +27 -7
  17. package/dist/workflows/rollout.mjs +1 -1
  18. package/dist/workflows.mjs +287 -161
  19. package/package.json +1 -1
  20. package/src/cli/Cli.test.ts +33 -1
  21. package/src/cli/Cli.ts +70 -6
  22. package/src/cli/History.ts +15 -0
  23. package/src/cli/Judges.test.ts +373 -0
  24. package/src/cli/Judges.ts +624 -0
  25. package/src/cli/Metrics.ts +3 -16
  26. package/src/cli/Program.test.ts +3 -1
  27. package/src/cli/Program.ts +9 -0
  28. package/src/steering/FORMATS.md +4 -4
  29. package/src/steering/index.ts +12 -1
  30. package/src/steering/review.test.ts +107 -0
  31. package/src/steering/review.ts +159 -9
  32. package/src/testkit/EmittedScriptRecognizer.ts +0 -14
  33. package/src/testkit/steps.ts +4 -0
  34. package/src/workflow-kit/review.test.ts +170 -26
  35. package/src/workflow-kit/review.ts +95 -19
  36. package/src/workflows/feature/coverage.ts +1 -1
  37. package/src/workflows/feature/feature.test.ts +8 -0
  38. package/src/workflows/feature/feature.ts +2 -2
  39. package/src/workflows/feature/index.ts +1 -1
  40. package/src/workflows/feature/lens-review.feature +16 -4
  41. package/src/workflows/feature/refactor.ts +1 -1
  42. package/src/workflows/feature/review-answers.feature +179 -0
  43. package/src/workflows/feature/seam.ts +1 -1
  44. package/src/workflows/feature/steps.test.ts +2 -76
  45. package/src/workflows/feature/steps.ts +5 -48
  46. package/src/workflows/fix/fix.feature +20 -21
  47. package/src/workflows/index.ts +1 -1
  48. package/src/workflows/review/review-door.feature +149 -0
  49. package/src/workflows/review/review.test.ts +1 -73
  50. package/src/workflows/review/review.ts +34 -6
  51. package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +6 -5
  52. package/src/workflows/shared/access.test.ts +2 -11
  53. package/src/workflows/shared/access.ts +0 -2
  54. package/src/workflows/{feature → shared}/review.test.ts +9 -61
  55. package/src/workflows/{feature → shared}/review.ts +40 -48
  56. package/src/workflows/shared/skills.test.ts +2 -0
  57. package/src/workflows/shared/skills.ts +2 -0
  58. package/src/workflows/shared/steps.test.ts +64 -2
  59. package/src/workflows/shared/steps.ts +40 -1
  60. package/src/workflows/shared/tail.ts +2 -2
  61. package/src/workflows/shared/text.test.ts +40 -89
  62. package/src/workflows/shared/text.ts +63 -54
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "42.0.0",
3
+ "version": "44.0.0",
4
4
  "description": "Drive the gtd loop inside Claude Code: agent turns run as subagents, gtd decides every beat",
5
5
  "author": {
6
6
  "name": "Philipp Melab"
package/README.md CHANGED
@@ -339,10 +339,10 @@ Then, in any repository:
339
339
  ```
340
340
 
341
341
  That starts a process from your requirements and drives it until it needs you.
342
- The requirements go to the top of `.gtd/TODO.md`; deferred review notes already
343
- there stay. A long, multi-line requirements paste still starts a process; if
344
- Claude Code never expanded the paste, gtd says it could not read it, and you add
345
- the requirements to the top of `.gtd/TODO.md` and run `/gtd`. `/gtd fix`,
342
+ The requirements go to the top of `.gtd/TODO.md`, above whatever it already
343
+ holds. A long, multi-line requirements paste still starts a process; if Claude
344
+ Code never expanded the paste, gtd says it could not read it, and you add the
345
+ requirements to the top of `.gtd/TODO.md` and run `/gtd`. `/gtd fix`,
346
346
  `/gtd review [base]`, `/gtd rollout <flag>` and `/gtd cleanup <flag>` (where the
347
347
  config exports them) take the doors described below; text whose word count does
348
348
  not fit a door's args (bare `/gtd rollout` included) starts a process from that
@@ -522,37 +522,41 @@ noted in steps 3 and 4 below.
522
522
 
523
523
  4. **You review.** After the last slice's quality lap gtd writes the review
524
524
  document itself (`build.review.composing`; no reviewer turn runs): one `## `
525
- chunk per design note the lap left, then — for reference, no tick boxes, no
526
- answer needed — what was **fixed automatically** (with the fixing commit) and
527
- what was **dropped as noise**. On `refactor-first` the refactor slice gets
528
- one of its own first, before the feature is built; see
529
- [the naive pass](#the-naive-pass). Review the diff yourself (`gtd base`
530
- prints its base). Land with no change to approve, or write what is wrong —
531
- disagreeing with a fix or a drop means writing a note of your own. Approving
532
- ends the process (at the refactor review, it moves on to the feature);
533
- feedback is judged note by note, each as `edit`, `question`, `nit` or
534
- `praise`. An `edit` sends the process back to step 2 for re-derived
535
- requirements — the built criteria keep their IDs (reworded in place when
536
- their behaviour changes) and new behaviour gets new ones — then the
537
- implementation is amended to meet them, with no new naive pass or seam judge,
538
- every criterion's coverage is judged again, the quality lap runs, and you
539
- review again. At the refactor review an `edit` re-derives the refactor
540
- instead. A `question` is answered inline under your note and the process
541
- stops at the review again — no new plan. A `nit` is fixed in one batch, the
542
- suite must go green (a red one gets fix turns first), then a fresh review of
543
- the change stops at the review again. `praise` is dropped; a round of only
544
- praise signs off. When a round mixes them, questions are answered and nits
545
- fixed first, then the edits are planned — risk: the planning lap may redo nit
546
- fixes it touches.
525
+ chunk per design note the lap left, each ending in three tick boxes — **fix
526
+ as suggested**, **won't fix**, or **your own answer** written over
527
+ `_your answer_` — then, for reference, no tick boxes, no answer needed — what
528
+ was **fixed automatically** (with the fixing commit) and what was **dropped
529
+ as noise**. On `refactor-first` the refactor slice gets one of its own first,
530
+ before the feature is built; see [the naive pass](#the-naive-pass). Review
531
+ the diff yourself (`gtd base` prints its base). **Every note needs exactly
532
+ one tick before `gtd land` accepts the review** (`answer-completeness`; an
533
+ open `- H:` thread waiting on the agent is the one exception). Fix as
534
+ suggested joins the nit batch below, unjudged; won't fix drops the note and
535
+ records it as `Gtd-Mark: wontfix=<title>` on the gate's commit; your own
536
+ answer is judged like a note. A note you answered is left out of the fresh
537
+ review after a nit fix; a round that also answered a question restores the
538
+ review instead, your ticks kept. Land with every note answered and nothing
539
+ else changed to approve, or write what is wrong — disagreeing with a fix or a
540
+ drop means writing a note of your own. Approving ends the process (at the
541
+ refactor review, it moves on to the feature); feedback is judged note by
542
+ note, each as `edit`, `question`, `nit` or `praise`. An `edit` sends the
543
+ process back to step 2 for re-derived requirements — the built criteria keep
544
+ their IDs (reworded in place when their behaviour changes) and new behaviour
545
+ gets new ones — then the implementation is amended to meet them, with no new
546
+ naive pass or seam judge, every criterion's coverage is judged again, the
547
+ quality lap runs, and you review again. At the refactor review an `edit`
548
+ re-derives the refactor instead. A `question` is answered inline under your
549
+ note and the process stops at the review again — no new plan. A `nit` is
550
+ fixed in one batch, the suite must go green (a red one gets fix turns first),
551
+ then a fresh review of the change stops at the review again. `praise` is
552
+ dropped; a round of only praise signs off. When a round mixes them, questions
553
+ are answered and nits fixed first, then the edits are planned — risk: the
554
+ planning lap may redo nit fixes it touches.
547
555
 
548
556
  Each `edit` note also gets a severity tier from four judge questions
549
- (security, reversibility, blast radius, new concept). `critical` and `should`
550
- notes are re-planned this round; `could` notes are deferred. A leading `!` on
551
- a note forces `critical` and skips the judge. Deferral appends to the
552
- committed `.gtd/TODO.md`; it never starts a process on its own, and it ships
553
- in the branch/PR unless you delete it. Landing clean at the gate after a
554
- deferral signs off without the deferred notes; the gate lists them. To pull
555
- one in, edit its line in `.gtd/TODO.md` when you start a process.
557
+ (security, reversibility, blast radius, new concept). Every tier is
558
+ re-planned this round, `critical` first, then `should`, then `could`; nothing
559
+ is deferred. A leading `!` on a note forces `critical` and skips the judge.
556
560
  `reviewSeverityMinP` sets the confidence floor (see
557
561
  [configuration](docs/configuration.md)). A hand-edit to code always plans a
558
562
  lap. Only a confident non-`edit` verdict skips the replan — a note whose
@@ -584,24 +588,28 @@ the agent may overturn the answer. See
584
588
  for its `ui:` settings.
585
589
 
586
590
  The bundled workflows are named `feature` (the ordinary start above), `review`
587
- and `fix`. They are independent: `review` and `fix` never plan, and they meet
588
- only in `.gtd/REVIEW.md`. Each brings its own door. This command:
591
+ and `fix`. `review` and `fix` never plan. Each brings its own door. This
592
+ command:
589
593
 
590
594
  ```bash
591
595
  gtd door review <commitish>
592
596
  ```
593
597
 
594
- starts a pure review of everything from `<commitish>` to HEAD. It goes straight
595
- to the quality lap and a review, with no planning and no building; unlike
596
- `feature`, its lap fixes every finding and a reviewer turn
597
- (`build.review.reviewing`) writes `.gtd/REVIEW.md`, risks and all. Sign off and
598
- it closes. Leave notes and the process ends with `.gtd/REVIEW.md` (and the
599
- reviewer's original, `.gtd/REVIEW.orig.md`) left for `gtd door fix`. Until `fix`
600
- consumes them, starting `feature` or `review` is refused; remove both files to
601
- abandon the hand-off. `gtd door fix` fixes those notes and every `Risk:` in one
602
- turn, then asks for your review of the fix. With no review waiting, it repairs a
603
- red baseline as its own reviewed commit instead. `gtd doors` lists the doors of
604
- the workflows your `gtd.config.ts` exports, plus any it adds.
598
+ starts a review of everything from `<commitish>` to HEAD — the same review a
599
+ `feature` process ends with, minus planning and building. A green gate, then the
600
+ quality lenses: mechanical findings are fixed, noise is dropped, and the rest
601
+ become notes in `.gtd/REVIEW.md`, one tick box each. Tick `fix as suggested` or
602
+ write your own answer and the process fixes it in place — your own answers that
603
+ ask for a change are collected into `.gtd/REQUIREMENTS.md` and built by one
604
+ `feedback.fixing` turn, with no design phase and no criteria — under the health
605
+ loop, then asks for your review again. Sign off (or tick `won't fix`) and it
606
+ closes; nothing is left behind. A note asking for new behaviour belongs in a
607
+ `feature` process: tick `won't fix` and start one. `gtd door fix` repairs a red
608
+ baseline as its own reviewed commit; a note at its review is left in
609
+ `.gtd/REVIEW.md` (beside the reviewer's original, `.gtd/REVIEW.orig.md`) for the
610
+ next `gtd door fix`, and until that runs, starting `feature` or `review` is
611
+ refused — remove both files to abandon the hand-off. `gtd doors` lists the doors
612
+ of the workflows your `gtd.config.ts` exports, plus any it adds.
605
613
  `gtd --workflow <name>` starts any workflow by name.
606
614
 
607
615
  The workflow itself is a plain async TypeScript function, and the
@@ -740,6 +748,7 @@ full trail with `gtd history fetch | sh`, then `gtd history show <ref>`.
740
748
  gtd history list # one line per kept process
741
749
  gtd history show <ref> # replay its steps, judge verdicts and approvals
742
750
  gtd history metrics [ref...] # how the kept processes went, per process and in total
751
+ gtd history judges [ref...] # how often each judge's verdicts were overturned
743
752
  gtd history fetch | sh # pull every kept ref from origin
744
753
  ```
745
754
 
@@ -772,6 +781,45 @@ process kept from that branch. A rate is `null` without a decided slice. Each
772
781
  process names its `workflow` (`null` for the default one); the total spans every
773
782
  ref measured, so name the refs to leave another workflow's processes out.
774
783
 
784
+ `gtd history judges` calibrates three of the bundled `feature` workflow's judges
785
+ from the `Gtd-Judge` verdicts the kept refs recorded. It never replays and asks
786
+ no model. A verdict is **overturned** when later history contradicts it:
787
+
788
+ - **retry** (`health.judge`) — an `identical` verdict, when the next fix turn's
789
+ check went green anyway (no outcome when a person edited code in between)
790
+ - **triage** (`quality.triage`) — a `mechanical` or `noise` verdict the review
791
+ listed under _Fixed automatically_ or _Dropped as noise_, when the human
792
+ edited that finding's line there or wrote under it
793
+ - **clear** (`design.clear`) — a `yes` that skipped design (landed on
794
+ `design.normalise`), when a later human design step in the same process
795
+ changed the acceptance criteria
796
+
797
+ Any other verdict has no outcome. The report counts verdicts, scored verdicts
798
+ and overturns per judge and per `p` bucket (`0.8-0.9: 1/2` — one of two
799
+ overturned). `--var <threshold>=<value>` re-decides every past verdict under
800
+ another threshold and prints the decisions that would change, against the
801
+ threshold each process recorded (a process that recorded none is left out):
802
+
803
+ ```sh
804
+ gtd history judges --var qualityTriageMinP=0.9
805
+ # what-if qualityTriageMinP=0.9: 7 noise drops become notes (2 overturned)
806
+ ```
807
+
808
+ The thresholds are `judgeIdenticalMinP` (retry) and `qualityTriageMinP`
809
+ (triage). The judge's evidence-budget cut is not recorded, so a cut verdict is
810
+ scored and re-decides as if it was whole. A retry verdict on an empty report is
811
+ not scored, as the flow drops it. `--json` prints
812
+ `{ processes, total, whatIf }`, each process and the total with its `judges`.
813
+ `--jsonl` prints one line per verdict instead — `process`, `judge`, `commit`,
814
+ `step`, `question`, `state` (the evidence the tree still shows: the finding, the
815
+ two check reports, the sketch without its numbered criteria), `answer`, `p` and
816
+ `outcome` (`overturned`, `held` or `null`) — ready to fine-tune a local model
817
+ on:
818
+
819
+ ```sh
820
+ gtd history judges --jsonl > judges.jsonl
821
+ ```
822
+
775
823
  `gtd history fetch` only prints a fetch script; pipe it into `sh` to run it.
776
824
  Beyond that one ship push, refs are not pushed or fetched by default. The ref
777
825
  holds every step commit, including steering files and code a later step removed,
@@ -828,8 +876,8 @@ install for any of these but `./testkit`: gtd resolves a `gtd.config.ts`'s
828
876
  The `gtd` bin's commands — `next`, `land`, `abandon`, `restore`, `validate`,
829
877
  `check`, `uncheck`, `install`, `summary`, `base`, `door`, `doors`, `slices`,
830
878
  `version`, `help`, `history list`, `history show`, `history metrics`,
831
- `history fetch`, `strays`, and the tools `ui`, `lsp`, `loop`, `context-hook`,
832
- `judge` (`judge answer`, `judge run`) — are listed in the
879
+ `history judges`, `history fetch`, `strays`, and the tools `ui`, `lsp`, `loop`,
880
+ `context-hook`, `judge` (`judge answer`, `judge run`) — are listed in the
833
881
  [CLI reference](https://github.com/pmelab/gtd/blob/main/docs/cli.md).
834
882
  `gtd slices` lists the spans a flow's `slice()` calls mark. It refuses (exit 1)
835
883
  when HEAD neither closes nor sits inside a process, or when today's config
@@ -1,5 +1,5 @@
1
1
  // New requirements go above what the steering file already holds, untouched:
2
- // a deferred review note the start diff alters reads as the human's edit.
2
+ // anything already there that the start diff alters reads as the human's edit.
3
3
  export function withRequirements(existing: string, requirements: string): string {
4
4
  const head = `${requirements.trimEnd()}\n`
5
5
  return existing.trim() ? `${head}\n${existing}` : head
@@ -1,4 +1,5 @@
1
1
  import { accessFor, agent, answered, changesSince, check, codeThreads, defineWorkflow, displayPath, env, head, human, judge, mark, numeric, quote, read, refuse, scope, skillsFor, start, vars } from "../flows.mjs";
2
+ import { d as ANSWER_FIX, f as ANSWER_LEAD, p as ANSWER_WONT_FIX, v as FREE_TEXT_PLACEHOLDER } from "./steering-D4LUQ-av.mjs";
2
3
  import { escalation as escalation$1, gate as gate$1, healthy as healthy$1, lensScopes, lensesOf, removedAssertions } from "../workflow-kit.mjs";
3
4
  import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct, n as Boolean$, s as Number$ } from "./Schema-C6fLlQzx.mjs";
4
5
  //#region src/workflows/shared/registry.ts
@@ -437,9 +438,9 @@ const SUMMARY_TITLES = [
437
438
  const unboxed = (body) => body.replace(/^(\s*(?:>\s*)*(?:[-*+]|\d+[.)])\s+)\[[ xX]\]\s?/gm, "$1");
438
439
  const short = (hash) => hash.slice(0, 7);
439
440
  /**
440
- * \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note, then the fixed and
441
- * noise lists, with no tick boxes. Chunk titles are made unique — notes are
442
- * matched to their chunk by title.
441
+ * \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note, each ending in its
442
+ * answer boxes, then the fixed and noise lists. Chunk titles are made unique —
443
+ * notes and answers are matched to their chunk by title.
443
444
  */
444
445
  const composedReview = (base, report) => {
445
446
  const used = new Set(SUMMARY_TITLES);
@@ -450,11 +451,16 @@ const composedReview = (base, report) => {
450
451
  return name;
451
452
  };
452
453
  const chunk = (title, body) => `## ${title}\n\n${body.trim()}`;
454
+ const answerBoxes = `${ANSWER_LEAD}\n\n- [ ] ${ANSWER_FIX}\n- [ ] ${ANSWER_WONT_FIX}\n- [ ] ${FREE_TEXT_PLACEHOLDER}`;
455
+ const notes = report.notes.map((n) => ({
456
+ ...n,
457
+ title: unique(n.title)
458
+ })).filter((n) => report.answered?.has(n.title) !== true);
453
459
  const weakened = (report.weakened ?? []).flatMap(({ path, lines }) => lines.map((a) => `- ${path} — ${a.scenario} — ${a.line}`));
454
460
  const chunks = [
455
- ...report.nitFix === void 0 ? [] : [chunk("Fixed from your notes", `Your nits were fixed in ${short(report.nitFix)}; the list below is from before.`)],
461
+ ...report.nitFix === void 0 ? [] : [chunk("Fixed from your notes", `Your nits and fix-as-suggested notes were worked on in ${short(report.nitFix)}; the notes you answered are left out — check the diff.`)],
456
462
  ...weakened.length === 0 ? [] : [chunk("Weakened scenarios", `These scenarios lost an assertion the review base had:\n\n${weakened.join("\n")}`)],
457
- ...report.notes.map((n) => chunk(unique(n.title), n.unresolved === true ? `_The fixer left this unresolved._\n\n${unboxed(n.body)}` : n.unclear === true ? `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${unboxed(n.body)}` : unboxed(n.body) || "_No detail given._")),
463
+ ...notes.map((n) => chunk(n.title, `${n.unresolved === true ? `_The fixer left this unresolved._\n\n${unboxed(n.body)}` : n.unclear === true ? `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${unboxed(n.body)}` : unboxed(n.body) || "_No detail given._"}\n\n${answerBoxes}`)),
458
464
  ...report.fixed.length === 0 ? [] : [chunk("Fixed automatically", report.fixed.map((f) => `- ${f.title} (${short(f.commit)})`).join("\n"))],
459
465
  ...report.noise.length === 0 ? [] : [chunk("Dropped as noise", report.noise.map((f) => `- ${f.title} (p=${f.p})`).join("\n"))]
460
466
  ];
@@ -537,10 +543,6 @@ ${stateFileRules}
537
543
  ${footnoteFoldIn}${codeThreadReplies()}
538
544
  - The only state file this turn touches is \`.gtd/REQUIREMENTS.md\`
539
545
  — no other files for notes or output
540
- - Lines of a \`## Deferred review notes\` section of \`.gtd/TODO.md\`
541
- that the start diff does not touch are parked by review, never folded;
542
- the human's edits to such a line (or its removal) are input like any
543
- other part of the start diff
544
546
  - \`.gtd/TODO.md\` is the likely home of the sketch that started
545
547
  this process — the human's input, folded into the concerns
546
548
  below like any other part of the start diff; never a state
@@ -833,8 +835,12 @@ const buildReviewAwaitReviewMessage = (base) => `\`.gtd/REVIEW.md\` holds what t
833
835
  per finding that needs your judgement — it questions a criterion or the
834
836
  approach, or the fixer could not resolve it — then, for reference only, what
835
837
  was **fixed automatically** (with the fixing commit) and what was **dropped
836
- as noise**. Nothing there needs an answer; disagreeing with a fix or a drop
837
- means writing a note of your own.
838
+ as noise**. Each finding chunk ends in three boxes — tick exactly one:
839
+ \`fix as suggested\` (fixed in the nit batch below), \`won't fix\` (dropped,
840
+ recorded in the commit), or write your own answer over \`_your answer_\` and
841
+ tick it (judged like a note). Every landing here is refused until each
842
+ finding holds exactly one tick. The fixed and noise lists need no answer;
843
+ disagreeing with a fix or a drop means writing a note of your own.
838
844
 
839
845
  Review the diff yourself, with whatever tool you like — gtd checks
840
846
  nothing out and touches no ref; \`gtd base\` prints this same hash any
@@ -844,8 +850,8 @@ working tree:
844
850
  git diff ${base}
845
851
  When you've been through the whole diff, run \`gtd land\`:
846
852
 
847
- - **Sign off** — leave no comment — no note in
848
- \`.gtd/REVIEW.md\`, no code edit — to close the process.
853
+ - **Sign off** — answer every finding, leave no other comment — no note
854
+ in \`.gtd/REVIEW.md\`, no code edit — to close the process.
849
855
  Every turn commit stays on the
850
856
  branch; run \`gtd summary\` afterward for a closing-message
851
857
  prompt.
@@ -972,6 +978,15 @@ ${notes.length === 0 ? "(none)\n" : notesCapture(notes)}
972
978
  The reviewer's risks are:
973
979
 
974
980
  ${risks.length === 0 ? "(none)\n" : notesCapture(risks)}`;
981
+ const reviewFeedbackFixPrompt = () => `${stateFileRules}
982
+ - Read \`.gtd/REQUIREMENTS.md\` — the concerns the human's review asked for,
983
+ one \`## \` heading each, in build order
984
+ - Build every concern in this one turn; an answered question is context,
985
+ not a concern
986
+ - Where a change is behavioural, add a test that fails without it
987
+ - Leave \`.gtd/REQUIREMENTS.md\` untouched — the next step removes it
988
+ - Leave everything uncommitted and finish your turn
989
+ `;
975
990
  const buildReviewReviewMissingMessage = (commit) => `The review round committed no \`.gtd/REVIEW.md\` (at ${commit}), so there is
976
991
  nothing to sign off on.
977
992
 
@@ -992,27 +1007,6 @@ const wrapItem = (item) => {
992
1007
  } else line = line === "" ? word : `${line} ${word}`;
993
1008
  return [...out, line];
994
1009
  };
995
- /** Appends one section of `notes` to `.gtd/TODO.md`, wrapped the way oxfmt writes it; an existing file is only ever appended to. */
996
- const deferNotesScript = (notes, round) => {
997
- return `mkdir -p .gtd
998
- if [ -s .gtd/TODO.md ]; then
999
- [ -z "$(tail -c1 .gtd/TODO.md)" ] || printf '\\n' >> .gtd/TODO.md
1000
- printf '\\n' >> .gtd/TODO.md
1001
- fi
1002
- printf '%s\\n' ${[
1003
- `## Deferred review notes (${round.slice(0, 7)})`,
1004
- "",
1005
- ...notes.flatMap((n) => wrapItem(`- ${noteHead(n)}: ${n.text.replace(/\s+/g, " ")}`))
1006
- ].map(quote).join(" ")} >> .gtd/TODO.md
1007
- `;
1008
- };
1009
- /** The gate right after a deferral: landing clean signs off without these notes. */
1010
- const buildReviewAwaitAfterDeferMessage = (base, deferred) => `${buildReviewAwaitReviewMessage(base)}
1011
- Deferred to \`.gtd/TODO.md\` just now, NOT acted on in this process — landing
1012
- clean signs off without them:
1013
-
1014
- ${deferred.map((n) => `- ${noteHead(n)}: ${n.text.replace(/\s*\n\s*/g, " ")}`).join("\n")}
1015
- `;
1016
1010
  const buildReviewTriageMessage = () => `Judging each note the human added to \`.gtd/REVIEW.md\` (a line note, a
1017
1011
  footnote, or prose under a chunk) with one verdict: \`edit\` (a change
1018
1012
  request), \`question\`, \`nit\` (a small fix needing no re-plan) or
@@ -1022,16 +1016,18 @@ default; a skipped judgment never dismisses a note).
1022
1016
 
1023
1017
  Also judge each note's severity, one id per note: \`note-<n>.security\`,
1024
1018
  \`note-<n>.reversibility\`, \`note-<n>.blastRadius\` and
1025
- \`note-<n>.newConcept\`.
1019
+ \`note-<n>.newConcept\`. An own answer under a finding's boxes is judged
1020
+ the same way, keyed \`answer-<n>\`.
1026
1021
  `;
1027
1022
  const closedFold = (round, target) => round === "edits" ? `- Fold every hand-edit and every note the human left that round into
1028
- \`${target}\`
1023
+ \`${target}\` — a note ticked \`fix as suggested\` or answered in its
1024
+ own words included; one ticked \`won't fix\` is dropped
1029
1025
  ` : round === "notes" ? `- Fold each note that asks for a change into \`${target}\`
1030
1026
  ` : `- Fold each \`edit\` note the capture lists, no other note, and every
1031
1027
  hand-edit, into \`${target}\`
1032
1028
  `;
1033
1029
  /** `refactor` retargets the prompt at `.gtd/REFACTOR.md` and fences it to the strays. A concern outside the fence is not folded; if nothing at all is written the landing is refused, but a partly buildable round drops the rest silently. */
1034
- const buildReviewCollectingPrompt = (capture, refactor) => {
1030
+ const buildReviewCollectingPrompt = (capture, refactor, round) => {
1035
1031
  const target = refactor ? ".gtd/REFACTOR.md" : ".gtd/REQUIREMENTS.md";
1036
1032
  const fence = refactor ? `- The next refactor may change only these paths, plus an existing test
1037
1033
  file's call sites, and never a flag read:
@@ -1106,12 +1102,16 @@ ${refactor ? refactor.round === "reply" ? `- This turn replies inside \`.gtd/REV
1106
1102
  ${closedFold(refactor.round, target)}` : `- This turn writes \`${target}\` (the folded concerns) and
1107
1103
  replies inside \`.gtd/REVIEW.md\` (thread replies only; the file stays
1108
1104
  in the tree) — you classify, you do not build
1109
- - Fold every concluded thread, ticked answer and hand-edit — and each
1110
- \`edit\` note the capture lists, no other note — into
1111
- \`${target}\` and delete the folded threads from
1112
- \`.gtd/REVIEW.md\`; append one \`- A:\` reply to each thread
1113
- whose last entry is a \`- H:\` question
1114
- `}- When the capture names an answering commit, carry each answered
1105
+ - Fold every concluded thread and hand-edit — and each \`edit\` note
1106
+ the capture lists, no other note — into \`${target}\` and
1107
+ delete the folded threads from \`.gtd/REVIEW.md\`; append one \`- A:\`
1108
+ reply to each thread whose last entry is a \`- H:\` question
1109
+ ${round === "edits" ? `- A hand-edit round routes no answer box itself: fold each note
1110
+ ticked \`fix as suggested\` or answered in its own words, too; drop one
1111
+ ticked \`won't fix\`
1112
+ ` : `- Never fold a note's ticked answer box the capture does not list: gtd
1113
+ routes those itself
1114
+ `}`}- When the capture names an answering commit, carry each answered
1115
1115
  question into \`${target}\`'s \`## Answered Questions\` as
1116
1116
  \`### <the note>\` plus its answer — the lap replaces \`.gtd/REVIEW.md\`
1117
1117
  - Keep capture order, and carry each note's \`(tier: reason)\`
@@ -1615,6 +1615,36 @@ const awaitReview = (base, message = buildReviewAwaitReviewMessage(base)) => hum
1615
1615
  base,
1616
1616
  acceptClean: true
1617
1617
  });
1618
+ /** Answer every `question` note inline in `.gtd/REVIEW.md`. */
1619
+ const answerReviewQuestions = (notes) => agentWithSkills("review.answer-review-questions", buildReviewAnswerQuestionsPrompt(notes), {
1620
+ label: "Answering your questions",
1621
+ file: REVIEW,
1622
+ mode: "review",
1623
+ model: planner(),
1624
+ system: reviewerSystem()
1625
+ });
1626
+ /** Fix every `nit` note in one turn. */
1627
+ const fixNits = (notes, access, rules) => {
1628
+ const fixing = (name) => agentWithSkills(name, buildReviewFixNitsPrompt(notes, rules), {
1629
+ label: "Fixing your nits",
1630
+ file: REVIEW,
1631
+ model: planner(),
1632
+ system: reviewerSystem()
1633
+ });
1634
+ return access === void 0 ? fixing("review.fix.nits.fixing") : scope({
1635
+ name: "review.fix.nits",
1636
+ access
1637
+ }, () => fixing("fixing"));
1638
+ };
1639
+ /** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
1640
+ const collecting = (capture, round) => agentWithSkills("review.collecting", buildReviewCollectingPrompt(capture, void 0, round), {
1641
+ label: "Collecting your feedback",
1642
+ file: REQUIREMENTS,
1643
+ mode: "qa",
1644
+ model: planner(),
1645
+ system: reviewerSystem(),
1646
+ allowEmpty: true
1647
+ });
1618
1648
  /** The round at commit `round` left no `.gtd/REVIEW.md`. */
1619
1649
  const reviewMissing = (round) => human("review.review-missing", {
1620
1650
  message: buildReviewReviewMissingMessage(round),
@@ -1811,6 +1841,7 @@ const skills = (vars) => {
1811
1841
  return {
1812
1842
  design: ["spec-driven-development", "planning-and-task-breakdown"],
1813
1843
  notes: ["incremental-implementation", "code-simplification"],
1844
+ feedback: ["test-driven-development", "incremental-implementation"],
1814
1845
  ...build,
1815
1846
  ...Object.fromEntries(Object.entries(build).map(([name, list]) => [`refactor.${name}`, list])),
1816
1847
  refactor: ["ponytail"],
@@ -1829,11 +1860,9 @@ const skills = (vars) => {
1829
1860
  const access = (vars) => ({
1830
1861
  design: { write: [] },
1831
1862
  "build.review": { write: [".gtd/REVIEW.md"] },
1832
- "build.review.defer": { write: [".gtd/TODO.md"] },
1833
1863
  "build.review.fix.nits": {},
1834
1864
  "build.review.fix.risks": {},
1835
1865
  "refactor.build.review": { write: [".gtd/REVIEW.md"] },
1836
- "refactor.build.review.defer": { write: [".gtd/TODO.md"] },
1837
1866
  ...lensScopes("build.quality", lensesOf(vars.qualityReviews ?? ""), () => ({ write: [] })),
1838
1867
  ...lensScopes("refactor.build.quality", lensesOf(vars.refactorQualityReviews ?? ""), () => ({ write: [] }))
1839
1868
  });
@@ -1845,4 +1874,4 @@ const steering = {
1845
1874
  [REVIEW]: "review"
1846
1875
  };
1847
1876
  //#endregion
1848
- export { implementFixPrompt as $, agentWithSkills as A, registryEntry as At, coverageMessage as B, fixCheck as C, unwindFailure as Ct, reviewMissing as D, flagMentions as Dt, planner as E, testFileGlobs as Et, buildReviewTriageMessage as F, designGateAnswerMessage as G, coveringPrompt as H, builderSystem as I, finisherSystem as J, designSystem as K, clearCriteriaMessage as L, buildReviewAwaitAfterDeferMessage as M, buildReviewCollectingPrompt as N, reviewQuality as O, flagReferences as Ot, buildReviewFixNitsPrompt as P, implementBuildingPrompt as Q, clearCriteriaQuestion as R, coder as S, unwindFailedMessage as St, fixRisks as T, envDefaults as Tt, criterionLine as U, coverageQuestion as V, deferNotesScript as W, handOffAwaitReviewMessage as X, fixReviewNotesPrompt as Y, idleMessage as Z, REFACTOR_NOTE as _, seamByHandMessage as _t, flagModeOn as a, qualityTriageMessage as at, SLICES as b, startGateBlockedMessage as bt, baseline as c, refactorDroppedRefusal as ct, guarded as d, refactorRules as dt, naiveApplyFailure as et, healthy as f, reviewEditNotesCapture as ft, REFACTOR as g, reviewerSystem as gt, QUALITY as h, reviewNotesCapture as ht, skills as i, qualityFindingsFile as it, buildReviewAnswerQuestionsPrompt as j, requireFlag as jt, reviewing as k, flipProblems as kt, escalation as l, refactorFixPrompt as lt, HANDOFF as m, reviewGateBlockedMessage as mt, summary as n, normaliseCriteriaScript as nt, flagShipped as o, qualityUnresolvedMessage as ot, FEEDBACK as p, reviewEditsCapture as pt, designTriagePrompt as q, access as r, oversizedMessage as rt, shipped as s, refactorBuildingPrompt as st, steering as t, naivePassPrompt as tt, gate as u, refactorNotePrompt as ut, REQUIREMENTS as v, seamSteerMessage as vt, fixQuality as w, defaults as wt, awaitReview as x, uncoveredMessage as xt, REVIEW as y, slicingPrompt as yt, composedReview as z };
1877
+ export { naiveApplyFailure as $, reviewMissing as A, registryEntry as At, coverageMessage as B, coder as C, unwindFailure as Ct, fixQuality as D, flagMentions as Dt, fixNits as E, testFileGlobs as Et, buildReviewTriageMessage as F, designSystem as G, coveringPrompt as H, builderSystem as I, fixReviewNotesPrompt as J, designTriagePrompt as K, clearCriteriaMessage as L, reviewing as M, agentWithSkills as N, fixRisks as O, flagReferences as Ot, buildReviewCollectingPrompt as P, implementFixPrompt as Q, clearCriteriaQuestion as R, awaitReview as S, unwindFailedMessage as St, fixCheck as T, envDefaults as Tt, criterionLine as U, coverageQuestion as V, designGateAnswerMessage as W, idleMessage as X, handOffAwaitReviewMessage as Y, implementBuildingPrompt as Z, REFACTOR_NOTE as _, seamByHandMessage as _t, flagModeOn as a, qualityUnresolvedMessage as at, SLICES as b, startGateBlockedMessage as bt, baseline as c, refactorFixPrompt as ct, guarded as d, reviewEditNotesCapture as dt, naivePassPrompt as et, healthy as f, reviewEditsCapture as ft, REFACTOR as g, reviewerSystem as gt, QUALITY as h, reviewNotesCapture as ht, skills as i, qualityTriageMessage as it, reviewQuality as j, requireFlag as jt, planner as k, flipProblems as kt, escalation as l, refactorNotePrompt as lt, HANDOFF as m, reviewGateBlockedMessage as mt, summary as n, oversizedMessage as nt, flagShipped as o, refactorBuildingPrompt as ot, FEEDBACK as p, reviewFeedbackFixPrompt as pt, finisherSystem as q, access as r, qualityFindingsFile as rt, shipped as s, refactorDroppedRefusal as st, steering as t, normaliseCriteriaScript as tt, gate as u, refactorRules as ut, REQUIREMENTS as v, seamSteerMessage as vt, collecting as w, defaults as wt, answerReviewQuestions as x, uncoveredMessage as xt, REVIEW as y, slicingPrompt as yt, composedReview as z };