@pmelab/gtd 39.0.0 → 40.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 (32) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +54 -53
  3. package/dist/chunks/{reserved-B6tTYjeb.mjs → reserved-Qt5FM6Ia.mjs} +7 -5
  4. package/dist/chunks/{tail-_CZTP7Hu.mjs → tail-tAHx_AHe.mjs} +1 -1
  5. package/dist/workflows/cleanup.mjs +2 -2
  6. package/dist/workflows/feature.mjs +98 -37
  7. package/dist/workflows/fix.mjs +2 -2
  8. package/dist/workflows/review.mjs +2 -2
  9. package/dist/workflows/rollout.mjs +1 -1
  10. package/dist/workflows.mjs +103 -40
  11. package/package.json +1 -1
  12. package/skills/authoring/SKILL.md +3 -2
  13. package/src/workflows/feature/coverage.feature +5 -5
  14. package/src/workflows/feature/criteria.test.ts +20 -1
  15. package/src/workflows/feature/criteria.ts +24 -0
  16. package/src/workflows/feature/fast-path.feature +4 -4
  17. package/src/workflows/feature/feature.ts +82 -22
  18. package/src/workflows/feature/flag-mode.feature +2 -2
  19. package/src/workflows/feature/one-step-land.feature +4 -4
  20. package/src/workflows/feature/planning.ts +3 -2
  21. package/src/workflows/feature/quality-unresolved.feature +6 -6
  22. package/src/workflows/feature/refactor-absorbs.feature +3 -3
  23. package/src/workflows/feature/refactor-first.feature +5 -5
  24. package/src/workflows/feature/refactor-rebuild.feature +2 -2
  25. package/src/workflows/feature/refactor-review.feature +8 -8
  26. package/src/workflows/feature/review-gate.feature +271 -0
  27. package/src/workflows/feature/seam-rejection.feature +4 -4
  28. package/src/workflows/feature/seam.feature +2 -2
  29. package/src/workflows/feature/seam.ts +17 -7
  30. package/src/workflows/feature/slicing.feature +63 -4
  31. package/src/workflows/shared/text.ts +7 -5
  32. package/src/workflows/review/review-door.feature +0 -121
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "39.0.0",
3
+ "version": "40.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
@@ -419,10 +419,9 @@ phone UI does not show them).
419
419
 
420
420
  ### The workflow it ships with
421
421
 
422
- One built-in workflow drives all of that. From where you sit, it has three
422
+ One built-in workflow drives all of that. From where you sit, it has four
423
423
  moments — everything between them runs without you, with the judged exceptions
424
- noted in step 3 below — plus a fourth, review, only on `refactor-first` or when
425
- you open the `review` door.
424
+ noted in steps 3 and 4 below.
426
425
 
427
426
  1. **You sketch.** Change anything, or write the idea into `.gtd/TODO.md`. Rough
428
427
  is fine; it is treated as a sketch, not as work.
@@ -449,14 +448,12 @@ you open the `review` door.
449
448
  against the full suite (`testCommand`) and fixed until it passes — so keep
450
449
  that suite fast; gtd has no lighter one. Scenario wording is not frozen: a
451
450
  review's `## Scenarios` chunk marks any assertion an existing scenario lost
452
- as `Weakened:` — **risk: `feature` has no review, so a build turn that
453
- weakens a scenario reaches `idle` unflagged unless you open the `review`
454
- door.** After the build the quality lap runs, and the process ends: the
455
- feature workflow has no human review of the finished change. Two points along
456
- the process (one with flag mode off) are judged rather than always asking you
457
- outright — each stops and hands you a verdict to make (`gtd judge answer`, or
458
- land with a clean tree to accept the conservative default, which never skips
459
- work; `gtd judge run --provider fixed --answers <path>` — or the
451
+ as `Weakened:`, so you sign the scenarios off at step 4. After the build the
452
+ quality lap runs. Three points along the process (two with flag mode off) are
453
+ judged rather than always asking you outright — each stops and hands you a
454
+ verdict to make (`gtd judge answer`, or land with a clean tree to accept the
455
+ conservative default, which never skips work;
456
+ `gtd judge run --provider fixed --answers <path>` — or the
460
457
  `GTD_JUDGE_ANSWERS` env var, inline JSON — answers one from a file, piped
461
458
  between `gtd judge --json` and `gtd judge answer`;
462
459
  `gtd judge run --provider jev` asks TypeSafe's Jev instead, with the key in
@@ -474,6 +471,8 @@ you open the `review` door.
474
471
  or get rejected and retried?
475
472
  - Every red round after the first: was the failure identical, new, or
476
473
  progress?
474
+ - After you review: is each of your notes an edit, a question, a nit or
475
+ praise?
477
476
 
478
477
  gtd itself never calls a judge: `gtd loop`, the Claude Code mod and the
479
478
  reference driver answer these through `gtd judge run` (auto selection: jev
@@ -485,22 +484,20 @@ you open the `review` door.
485
484
  and skip a gate unattended.**
486
485
 
487
486
  Once the change is built, the whole change goes through a qualitative review
488
- lap: six lenses, one turn each, in order — `correctness`, `owasp-security`,
489
- `ponytail-review`, `test-audit`, `conventions`, `spec-challenge`.
490
- `correctness`, `owasp-security` and `spec-challenge` run on the planner model
491
- (`smart`), the other three on the coder model (`base`); a `qualityReviews`
492
- entry `<lens>=<model>` picks a lens's own. Each traces the change from its
493
- own angle and records every finding, blocking or not; one fix turn then fixes
494
- ALL of them once, with no re-review after the fix. A clean turn means
495
- approval only when that lens found nothing at all. This lap is where code
496
- quality is looked at, and every round pays for it. It is the last look the
497
- feature workflow takes: the process ends at `idle` once it is clean (and the
498
- suite green), with no review rest — **risk: nothing re-reads the code the
499
- single fix turn writes; only the suite gates it.** Findings the fix cap
500
- leaves in `.gtd/QUALITY.md` rest at `build.quality-unresolved` until you
501
- delete or empty the file; the suite then runs on any hand edits. **Nobody
502
- signs the finished change off unless you open the `review` door** (below).
503
- The `fix` door repairs a red baseline through this same lap.
487
+ lap before you see anything: six lenses, one turn each, in order —
488
+ `correctness`, `owasp-security`, `ponytail-review`, `test-audit`,
489
+ `conventions`, `spec-challenge`. `correctness`, `owasp-security` and
490
+ `spec-challenge` run on the planner model (`smart`), the other three on the
491
+ coder model (`base`); a `qualityReviews` entry `<lens>=<model>` picks a
492
+ lens's own. Each traces the change from its own angle and records every
493
+ finding, blocking or not; one fix turn then fixes ALL of them once, with no
494
+ re-review after the fix. A clean turn means approval only when that lens
495
+ found nothing at all. This lap is where code quality is looked at, and every
496
+ round pays for it. It never replaces step 4 — your review stays the final
497
+ gate, and nothing here skips it. Findings the fix cap leaves in
498
+ `.gtd/QUALITY.md` rest at `build.quality-unresolved` until you delete or
499
+ empty the file; the suite then runs on any hand edits before the review. The
500
+ `fix` door (below) repairs a red baseline through this same lap.
504
501
 
505
502
  A red suite that keeps failing past a few fix attempts escalates instead of
506
503
  retrying forever: an agent turn reads the failing output and writes
@@ -514,28 +511,30 @@ you open the `review` door.
514
511
  `Gtd-Mark: flaky=<check>` (see
515
512
  [Re-check before a person](docs/configuration.md#re-check-before-a-person)).
516
513
 
517
- 4. **You review — the refactor, or on request.** The feature workflow asks for
518
- one review only on `refactor-first`: of the refactor slice, before the
519
- feature is built (see [the naive pass](#the-naive-pass)). For the finished
520
- change run `gtd door review <commitish>` (`/gtd review [base]`); that review
521
- hands notes to `fix` instead of re-planning (below). Either way you get a
522
- review document listing what changed and what to look at. Before you see it,
514
+ 4. **You review.** After the last slice's quality lap you get a review document
515
+ over the whole change, listing what changed and what to look at (on
516
+ `refactor-first` the refactor slice gets one of its own first, before the
517
+ feature is built; see [the naive pass](#the-naive-pass)). Before you see it,
523
518
  an automatic risk-fix pass (`build.review.fix.risks.fixing`) runs. Any risk
524
519
  the reviewer names (a note opening with `Risk:`) is fixed first, the suite
525
520
  kept green, and the review rewritten — once per review round, so a risk the
526
521
  rewrite still names reaches you unfixed; risk: a fix lands with no check that
527
- the risk was real. Tick the boxes to approve, or write what is wrong. At the
528
- refactor review, approving moves on to the feature; feedback is judged note
529
- by note (a third judged point), each as `edit`, `question`, `nit` or
530
- `praise`. An `edit` re-derives the refactor (in a custom workflow built on
531
- `buildTail`, it sends the process back to step 2 for re-derived
532
- requirements). A `question` is answered inline under your note and the
533
- process stops at the review again — no new plan. A `nit` is fixed in one
534
- batch, the suite must go green (a red one gets fix turns first), then a fresh
535
- review of the change stops at the review again. `praise` is dropped; a round
536
- of only praise signs off. When a round mixes them, questions are answered and
537
- nits fixed first, then the edits are planned — risk: the planning lap may
538
- redo nit fixes it touches.
522
+ the risk was real. Tick the boxes to approve, or write what is wrong.
523
+ Approving ends the process (at the refactor review, it moves on to the
524
+ feature); feedback is judged note by note, each as `edit`, `question`, `nit`
525
+ or `praise`. An `edit` sends the process back to step 2 for re-derived
526
+ requirements — the built criteria keep their IDs (reworded in place when
527
+ their behaviour changes) and new behaviour gets new ones — then the
528
+ implementation is amended to meet them, with no new naive pass or seam judge,
529
+ every criterion's coverage is judged again, the quality lap runs, and you
530
+ review again. At the refactor review an `edit` re-derives the refactor
531
+ instead. A `question` is answered inline under your note and the process
532
+ stops at the review again — no new plan. A `nit` is fixed in one batch, the
533
+ suite must go green (a red one gets fix turns first), then a fresh review of
534
+ the change stops at the review again. `praise` is dropped; a round of only
535
+ praise signs off. When a round mixes them, questions are answered and nits
536
+ fixed first, then the edits are planned — risk: the planning lap may redo nit
537
+ fixes it touches.
539
538
 
540
539
  Each `edit` note also gets a severity tier from four judge questions
541
540
  (security, reversibility, blast radius, new concept). `critical` and `should`
@@ -555,7 +554,7 @@ you open the `review` door.
555
554
  You never talk to it. Every exchange is a file in `.gtd/` that you edit in your
556
555
  own editor, and every answer you give is a commit. Your test suite is the gate
557
556
  throughout: a red baseline stops the process before it starts, and a red build
558
- is fixed before the process ends.
557
+ is fixed before review.
559
558
 
560
559
  Prefer a phone or another device over your editor? `gtd ui` opens the file whose
561
560
  turn it is as a small web app, over Tailscale — including a step that names no
@@ -635,14 +634,15 @@ criterion ID the naive pass's tests named, and the suite must be green with
635
634
  them. A wrong approach is retried twice, then rests for you to steer or overrule
636
635
  it. `gtd slices` lists the `refactor` and `feature` slices. A refactor that
637
636
  already carried the whole feature leaves the rebuild only the carried-over
638
- tests, and the feature's quality lap covers the whole change. Once the
637
+ tests, and the feature's quality lap and review cover the whole change. Once the
639
638
  implementation is green, a judge checks every criterion ID has a covering test:
640
639
  an uncovered one goes back to the agent (twice, then to you) instead of ending
641
640
  the feature, and each covered ID is recorded as a `Gtd-Mark: covers=<id>`
642
641
  trailer. See
643
642
  [Acceptance-criteria coverage](docs/configuration.md#acceptance-criteria-coverage).
644
- Feedback at the refactor review re-runs the refactor, never design; a
645
- refactor-review change the refactor cannot build is refused, and the only way
643
+ Feedback at the feature review re-runs design and the implementation, never the
644
+ naive pass; feedback at the refactor review re-runs the refactor, never design;
645
+ a refactor-review change the refactor cannot build is refused, and the only way
646
646
  out is `gtd abandon`. A process from 27.5.0 or later whose judge ruled
647
647
  `refactor-first` must `gtd abandon` too. Each slice's quality lap reviews only
648
648
  that slice. Details in
@@ -652,10 +652,11 @@ that slice. Details in
652
652
  criteria than the cap is built in slices: after design an agent groups related
653
653
  criteria (same flow or subsystem) into `.gtd/SLICES.md`, as few slices as the
654
654
  cap allows, and each slice — naive pass, refactor and rebuild when judged so,
655
- health loop, quality lap — runs in order from where the previous one landed.
656
- `gtd slices` lists them as `feature-1`, `feature-2`, … (`refactor-<n>` ahead of
657
- a refactored one). A change within the cap is one `feature` slice, as before.
658
- See [Coarse slices](docs/configuration.md#coarse-slices).
655
+ health loop, quality lap — runs in order from where the previous one landed; the
656
+ review follows the last one and covers the whole change. `gtd slices` lists them
657
+ as `feature-1`, `feature-2`, … (`refactor-<n>` ahead of a refactored one). A
658
+ change within the cap is one `feature` slice, as before. See
659
+ [Coarse slices](docs/configuration.md#coarse-slices).
659
660
 
660
661
  **Flag mode (`flagMode`, on by default).** On, the naive pass's judge runs as
661
662
  above and a rebuilt implementation is asked for a new release flag (none at a
@@ -421,14 +421,14 @@ What each change does next (then run \`gtd land\`):
421
421
  - **Continue** — having undone the sketch by hand, check the test baseline is green and start triage (**start-gate.check**).
422
422
  `;
423
423
  const qualityUnresolvedMessage = () => `The quality lap's fix turns hit their cap and \`.gtd/QUALITY.md\` still
424
- holds findings. Nothing reviews the change after this, so the process
425
- will not end with them open.
424
+ holds findings. Nothing after this fixes them, so the process will not
425
+ go on with them open.
426
426
 
427
427
  Fix what matters by hand, then delete or empty \`.gtd/QUALITY.md\` —
428
428
  dropping a finding is your call to make.
429
429
 
430
430
  What each change does next (then run \`gtd land\`):
431
- - **Delete or empty \`.gtd/QUALITY.md\`** — the suite runs on your edits, then the process ends (**idle**).
431
+ - **Delete or empty \`.gtd/QUALITY.md\`** — the suite runs on your edits, then the next slice is built, or after the last one you review the change (**build.review.reviewing**).
432
432
  - **Anything else** — rests here again.
433
433
  `;
434
434
  const startGateBlockedMessage = () => `The test baseline is red — gtd will not start new work on a broken suite.
@@ -466,8 +466,10 @@ reword a criterion in place, append a new one with the next unused
466
466
  number, delete a dropped one — never renumber, never reuse an ID. When
467
467
  the file holds no list (a review loop-back rewrites it), recover the
468
468
  last settled one from history (\`git log -p -- .gtd/REQUIREMENTS.md\`)
469
- and carry its IDs over. Landing is refused while a criterion has no ID or
470
- two share one.
469
+ and carry its IDs over: after a review a built criterion keeps its ID,
470
+ reworded in place when the review changes its behaviour, and new
471
+ behaviour gets a new ID. Landing is refused while a criterion has no ID
472
+ or two share one.
471
473
  `;
472
474
  const riskTierBlock = `
473
475
  ## Risk tier and production signals
@@ -1,6 +1,6 @@
1
1
  import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
2
2
  import { fixFindings, lensEntriesOf, lensOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
3
- import { C as fixCheck, D as reviewMissing, O as reviewQuality, T as fixRisks, Y as handOffAwaitReviewMessage, d as guarded, f as healthy, h as QUALITY, k as reviewing, l as escalation, w as fixQuality, x as awaitReview, y as REVIEW } from "./reserved-B6tTYjeb.mjs";
3
+ import { C as fixCheck, D as reviewMissing, O as reviewQuality, T as fixRisks, Y as handOffAwaitReviewMessage, d as guarded, f as healthy, h as QUALITY, k as reviewing, l as escalation, w as fixQuality, x as awaitReview, y as REVIEW } from "./reserved-Qt5FM6Ia.mjs";
4
4
  //#region src/workflows/shared/tail.ts
5
5
  /** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
6
6
  const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
@@ -1,6 +1,6 @@
1
1
  import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
2
- import { Dt as registryEntry, Ot as requireFlag, S as coder, St as envDefaults, Tt as flagReferences, i as skills, n as summary, o as flagShipped, r as access, t as steering, xt as defaults } from "../chunks/reserved-B6tTYjeb.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-_CZTP7Hu.mjs";
2
+ import { Dt as registryEntry, Ot as requireFlag, S as coder, St as envDefaults, Tt as flagReferences, i as skills, n as summary, o as flagShipped, r as access, t as steering, xt as defaults } from "../chunks/reserved-Qt5FM6Ia.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-tAHx_AHe.mjs";
4
4
  //#region src/workflows/cleanup/cleanup.ts
5
5
  const removingPrompt = (name, registry) => `Clean up the release flag "${name}", which is default-on: delete its entry in ${registry}, and remove every \`GTD_FLAG_${name.toUpperCase()}\` check from the shipped sources. Keep the flag-on path; delete code only the off path reached, including tests that pin the off path.`;
6
6
  const cleanupFlow = async () => {
@@ -1,7 +1,7 @@
1
1
  import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, slice, start, vars, wrote } from "../flows.mjs";
2
2
  import { lensEntriesOf, review as review$1 } from "../workflow-kit.mjs";
3
- import { $ as naiveApplyFailure, A as agentWithSkills, B as coverageQuestion, C as fixCheck, Ct as testFileGlobs, D as reviewMissing, E as planner, F as buildReviewTriageMessage, G as designSystem, H as criterionLine, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, M as buildReviewAwaitAfterDeferMessage, N as buildReviewCollectingPrompt, P as buildReviewFixNitsPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, S as coder, St as envDefaults, T as fixRisks, U as deferNotesScript, V as coveringPrompt, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as SLICES, bt as unwindFailure, ct as refactorRules, d as guarded, et as naivePassPrompt, f as healthy, ft as reviewNotesCapture, g as REFACTOR, gt as slicingPrompt, h as QUALITY, ht as seamSteerMessage, i as skills, it as refactorBuildingPrompt, j as buildReviewAnswerQuestionsPrompt, k as reviewing, lt as reviewEditNotesCapture, m as HANDOFF, mt as seamByHandMessage, n as summary, nt as oversizedMessage, ot as refactorFixPrompt, p as FEEDBACK, pt as reviewerSystem, r as access, rt as qualityUnresolvedMessage, s as shipped, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, ut as reviewEditsCapture, v as REQUIREMENTS, vt as uncoveredMessage, x as awaitReview, xt as defaults, y as REVIEW, yt as unwindFailedMessage, z as coverageMessage } from "../chunks/reserved-B6tTYjeb.mjs";
4
- import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-_CZTP7Hu.mjs";
3
+ import { $ as naiveApplyFailure, A as agentWithSkills, B as coverageQuestion, C as fixCheck, Ct as testFileGlobs, D as reviewMissing, E as planner, F as buildReviewTriageMessage, G as designSystem, H as criterionLine, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, M as buildReviewAwaitAfterDeferMessage, N as buildReviewCollectingPrompt, P as buildReviewFixNitsPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, S as coder, St as envDefaults, T as fixRisks, U as deferNotesScript, V as coveringPrompt, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as SLICES, bt as unwindFailure, ct as refactorRules, d as guarded, et as naivePassPrompt, f as healthy, ft as reviewNotesCapture, g as REFACTOR, gt as slicingPrompt, h as QUALITY, ht as seamSteerMessage, i as skills, it as refactorBuildingPrompt, j as buildReviewAnswerQuestionsPrompt, k as reviewing, lt as reviewEditNotesCapture, m as HANDOFF, mt as seamByHandMessage, n as summary, nt as oversizedMessage, ot as refactorFixPrompt, p as FEEDBACK, pt as reviewerSystem, r as access, rt as qualityUnresolvedMessage, s as shipped, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, ut as reviewEditsCapture, v as REQUIREMENTS, vt as uncoveredMessage, x as awaitReview, xt as defaults, y as REVIEW, yt as unwindFailedMessage, z as coverageMessage } from "../chunks/reserved-Qt5FM6Ia.mjs";
4
+ import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-tAHx_AHe.mjs";
5
5
  import { posix } from "node:path";
6
6
  //#region src/workflows/shared/fences.ts
7
7
  const markFenced = (text) => {
@@ -112,6 +112,19 @@ const requireCriteria = (file) => {
112
112
  if (criteria.length > 0 && unnamed.length === 0 && twice.length === 0) return;
113
113
  refuse(`gtd land: acceptance-criteria: ${file} has ${criteria.length === 0 ? `no "## Acceptance Criteria" list` : unnamed.length > 0 ? `${unnamed.length} criterion without an ID:\n${unnamed.map((c) => ` - ${c.text}`).join("\n")}` : `duplicate IDs: ${[...new Set(twice)].join(", ")}`} — list every criterion under "## Acceptance Criteria" as "- AC-<n>: <checkable criterion>", each ID unique and never reused`);
114
114
  };
115
+ const idNumber = (id) => Number(id.slice(3));
116
+ /** Why `listed` renumbers or reuses an earlier ID; `undefined` when it does neither. */
117
+ const unstableIds = (listed, ids) => {
118
+ const top = Math.max(0, ...ids.used.map(idNumber));
119
+ const reused = listed.filter((id) => !ids.closed.includes(id) && idNumber(id) <= top);
120
+ if (reused.length === 0) return void 0;
121
+ return `numbers ${reused.join(", ")} at or below AC-${top}, an ID already handed out — keep a built criterion's ID (reword it in place when the review changes it), and number new ones from AC-${top + 1}`;
122
+ };
123
+ /** Refuse criteria in `file` that renumber or reuse an earlier ID. */
124
+ const requireStableIds = (file, ids) => {
125
+ const why = unstableIds(criterionIds(read(file) ?? ""), ids);
126
+ if (why !== void 0) refuse(`gtd land: criteria-ids: ${file} ${why}`);
127
+ };
115
128
  /** Refuse a turn that changed the acceptance criteria in `file`: after the design gate only another design lap may. */
116
129
  const requireCriteriaFrozen = (file) => {
117
130
  const change = changes().get(file);
@@ -267,13 +280,14 @@ const noteGate = async (file, answer) => {
267
280
  if (!hasThreadFor("agent", file)) requireAnswers(file);
268
281
  return true;
269
282
  };
270
- /** Triage the sketch since `base` into requirements, until no product question is open. */
271
- const design = (base) => scope("design", async () => {
283
+ /** Triage the sketch since `base` into requirements, until no product question is open; `guard` holds every round's criteria. */
284
+ const design = (base, guard) => scope("design", async () => {
272
285
  do {
273
286
  await triage(base);
274
287
  requireProgress(REQUIREMENTS);
275
288
  requireReplies(REQUIREMENTS);
276
289
  requireCriteria(REQUIREMENTS);
290
+ guard?.();
277
291
  } while (await noteGate(REQUIREMENTS, answerProductQuestions));
278
292
  });
279
293
  //#endregion
@@ -1085,27 +1099,31 @@ const build = (options) => continuing(phase("build", requireCriteriaIfEdited), a
1085
1099
  }
1086
1100
  };
1087
1101
  });
1088
- function implement(first, tier, amend) {
1089
- const options = typeof first === "object" && first !== null ? first : {
1102
+ async function implement(first, tier, amend) {
1103
+ await implementing(typeof first === "object" && first !== null ? first : {
1090
1104
  reference: first,
1091
1105
  tier,
1092
1106
  amend
1093
- };
1094
- return scope("implement", async () => {
1095
- const since = head();
1096
- const acceptance = options.rebuild?.acceptance;
1097
- const built = options.naive === void 0 || !await applied(options.naive);
1098
- if (built) await build(options);
1099
- else requireCriteriaFrozen(REQUIREMENTS);
1100
- if (built && acceptance !== void 0) requireAcceptance({
1101
- ...acceptance,
1102
- ids: settled(acceptance.ids, options.slice)
1103
- }, since);
1104
- await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
1105
- await requireCoverage(options.base, options.slice);
1106
- await closing(options.more === true);
1107
1107
  });
1108
1108
  }
1109
+ /** `implement`, resolving with the criterion IDs the requirements listed when it closed. */
1110
+ const implementing = (options) => scope("implement", async () => {
1111
+ const since = head();
1112
+ const acceptance = options.rebuild?.acceptance;
1113
+ const built = options.naive === void 0 || !await applied(options.naive);
1114
+ if (built) await build(options);
1115
+ else requireCriteriaFrozen(REQUIREMENTS);
1116
+ if (built && acceptance !== void 0) requireAcceptance({
1117
+ ...acceptance,
1118
+ ids: settled(acceptance.ids, options.slice)
1119
+ }, since);
1120
+ await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
1121
+ await requireCoverage(options.base, options.slice);
1122
+ const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
1123
+ await closing(moreToBuild(options.more));
1124
+ return ids;
1125
+ });
1126
+ const moreToBuild = (more) => typeof more === "function" ? more() : more === true;
1109
1127
  /** The last slice's closing, for a last slice a steer left with no criterion to build. */
1110
1128
  const closeOut = () => scope("implement", () => closing(false));
1111
1129
  /** A slice with `more` after it keeps the criteria and the slicing for the next one. */
@@ -1340,32 +1358,37 @@ const unwind = () => {
1340
1358
  const buildFeature = async (firstBase) => {
1341
1359
  const tier = riskTierOn() ? recordRisk() : void 0;
1342
1360
  const slices = await slicing();
1361
+ const handedOut = new Set(listedIds());
1343
1362
  const one = slices.length === 1;
1363
+ const built = (i) => sliceIds(sliceCriteria(slices, i))?.length !== 0;
1344
1364
  for (const i of slices.keys()) {
1345
- const more = i < slices.length - 1;
1346
- const criteria = sliceCriteria(slices, i);
1347
- if (sliceIds(criteria)?.length === 0) {
1348
- if (!more) await closeOut();
1349
- continue;
1350
- }
1365
+ if (!built(i)) continue;
1351
1366
  const n = one ? "" : `-${i + 1}`;
1352
1367
  await buildSlice(i === 0 ? firstBase : head(), {
1353
1368
  tier,
1354
- criteria,
1369
+ criteria: sliceCriteria(slices, i),
1355
1370
  refactor: `refactor${n}`,
1356
1371
  feature: `feature${n}`,
1357
- more
1372
+ more: () => slices.some((_, j) => j > i && built(j)),
1373
+ reviewBase: one ? void 0 : firstBase,
1374
+ handedOut
1358
1375
  });
1359
1376
  }
1360
1377
  };
1361
- /** A naive pass (judged only with `flagMode` on), then implement and the quality lap. */
1378
+ const listedIds = () => criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
1379
+ const gather = (handedOut) => {
1380
+ for (const id of listedIds()) handedOut.add(id);
1381
+ return [...handedOut];
1382
+ };
1383
+ /** A naive pass (judged only with `flagMode` on), then implement and the quality lap; after the last slice, the review. */
1362
1384
  const buildSlice = async (sliceBase, build) => {
1363
1385
  const seam = await seamBuild(sliceBase, {
1364
1386
  criteria: build.criteria,
1365
1387
  refactor: build.refactor
1366
1388
  });
1389
+ const used = gather(build.handedOut);
1367
1390
  await slice(build.feature, async () => {
1368
- await implement({
1391
+ const closed = await implementing({
1369
1392
  reference: seam.reference,
1370
1393
  tier: build.tier,
1371
1394
  afterRefactor: seam.refactored,
@@ -1375,14 +1398,23 @@ const buildSlice = async (sliceBase, build) => {
1375
1398
  base: build.criteria === void 0 ? void 0 : sliceBase,
1376
1399
  more: build.more
1377
1400
  });
1401
+ const more = build.more();
1378
1402
  const carried = seam.rebuild?.acceptance?.tests ?? [];
1379
1403
  const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? sliceBase : seam.base;
1380
- const guard = build.more ? () => requireCriteriaFrozen(REQUIREMENTS) : void 0;
1381
- await tail(settleQuality, {
1382
- green: true,
1383
- lensBase: base,
1384
- guard,
1385
- guardRests: false
1404
+ if (more) {
1405
+ const guard = () => requireCriteriaFrozen(REQUIREMENTS);
1406
+ await tail(settleQuality, {
1407
+ green: true,
1408
+ lensBase: base,
1409
+ guard,
1410
+ guardRests: false
1411
+ });
1412
+ return;
1413
+ }
1414
+ const reviewBase = build.reviewBase ?? base;
1415
+ await featureFeedback(await reviewedTail(reviewBase, base), base, {
1416
+ closed,
1417
+ used
1386
1418
  });
1387
1419
  });
1388
1420
  };
@@ -1398,6 +1430,35 @@ const settleQuality = async () => {
1398
1430
  }
1399
1431
  if (rested) await healthy(() => fixCheck());
1400
1432
  };
1433
+ /** The quality lap, then the human review since `base`. */
1434
+ const reviewedTail = (base, lensBase, lap) => tail(async (escalations) => {
1435
+ await settleQuality();
1436
+ return review(base, escalations, { lap });
1437
+ }, {
1438
+ green: true,
1439
+ lensBase
1440
+ });
1441
+ const featureFeedback = async (first, lensBase, ids) => {
1442
+ let outcome = first;
1443
+ let closed = ids.closed;
1444
+ const handedOut = /* @__PURE__ */ new Set([...ids.used, ...ids.closed]);
1445
+ while (outcome.verdict === "feedback") {
1446
+ await reUnwind(outcome);
1447
+ const lap = {
1448
+ closed,
1449
+ used: [...handedOut]
1450
+ };
1451
+ await design(outcome.base, () => {
1452
+ requireStableIds(REQUIREMENTS, lap);
1453
+ gather(handedOut);
1454
+ });
1455
+ closed = await implementing({
1456
+ tier: riskTierOn() ? recordRisk() : void 0,
1457
+ amend: true
1458
+ });
1459
+ outcome = await reviewedTail(outcome.base, lensBase, "feedback");
1460
+ }
1461
+ };
1401
1462
  /** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
1402
1463
  const planAndBuild = async (firstBase, stated) => {
1403
1464
  if (!await skipDesign(riskTierOn() ? void 0 : stated)) await design(firstBase);
@@ -1444,4 +1505,4 @@ const feature = shipped(ordinaryStart);
1444
1505
  //#region src/workflows/feature/index.ts
1445
1506
  var feature_default = feature;
1446
1507
  //#endregion
1447
- export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, holdsCallSites, implement, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, triage, unwind };
1508
+ export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, holdsCallSites, implement, implementing, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, triage, unstableIds, unwind };
@@ -1,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
2
2
  import { d as reviewNotes, f as reviewRisks } from "../chunks/steering-DVILanVf.mjs";
3
- import { A as agentWithSkills, J as fixReviewNotesPrompt, S as coder, St as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, xt as defaults, y as REVIEW } from "../chunks/reserved-B6tTYjeb.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-_CZTP7Hu.mjs";
3
+ import { A as agentWithSkills, J as fixReviewNotesPrompt, S as coder, St as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, xt as defaults, y as REVIEW } from "../chunks/reserved-Qt5FM6Ia.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-tAHx_AHe.mjs";
5
5
  //#region src/workflows/fix/fix.ts
6
6
  /**
7
7
  * Fix every note a human left in a handed-off `.gtd/REVIEW.md` and every
@@ -1,6 +1,6 @@
1
1
  import { start } from "../flows.mjs";
2
- import { St as envDefaults, dt as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate, xt as defaults } from "../chunks/reserved-B6tTYjeb.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-_CZTP7Hu.mjs";
2
+ import { St as envDefaults, dt as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate, xt as defaults } from "../chunks/reserved-Qt5FM6Ia.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-tAHx_AHe.mjs";
4
4
  //#region src/workflows/review/review.ts
5
5
  /**
6
6
  * Pure review of everything since `reviewBase`: a green gate, the quality
@@ -1,5 +1,5 @@
1
1
  import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
2
- import { Et as flipProblems, Ot as requireFlag, S as coder, St as envDefaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, wt as flagMentions, xt as defaults } from "../chunks/reserved-B6tTYjeb.mjs";
2
+ import { Et as flipProblems, Ot as requireFlag, S as coder, St as envDefaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, wt as flagMentions, xt as defaults } from "../chunks/reserved-Qt5FM6Ia.mjs";
3
3
  //#region src/workflows/rollout/rollout.ts
4
4
  const flippingPrompt = (name, registry) => `Roll out the release flag "${name}": in ${registry}, set \`defaultOn: true\` on its entry. Change nothing else — no other entry, no other field, no other file, no comments.`;
5
5
  const suiteBlockedMessage = () => "The suite is red after the rollout. Fix it by hand — a rollout may only touch the flag's registry entry — then land.";