@pmelab/gtd 50.0.0 → 51.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 (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +32 -24
  3. package/dist/chunks/{reserved-CdOzKFVX.mjs → reserved-CUkk9XgL.mjs} +93 -4
  4. package/dist/chunks/{review-BChXEl2Y.mjs → review-BaCXlIT2.mjs} +2 -2
  5. package/dist/chunks/{tail-DRM2eC1z.mjs → tail-CX6Pdsql.mjs} +1 -1
  6. package/dist/workflows/cleanup.mjs +2 -2
  7. package/dist/workflows/feature.mjs +144 -11
  8. package/dist/workflows/fix.mjs +2 -2
  9. package/dist/workflows/review.mjs +3 -3
  10. package/dist/workflows/rollout.mjs +1 -1
  11. package/dist/workflows.mjs +234 -12
  12. package/package.json +1 -1
  13. package/src/workflows/feature/coverage.feature +1 -0
  14. package/src/workflows/feature/fast-path.feature +1 -0
  15. package/src/workflows/feature/feature.ts +4 -1
  16. package/src/workflows/feature/flag-mode.feature +1 -1
  17. package/src/workflows/feature/lens-models.feature +1 -0
  18. package/src/workflows/feature/lens-review.feature +1 -0
  19. package/src/workflows/feature/one-step-land.feature +1 -0
  20. package/src/workflows/feature/prototype-rounds.feature +152 -0
  21. package/src/workflows/feature/prototype.feature +419 -0
  22. package/src/workflows/feature/prototype.test.ts +43 -0
  23. package/src/workflows/feature/prototype.ts +181 -0
  24. package/src/workflows/feature/refactor-absorbs.feature +9 -1
  25. package/src/workflows/feature/refactor-first.feature +13 -3
  26. package/src/workflows/feature/refactor-rebuild.feature +29 -5
  27. package/src/workflows/feature/refactor-review.feature +111 -13
  28. package/src/workflows/feature/refactor.test.ts +14 -4
  29. package/src/workflows/feature/refactor.ts +2 -0
  30. package/src/workflows/feature/review-answers.feature +1 -0
  31. package/src/workflows/feature/review-gate.feature +1 -0
  32. package/src/workflows/feature/reviewer-walkthrough.feature +1 -0
  33. package/src/workflows/feature/seam-rejection.feature +1 -0
  34. package/src/workflows/feature/seam.feature +1 -0
  35. package/src/workflows/feature/seam.test.ts +3 -2
  36. package/src/workflows/feature/seam.ts +19 -5
  37. package/src/workflows/feature/severity-budget.feature +1 -0
  38. package/src/workflows/feature/severity-sorted-review.feature +1 -0
  39. package/src/workflows/feature/slice-findings.feature +1 -0
  40. package/src/workflows/feature/slicing-cap.feature +9 -0
  41. package/src/workflows/feature/slicing.feature +1 -0
  42. package/src/workflows/feature/standards.feature +1 -0
  43. package/src/workflows/shared/options.ts +1 -0
  44. package/src/workflows/shared/prose.ts +6 -0
  45. package/src/workflows/shared/skills.test.ts +4 -0
  46. package/src/workflows/shared/skills.ts +5 -0
  47. package/src/workflows/shared/steps.ts +1 -0
  48. package/src/workflows/shared/text.ts +104 -3
  49. package/src/workflows/shared/vars.ts +3 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "50.0.0",
3
+ "version": "51.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
@@ -545,30 +545,31 @@ noted in steps 3 and 4 below.
545
545
  each led by its tier and reason (`_critical: security, one-way door_`) — the
546
546
  phone shows it on the chunk's card. A chunk the judge is unsure of, or whose
547
547
  evidence the budget cut, is `critical`. On `refactor-first` the refactor
548
- slice gets a review of its own first, composed by gtd from its lap's notes,
549
- before the feature is built; see [the naive pass](#the-naive-pass). **Every
550
- note needs exactly one tick before `gtd land` accepts the review**
551
- (`answer-completeness`; an open `- H:` thread waiting on the agent is the one
552
- exception). Fix as suggested joins the nit batch below, unjudged; won't fix
553
- drops the note and records it as `Gtd-Mark: wontfix=<title>` on the gate's
554
- commit; your own answer is judged like a note. A note you answered is left
555
- out of the fresh review after a nit fix. Land with every note answered and
556
- nothing else changed to approve, or write what is wrong — disagreeing with a
557
- fix or a drop means writing a note of your own. Approving ends the process
558
- (at the refactor review, it moves on to the feature); feedback is judged note
559
- by note, each as `edit`, `question`, `nit` or `praise`. An `edit` sends the
560
- process back to step 2 for re-derived requirements — the built criteria keep
561
- their IDs (reworded in place when their behaviour changes) and new behaviour
562
- gets new ones — then the implementation is amended to meet them, with no new
563
- naive pass or seam judge, every criterion's coverage is judged again, the
564
- quality lap runs, and you review again. At the refactor review an `edit`
565
- re-derives the refactor instead. A `question` is answered inline under your
566
- note and the process stops at the review again — no new plan. A `nit` is
567
- fixed in one batch, the suite must go green (a red one gets fix turns first),
568
- then a fresh review of the change stops at the review again. `praise` is
569
- dropped; a round of only praise signs off. When a round mixes them, questions
570
- are answered and nits fixed first, then the edits are planned — risk: the
571
- planning lap may redo nit fixes it touches.
548
+ slice gets a walkthrough of its own first, covering the whole refactor on
549
+ every round, before the feature is built; see
550
+ [the naive pass](#the-naive-pass). **Every note needs exactly one tick before
551
+ `gtd land` accepts the review** (`answer-completeness`; an open `- H:` thread
552
+ waiting on the agent is the one exception). Fix as suggested joins the nit
553
+ batch below, unjudged; won't fix drops the note and records it as
554
+ `Gtd-Mark: wontfix=<title>` on the gate's commit; your own answer is judged
555
+ like a note. A note you answered is left out of the fresh review after a nit
556
+ fix. Land with every note answered and nothing else changed to approve, or
557
+ write what is wrong — disagreeing with a fix or a drop means writing a note
558
+ of your own. Approving ends the process (at the refactor review, it moves on
559
+ to the feature); feedback is judged note by note, each as `edit`, `question`,
560
+ `nit` or `praise`. An `edit` sends the process back to step 2 for re-derived
561
+ requirements — the built criteria keep their IDs (reworded in place when
562
+ their behaviour changes) and new behaviour gets new ones — then the
563
+ implementation is amended to meet them, with no new naive pass or seam judge,
564
+ every criterion's coverage is judged again, the quality lap runs, and you
565
+ review again. At the refactor review an `edit` re-derives the refactor
566
+ instead. A `question` is answered inline under your note and the process
567
+ stops at the review again — no new plan. A `nit` is fixed in one batch, the
568
+ suite must go green (a red one gets fix turns first), then a fresh review of
569
+ the change stops at the review again. `praise` is dropped; a round of only
570
+ praise signs off. When a round mixes them, questions are answered and nits
571
+ fixed first, then the edits are planned — risk: the planning lap may redo nit
572
+ fixes it touches.
572
573
 
573
574
  Each `edit` note also gets a severity tier from four judge questions
574
575
  (security, reversibility, blast radius, new concept). Every tier is
@@ -692,6 +693,13 @@ an uncovered one goes back to the agent (twice, then to you) instead of ending
692
693
  the feature, and each covered ID is recorded as a `Gtd-Mark: covers=<id>`
693
694
  trailer. See
694
695
  [Acceptance-criteria coverage](docs/configuration.md#acceptance-criteria-coverage).
696
+ Then an agent uses the slice by hand — the real program in a scratch directory,
697
+ the real page, guided by a project skill or a manual testing section in
698
+ `AGENTS.md`, `CLAUDE.md` or `README.md` — and writes `.gtd/PROTOTYPE.md`: per
699
+ criterion pass, fail or blocked, with the steps run and what it saw. Failures
700
+ are fixed and re-tested before the quality lap, up to `prototypeRounds` (3) fix
701
+ rounds; past them, with only blocked items, or with no way to test found, it
702
+ rests for you. See [The hands-on test](docs/configuration.md#the-hands-on-test).
695
703
  Feedback at the feature review re-runs design and the implementation, never the
696
704
  naive pass; feedback at the refactor review re-runs the refactor, never design;
697
705
  a refactor-review change the refactor cannot build is refused, and the only way
@@ -243,6 +243,7 @@ const defaults = {
243
243
  qualityReviews: "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge",
244
244
  refactorQualityReviews: "correctness, ponytail-review, conventions",
245
245
  continuations: "3",
246
+ prototypeRounds: "3",
246
247
  continuationStall: "2"
247
248
  };
248
249
  /** Environment settings. */
@@ -320,6 +321,11 @@ task in front of you end to end, tests first, and come back to fix
320
321
  things when redirected — a failing check, or reviewer feedback. Stay
321
322
  strictly inside that task; never touch code outside it. Treat feedback as the
322
323
  diagnosis to act on; discard it, with reason, only when simply wrong.`;
324
+ const testerPersona = `You are the hands-on tester in gtd's build pipeline: use what was just
325
+ built the way a person would — run the real program, open the real
326
+ page, call the real endpoint — and report exactly what you did and
327
+ what you saw. Never fix anything yourself, and never pass a criterion
328
+ you did not watch hold.`;
323
329
  const finisherPersona = `You are the closing identity in gtd's build pipeline — the last coder
324
330
  before it closes. Fix a late-breaking failing check after sign-off,
325
331
  focused and minimal. Every turn lands its own commit — nothing here
@@ -745,8 +751,11 @@ const acceptanceCriteriaBlock = `
745
751
  Whichever lap this is, \`.gtd/REQUIREMENTS.md\` carries a
746
752
  \`## Acceptance Criteria\` section: one \`- AC-<n>: <criterion>\` line
747
753
  per criterion, numbered from \`AC-1\`. They bind every later step —
748
- the build is done when each one holds — so each must be checkable: a
749
- test or a command shows it holds or not. A later lap keeps every ID:
754
+ the build is done when each one holds — so each must be behaviour a
755
+ test can pin: one that fails when it does not hold. A green suite, a
756
+ passing typecheck or lint, a generator or build that runs — the health
757
+ check enforces those on every lap, so none is a criterion; fold what it
758
+ must produce into a behaviour a test can observe. A later lap keeps every ID:
750
759
  reword a criterion in place, append a new one with the next unused
751
760
  number, delete a dropped one — never renumber, never reuse an ID. When
752
761
  the file holds no list (a review loop-back rewrites it), recover the
@@ -854,7 +863,7 @@ const clearCriteriaQuestion = {
854
863
  id: "clear",
855
864
  primitive: "noul",
856
865
  instructions: "state.criteria lists the acceptance criteria the human's sketch (state.sketch) stated, as gtd numbered them. Decide whether they can stand as the binding definition of done, with no design conversation.",
857
- criteria: "yes: every criterion is clear and checkable — a test or a command can show it holds or not — and together they say what to build. no: any one criterion is vague, subjective or untestable, or the sketch leaves an open product question. Unsure means no."
866
+ criteria: "yes: every criterion is clear behaviour a test can pin — a test would fail if it did not hold — and together they say what to build. no: any one criterion is vague, subjective or untestable, only a command can check it (a suite, typecheck, lint, build or generator run that passes), or the sketch leaves an open product question. Unsure means no."
858
867
  };
859
868
  const clearCriteriaMessage = () => `The sketch stated its own acceptance criteria. Run \`gtd judge answer\` and
860
869
  pipe \`yes\` when every one is clear and checkable, so design is skipped and
@@ -1773,6 +1782,80 @@ Two ways forward (then run \`gtd land\`):
1773
1782
  - **Overrule** — land a clean tree to finish without them. The slice records
1774
1783
  the IDs it covers and, as \`uncovered\`, the ones it does not
1775
1784
  `;
1785
+ const testerSystem = () => `${testerPersona}
1786
+
1787
+ ${agentConduct}`;
1788
+ const PROTOTYPE_FORMAT = `- Write \`.gtd/PROTOTYPE.md\`, replacing any earlier report: one \`## \` chunk
1789
+ per criterion, headed \`## AC-<n>: pass\`, \`## AC-<n>: fail\` or
1790
+ \`## AC-<n>: blocked\`, then one chunk per problem you found outside any
1791
+ criterion, headed \`## Problem <short title>: fail\` (\`: pass\` once a
1792
+ re-test shows it fixed). Under each: the exact steps you ran, what you
1793
+ observed (output, a screenshot path, the error), and for a failure what
1794
+ you expected instead
1795
+ - \`blocked\` is for a criterion you could not exercise, or one whose
1796
+ expected behaviour itself looks wrong — say which and why: a person
1797
+ decides it, never a fixer`;
1798
+ const prototypeTestingPrompt = (slice, retest) => `${stateFileRules}
1799
+ - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1800
+ ${sliceLine(slice)}- The change is built and its suite is green. Now test it by hand: use
1801
+ every acceptance criterion the way a person would and watch what
1802
+ happens. Running the test suite is not this — it already ran
1803
+ - Find how this project is exercised for real, in this order: a project
1804
+ skill that launches or drives it (e.g. \`.claude/skills/*\`); a manual
1805
+ testing or running section in \`AGENTS.md\`, \`CLAUDE.md\` or \`README.md\`;
1806
+ failing those, the project's shape — a browser or Playwright for a web
1807
+ project, the built CLI in a scratch directory for a CLI, a request client
1808
+ for an API
1809
+ - Found none and recognised no shape: write only a chunk headed
1810
+ \`## No testing instructions\` saying what you looked for, and finish —
1811
+ a person is asked; never guess
1812
+ - Test in a disposable place only — a scratch directory outside this
1813
+ repository, a throwaway repository, a dev server on a free port — never
1814
+ against the user's real data or this working tree. Point every tool's
1815
+ output (screenshots, traces, test results) outside the repository too
1816
+ ${retest ? `- This is a re-test: \`.gtd/PROTOTYPE.md\` holds the last round's report and
1817
+ a fix turn or a person has changed the code since. Re-test every failed or blocked criterion and
1818
+ every problem it lists in full, then re-run the passed ones quickly
1819
+ ` : ""}${PROTOTYPE_FORMAT}
1820
+ - Change nothing but \`.gtd/PROTOTYPE.md\` — a turn that does is refused
1821
+ - Leave everything uncommitted and finish your turn — do not commit
1822
+ `;
1823
+ const prototypeFixingPrompt = (slice) => `${stateFileRules}
1824
+ - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1825
+ ${sliceLine(slice)}${criteriaBinding(true)}
1826
+ - Read \`.gtd/PROTOTYPE.md\`: a hands-on test of the change. Fix every
1827
+ \`fail\` chunk of this change's criteria${slice === void 0 ? "" : " (this slice's only)"} or a problem found
1828
+ beside them, at its cause, with a test that shows it; leave \`blocked\`
1829
+ ones to the person
1830
+ - A \`fail\` the code does not explain (the tester misread what it saw) has
1831
+ nothing to fix: leave it and change nothing for it — the next round re-tests
1832
+ - Leave \`.gtd/PROTOTYPE.md\` as it is: the next round re-tests from it
1833
+ - Run \`${env.testCommand}\` and finish only once it is green
1834
+ - Leave everything uncommitted and finish your turn — do not commit
1835
+ `;
1836
+ const openItems = (items) => items.map((item) => `- ${item.subject}: ${item.verdict}`).join("\n");
1837
+ const prototypeOpenMessage = (items, rounds) => `The hands-on test of this slice still has open items${rounds > 0 ? ` after ${rounds} fix round${rounds === 1 ? "" : "s"}` : ", and none a fixer can take"}:
1838
+ ${openItems(items)}
1839
+
1840
+ \`.gtd/PROTOTYPE.md\` holds the last report: the steps run and what was seen.
1841
+
1842
+ Two ways forward (then run \`gtd land\`):
1843
+ - **Fix** — change the code, or the testing instructions, yourself (the
1844
+ criteria are settled: an edit to them is refused); the suite runs and the
1845
+ slice is tested again, with a fresh round budget
1846
+ - **Overrule** — land a clean tree to go on to the quality lap with these
1847
+ items open
1848
+ `;
1849
+ const prototypeUnguidedMessage = () => `The hands-on test found no way to exercise this project for real — no
1850
+ project skill, no manual testing section in \`AGENTS.md\`, \`CLAUDE.md\` or
1851
+ \`README.md\`, and no project shape it recognised. \`.gtd/PROTOTYPE.md\` says
1852
+ what it looked for.
1853
+
1854
+ Two ways forward (then run \`gtd land\`):
1855
+ - **Instruct** — add testing instructions (a project skill, or a manual
1856
+ testing section); the slice is tested again
1857
+ - **Skip** — land a clean tree to go on to the quality lap untested
1858
+ `;
1776
1859
  //#endregion
1777
1860
  //#region src/workflows/shared/steps.ts
1778
1861
  const FEEDBACK = ".gtd/FEEDBACK.md";
@@ -1784,6 +1867,7 @@ const REFACTOR_NOTE = ".gtd/REFACTOR-NOTE.md";
1784
1867
  const QUALITY = ".gtd/QUALITY.md";
1785
1868
  const SLICES = ".gtd/SLICES.md";
1786
1869
  const HANDOFF = ".gtd/HANDOFF.md";
1870
+ const PROTOTYPE = ".gtd/PROTOTYPE.md";
1787
1871
  const planner = () => env.plannerModel ?? "";
1788
1872
  const coder = () => env.coderModel ?? "";
1789
1873
  const fixCheck = (rules) => agentWithSkills("fix", buildFixPrompt(rules), {
@@ -2028,6 +2112,7 @@ const ShippedOptions = Struct({
2028
2112
  qualityReviews: setting,
2029
2113
  refactorQualityReviews: setting,
2030
2114
  continuations: setting,
2115
+ prototypeRounds: setting,
2031
2116
  continuationStall: setting,
2032
2117
  testCommand: setting,
2033
2118
  plannerModel: setting,
@@ -2107,6 +2192,10 @@ const skills = (vars) => {
2107
2192
  seam: ["incremental-implementation"],
2108
2193
  implement: ["test-driven-development", "incremental-implementation"],
2109
2194
  "implement.fix.suite": ["debugging-and-error-recovery"],
2195
+ "implement.prototype": [],
2196
+ "implement.prototype.fix": ["test-driven-development", "incremental-implementation"],
2197
+ "implement.prototype.fix.suite": ["debugging-and-error-recovery"],
2198
+ "implement.prototype.health": ["debugging-and-error-recovery"],
2110
2199
  "implement.health": ["debugging-and-error-recovery"],
2111
2200
  ...lensScopes("build.quality", lensesOf(vars.qualityReviews ?? ""), (lens) => builtInLenses[lens]?.skills ?? [lens]),
2112
2201
  ...lensScopes("refactor.build.quality", lensesOf(vars.refactorQualityReviews ?? ""), (lens) => builtInLenses[lens]?.skills ?? [lens])
@@ -2131,4 +2220,4 @@ const steering = {
2131
2220
  [REVIEW]: "review"
2132
2221
  };
2133
2222
  //#endregion
2134
- export { naiveApplyFailure as $, reviewQuality as A, defaults as At, coverageMessage as B, renderStandards as Bt, collecting as C, uncoveredMessage as Ct, fixRisks as D, walkthroughBlock as Dt, fixQuality as E, unwindFailure as Et, builderSystem as F, conflictFor as Ft, designSystem as G, registryEntry as Gt, coveringPrompt as H, flagMentions as Ht, chunkTexts as I, conflictTitle as It, fixReviewNotesPrompt as J, designTriagePrompt as K, requireFlag as Kt, clearCriteriaMessage as L, isConflictTitle as Lt, agentWithSkills as M, testFileGlobs as Mt, buildReviewCollectingPrompt as N, STANDARDS as Nt, planner as O, withLapFindings as Ot, buildReviewTriageMessage as P, conflictBody as Pt, implementFixPrompt as Q, clearCriteriaQuestion as R, oneLine as Rt, coder as S, titledNotes as St, fixNits as T, unwindFailedMessage as Tt, criterionLine as U, flagReferences as Ut, coverageQuestion as V, standardKey as Vt, designGateAnswerMessage as W, flipProblems as Wt, idleMessage as X, handOffAwaitReviewMessage as Y, implementBuildingPrompt as Z, REQUIREMENTS as _, reviewerSystem as _t, flagModeOn as a, rankedReview as at, answerReviewQuestions as b, slicingPrompt as bt, baseline as c, refactorFixPrompt as ct, healthy as d, reviewEditNotesCapture as dt, naivePassPrompt as et, FEEDBACK as f, reviewEditsCapture as ft, REFACTOR_NOTE as g, reviewRankingMessage as gt, REFACTOR as h, reviewNotesCapture as ht, skills as i, qualityTriageMessage as it, reviewing as j, envDefaults as jt, reviewMissing as k, withWalkthrough as kt, gate as l, refactorNotePrompt as lt, QUALITY as m, reviewGateBlockedMessage as mt, summary as n, oversizedMessage as nt, flagShipped as o, refactorBuildingPrompt as ot, HANDOFF 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, guarded as u, refactorRules as ut, REVIEW as v, seamByHandMessage as vt, fixCheck as w, unranked as wt, awaitReview as x, startGateBlockedMessage as xt, SLICES as y, seamSteerMessage as yt, composedReview as z, parseStandards as zt };
2223
+ export { implementFixPrompt as $, reviewMissing as A, unranked as At, composedReview as B, conflictBody as Bt, coder as C, seamByHandMessage as Ct, fixQuality as D, testerSystem as Dt, fixNits as E, startGateBlockedMessage as Et, buildReviewTriageMessage as F, withWalkthrough as Ft, designGateAnswerMessage as G, parseStandards as Gt, coverageQuestion as H, conflictTitle as Ht, builderSystem as I, defaults as It, finisherSystem as J, flagMentions as Jt, designSystem as K, renderStandards as Kt, chunkTexts as L, envDefaults as Lt, reviewing as M, unwindFailure as Mt, agentWithSkills as N, walkthroughBlock as Nt, fixRisks as O, titledNotes as Ot, buildReviewCollectingPrompt as P, withLapFindings as Pt, implementBuildingPrompt as Q, requireFlag as Qt, clearCriteriaMessage as R, testFileGlobs as Rt, awaitReview as S, reviewerSystem as St, fixCheck as T, slicingPrompt as Tt, coveringPrompt as U, isConflictTitle as Ut, coverageMessage as V, conflictFor as Vt, criterionLine as W, oneLine as Wt, handOffAwaitReviewMessage as X, flipProblems as Xt, fixReviewNotesPrompt as Y, flagReferences as Yt, idleMessage as Z, registryEntry as Zt, REFACTOR_NOTE as _, reviewEditsCapture as _t, flagModeOn as a, prototypeOpenMessage as at, SLICES as b, reviewNotesCapture as bt, baseline as c, qualityFindingsFile as ct, healthy as d, refactorBuildingPrompt as dt, naiveApplyFailure as et, FEEDBACK as f, refactorDroppedRefusal as ft, REFACTOR as g, reviewEditNotesCapture as gt, QUALITY as h, refactorRules as ht, skills as i, prototypeFixingPrompt as it, reviewQuality as j, unwindFailedMessage as jt, planner as k, uncoveredMessage as kt, gate as l, qualityTriageMessage as lt, PROTOTYPE as m, refactorNotePrompt as mt, summary as n, normaliseCriteriaScript as nt, flagShipped as o, prototypeTestingPrompt as ot, HANDOFF as p, refactorFixPrompt as pt, designTriagePrompt as q, standardKey as qt, access as r, oversizedMessage as rt, shipped as s, prototypeUnguidedMessage as st, steering as t, naivePassPrompt as tt, guarded as u, rankedReview as ut, REQUIREMENTS as v, reviewFeedbackFixPrompt as vt, collecting as w, seamSteerMessage as wt, answerReviewQuestions as x, reviewRankingMessage as xt, REVIEW as y, reviewGateBlockedMessage as yt, clearCriteriaQuestion as z, STANDARDS as zt };
@@ -1,8 +1,8 @@
1
1
  import { answered, changesSince, head, judge, numeric, read, refuse, requireRevert, restoreScript, run, showScript, vars, writeScript } from "../flows.mjs";
2
2
  import { _ as reviewRisks, m as reviewChunks } from "./steering-DtUVUz9l.mjs";
3
3
  import { removedAssertions, review } from "../workflow-kit.mjs";
4
- import { Bt as renderStandards, C as collecting, D as fixRisks, Dt as walkthroughBlock, Ft as conflictFor, I as chunkTexts, It as conflictTitle, Lt as isConflictTitle, Nt as STANDARDS, P as buildReviewTriageMessage, Pt as conflictBody, Rt as oneLine, St as titledNotes, T as fixNits, Vt as standardKey, at as rankedReview, b as answerReviewQuestions, d as healthy, dt as reviewEditNotesCapture, ft as reviewEditsCapture, gt as reviewRankingMessage, ht as reviewNotesCapture, j as reviewing, k as reviewMissing, kt as withWalkthrough, u as guarded, v as REVIEW, w as fixCheck, wt as unranked, x as awaitReview, z as composedReview, zt as parseStandards } from "./reserved-CdOzKFVX.mjs";
5
- import { i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "./tail-DRM2eC1z.mjs";
4
+ import { A as reviewMissing, At as unranked, B as composedReview, Bt as conflictBody, E as fixNits, F as buildReviewTriageMessage, Ft as withWalkthrough, Gt as parseStandards, Ht as conflictTitle, Kt as renderStandards, L as chunkTexts, M as reviewing, Nt as walkthroughBlock, O as fixRisks, Ot as titledNotes, S as awaitReview, T as fixCheck, Ut as isConflictTitle, Vt as conflictFor, Wt as oneLine, _t as reviewEditsCapture, bt as reviewNotesCapture, d as healthy, gt as reviewEditNotesCapture, qt as standardKey, u as guarded, ut as rankedReview, w as collecting, x as answerReviewQuestions, xt as reviewRankingMessage, y as REVIEW, zt as STANDARDS } from "./reserved-CUkk9XgL.mjs";
5
+ import { i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "./tail-CX6Pdsql.mjs";
6
6
  //#region src/workflows/shared/learn.ts
7
7
  const SUMMARY_MAX = 200;
8
8
  const BANNER = /^_(?:The fixer left this unresolved\.|A fix turn removed this while renaming another finding — check whether it was fixed\.|No detail given\.)_\s*/;
@@ -1,6 +1,6 @@
1
1
  import { answered, head, judge, mark, numeric, read, refuse, removeScript, run, scope, sectionBodies, showScript, vars, writeScript } from "../flows.mjs";
2
2
  import { lensEntriesOf, lensOf, qualityLap, reviewToHandOff } from "../workflow-kit.mjs";
3
- import { A as reviewQuality, D as fixRisks, E as fixQuality, Ot as withLapFindings, Y as handOffAwaitReviewMessage, d as healthy, it as qualityTriageMessage, j as reviewing, k as reviewMissing, m as QUALITY, rt as qualityFindingsFile, u as guarded, v as REVIEW, w as fixCheck, x as awaitReview } from "./reserved-CdOzKFVX.mjs";
3
+ import { A as reviewMissing, D as fixQuality, M as reviewing, O as fixRisks, Pt as withLapFindings, S as awaitReview, T as fixCheck, X as handOffAwaitReviewMessage, ct as qualityFindingsFile, d as healthy, h as QUALITY, j as reviewQuality, lt as qualityTriageMessage, u as guarded, y as REVIEW } from "./reserved-CUkk9XgL.mjs";
4
4
  //#region src/workflows/shared/lenses.ts
5
5
  const NO_FINDINGS = {
6
6
  notes: [],
@@ -1,6 +1,6 @@
1
1
  import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
2
- import { At as defaults, Gt as registryEntry, Kt as requireFlag, S as coder, Ut as flagReferences, i as skills, jt as envDefaults, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-CdOzKFVX.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-DRM2eC1z.mjs";
2
+ import { C as coder, It as defaults, Lt as envDefaults, Qt as requireFlag, Yt as flagReferences, Zt as registryEntry, i as skills, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-CUkk9XgL.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-CX6Pdsql.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,8 +1,8 @@
1
1
  import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireThreadsClosed, revertScript, run, scope, slice, start, vars, wrote } from "../flows.mjs";
2
2
  import { lensEntriesOf } from "../workflow-kit.mjs";
3
- import { $ as naiveApplyFailure, At as defaults, B as coverageMessage, C as collecting, Ct as uncoveredMessage, Et as unwindFailure, F as builderSystem, G as designSystem, H as coveringPrompt, K as designTriagePrompt, L as clearCriteriaMessage, M as agentWithSkills, Mt as testFileGlobs, N as buildReviewCollectingPrompt, O as planner, Q as implementFixPrompt, R as clearCriteriaQuestion, S as coder, T as fixNits, Tt as unwindFailedMessage, U as criterionLine, V as coverageQuestion, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REQUIREMENTS, _t as reviewerSystem, a as flagModeOn, b as answerReviewQuestions, bt as slicingPrompt, ct as refactorFixPrompt, d as healthy, et as naivePassPrompt, f as FEEDBACK, g as REFACTOR_NOTE, h as REFACTOR, i as skills, jt as envDefaults, l as gate, lt as refactorNotePrompt, n as summary, nt as oversizedMessage, ot as refactorBuildingPrompt, p as HANDOFF, r as access, s as shipped, st as refactorDroppedRefusal, t as steering, tt as normaliseCriteriaScript, ut as refactorRules, vt as seamByHandMessage, xt as startGateBlockedMessage, y as SLICES, yt as seamSteerMessage } from "../chunks/reserved-CdOzKFVX.mjs";
4
- import { a as refusePendingHandOff, c as joinReports, o as tail } from "../chunks/tail-DRM2eC1z.mjs";
5
- import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-BChXEl2Y.mjs";
3
+ import { $ as implementFixPrompt, C as coder, Ct as seamByHandMessage, Dt as testerSystem, E as fixNits, Et as startGateBlockedMessage, G as designGateAnswerMessage, H as coverageQuestion, I as builderSystem, It as defaults, K as designSystem, Lt as envDefaults, Mt as unwindFailure, N as agentWithSkills, P as buildReviewCollectingPrompt, Q as implementBuildingPrompt, R as clearCriteriaMessage, Rt as testFileGlobs, St as reviewerSystem, Tt as slicingPrompt, U as coveringPrompt, V as coverageMessage, W as criterionLine, Z as idleMessage, _ as REFACTOR_NOTE, a as flagModeOn, at as prototypeOpenMessage, b as SLICES, d as healthy, dt as refactorBuildingPrompt, et as naiveApplyFailure, f as FEEDBACK, ft as refactorDroppedRefusal, g as REFACTOR, ht as refactorRules, i as skills, it as prototypeFixingPrompt, jt as unwindFailedMessage, k as planner, kt as uncoveredMessage, l as gate, m as PROTOTYPE, mt as refactorNotePrompt, n as summary, nt as normaliseCriteriaScript, ot as prototypeTestingPrompt, p as HANDOFF, pt as refactorFixPrompt, q as designTriagePrompt, r as access, rt as oversizedMessage, s as shipped, st as prototypeUnguidedMessage, t as steering, tt as naivePassPrompt, v as REQUIREMENTS, w as collecting, wt as seamSteerMessage, x as answerReviewQuestions, z as clearCriteriaQuestion } from "../chunks/reserved-CUkk9XgL.mjs";
4
+ import { a as refusePendingHandOff, c as joinReports, o as tail } from "../chunks/tail-CX6Pdsql.mjs";
5
+ import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-BaCXlIT2.mjs";
6
6
  import { posix } from "node:path";
7
7
  //#region src/workflows/shared/fences.ts
8
8
  const markFenced = (text) => {
@@ -561,7 +561,9 @@ const reviewedRefactor = async (shape, options = {}) => {
561
561
  feedback,
562
562
  fixAccess: strayAccess(p.strays),
563
563
  lensBase: entry,
564
- rules: refactorRules(p.strays, p.pattern)
564
+ rules: refactorRules(p.strays, p.pattern),
565
+ walkthrough: true,
566
+ lap: "refactor"
565
567
  });
566
568
  if (outcome.verdict === "signoff") return true;
567
569
  await reUnwind(outcome);
@@ -576,7 +578,7 @@ const reviewedRefactor = async (shape, options = {}) => {
576
578
  const COVERED_MIN_P = .7;
577
579
  /** Covering turns before the process rests at `uncovered`; a human edit there refills them. */
578
580
  const COVERING_RETRIES = 2;
579
- const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
581
+ const frozen$1 = () => requireCriteriaFrozen(REQUIREMENTS);
580
582
  const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
581
583
  label: "Fixing the check",
582
584
  file: FEEDBACK,
@@ -584,7 +586,7 @@ const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", imple
584
586
  system: builderSystem()
585
587
  }), {
586
588
  guard: () => {
587
- frozen();
589
+ frozen$1();
588
590
  guard?.();
589
591
  },
590
592
  guardRests: false
@@ -637,7 +639,7 @@ const requireCoverage = async (base = start(), slice) => {
637
639
  system: builderSystem(),
638
640
  allowEmpty: true
639
641
  });
640
- frozen();
642
+ frozen$1();
641
643
  } else {
642
644
  await human("uncovered", {
643
645
  message: uncoveredMessage(uncovered),
@@ -646,13 +648,136 @@ const requireCoverage = async (base = start(), slice) => {
646
648
  acceptClean: true
647
649
  });
648
650
  if (!changes().some((c) => isCode$1(c.path))) return record(covered, uncovered);
649
- frozen();
651
+ frozen$1();
650
652
  retries = 0;
651
653
  }
652
654
  await green();
653
655
  }
654
656
  };
655
657
  //#endregion
658
+ //#region src/workflows/feature/prototype.ts
659
+ /** The `prototypeRounds` setting. Anything but a non-negative integer fails rather than guess a cap. */
660
+ const prototypeRounds = () => {
661
+ const value = vars.prototypeRounds ?? defaults.prototypeRounds;
662
+ if (!/^(0|[1-9]\d*)$/.test(value)) refuse(`gtd: prototypeRounds must be a non-negative integer, got ${JSON.stringify(value)}`);
663
+ return Number(value);
664
+ };
665
+ const CHUNK = /^(AC-\d+|Problem\b.*?)\s*:\s*(pass|fail|blocked)$/i;
666
+ const UNGUIDED = /^no testing instructions$/i;
667
+ /** The `## ` chunks of a prototype report; a heading in neither shape is prose, not an item. */
668
+ const reportOf = (text) => {
669
+ let unguided = false;
670
+ const items = [];
671
+ const malformed = [];
672
+ for (const { line, fenced } of markFenced(text)) {
673
+ const heading = fenced ? null : HEADING.exec(line);
674
+ if (heading?.[1] !== "##") continue;
675
+ if (UNGUIDED.test(heading[2])) unguided = true;
676
+ const chunk = CHUNK.exec(heading[2]);
677
+ if (chunk === null && /^(ac-\d|problem)/i.test(heading[2])) malformed.push(heading[2]);
678
+ if (chunk !== null) items.push({
679
+ subject: /^ac-/i.test(chunk[1]) ? chunk[1].toUpperCase() : chunk[1],
680
+ verdict: chunk[2].toLowerCase()
681
+ });
682
+ }
683
+ return {
684
+ unguided,
685
+ items,
686
+ malformed
687
+ };
688
+ };
689
+ /**
690
+ * The report the testing turn left, refused unless it is the only change and
691
+ * names every criterion once, with its open items: another slice's criteria
692
+ * are not this slice's to fix.
693
+ */
694
+ const requireReport = (slice, retest) => {
695
+ const others = changes().map((c) => c.path).filter((path) => path !== PROTOTYPE);
696
+ if (others.length > 0) refuse(`gtd land: prototype-report: test in a disposable place and change nothing but ${PROTOTYPE} — revert these:\n${others.map((path) => ` - ${path}`).join("\n")}`);
697
+ const text = read(PROTOTYPE);
698
+ if (text === void 0) return refuse(`gtd land: prototype-report: write ${PROTOTYPE}`);
699
+ const report = reportOf(text);
700
+ if (report.malformed.length > 0) refuse(`gtd land: prototype-report: head each chunk \`## <ID>: pass|fail|blocked\` or \`## Problem <title>: pass|fail|blocked\` — these miss it:\n${report.malformed.map((h) => ` - ${h}`).join("\n")}`);
701
+ if (report.unguided && report.items.length === 0 && !retest) return {
702
+ unguided: true,
703
+ open: []
704
+ };
705
+ const requirements = read(".gtd/REQUIREMENTS.md") ?? "";
706
+ const ids = criterionIds(requirements, slice);
707
+ const elsewhere = criterionIds(requirements).filter((id) => !ids.includes(id));
708
+ const count = (id) => report.items.filter((i) => i.subject === id).length;
709
+ const wrong = ids.filter((id) => count(id) !== 1);
710
+ if (wrong.length > 0) refuse(`gtd land: prototype-report: ${PROTOTYPE} needs exactly one chunk headed \`## <ID>: pass|fail|blocked\` for ${wrong.join(", ")}`);
711
+ return {
712
+ unguided: false,
713
+ open: report.items.filter((i) => i.verdict !== "pass" && !elsewhere.includes(i.subject))
714
+ };
715
+ };
716
+ const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
717
+ const testing = async (slice, retest) => {
718
+ await agentWithSkills("testing", prototypeTestingPrompt(sliceIds(slice), retest), {
719
+ label: retest ? "Re-testing the slice by hand" : "Testing the slice by hand",
720
+ file: PROTOTYPE,
721
+ model: coder(),
722
+ system: testerSystem(),
723
+ allowEmpty: true
724
+ });
725
+ return requireReport(slice, retest);
726
+ };
727
+ const fixing = async (slice, guard) => {
728
+ await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(sliceIds(slice)), {
729
+ label: "Fixing what the hands-on test found",
730
+ file: PROTOTYPE,
731
+ model: coder(),
732
+ system: builderSystem(),
733
+ allowEmpty: true
734
+ }));
735
+ if (changes().some((c) => c.path === ".gtd/PROTOTYPE.md")) refuse(`gtd land: prototype-fix: leave ${PROTOTYPE} as it is — the next round re-tests from it`);
736
+ frozen();
737
+ guard?.();
738
+ };
739
+ /** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
740
+ const askPerson = async (name, message, label) => {
741
+ await human(name, {
742
+ message,
743
+ label,
744
+ file: PROTOTYPE,
745
+ acceptClean: true
746
+ });
747
+ if (!changes().some((c) => c.path !== ".gtd/PROTOTYPE.md")) return false;
748
+ frozen();
749
+ return true;
750
+ };
751
+ /**
752
+ * Use the slice by hand: a testing turn reports on every criterion, a fix turn
753
+ * takes each failure, and the slice is tested again — until the report is
754
+ * clean, or `prototypeRounds` fixes are spent and a person decides. `guard`
755
+ * holds every fix turn, as it holds the implementation's.
756
+ */
757
+ const prototypeTest = async (slice, guard) => {
758
+ const cap = prototypeRounds();
759
+ if (cap === 0) return;
760
+ await scope("prototype", async () => {
761
+ let fixes = 0;
762
+ let retest = false;
763
+ for (;;) {
764
+ const { unguided, open } = await testing(slice, retest);
765
+ retest = !unguided;
766
+ if (unguided) {
767
+ if (!await askPerson("unguided", prototypeUnguidedMessage(), "No way to test by hand")) return;
768
+ } else if (open.length === 0) return;
769
+ else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
770
+ fixes++;
771
+ await fixing(slice, guard);
772
+ } else {
773
+ if (!await askPerson("open", prototypeOpenMessage(open, fixes), "Hands-on test still failing")) return;
774
+ fixes = 0;
775
+ }
776
+ await green(guard);
777
+ }
778
+ });
779
+ };
780
+ //#endregion
656
781
  //#region src/workflows/feature/seam.ts
657
782
  /** Naive-pass retries after a `reject` or a red suite; the next one rests at `seam.steer`. */
658
783
  const NAIVE_RETRIES = 2;
@@ -859,8 +984,10 @@ const implementing = (options) => scope("implement", async () => {
859
984
  ...acceptance,
860
985
  ids: settled(acceptance.ids, options.slice)
861
986
  }, since);
862
- await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
987
+ const keepsTests = built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0;
988
+ await green(keepsTests);
863
989
  await requireCoverage(options.base, options.slice);
990
+ await prototypeTest(options.slice, keepsTests);
864
991
  const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
865
992
  await closing(moreToBuild(options.more));
866
993
  return ids;
@@ -869,11 +996,16 @@ const moreToBuild = (more) => typeof more === "function" ? more() : more === tru
869
996
  /** The last slice's closing, for a last slice a steer left with no criterion to build. */
870
997
  const closeOut = () => scope("implement", () => closing(false));
871
998
  /** A slice with `more` after it keeps the criteria and the slicing for the next one. */
872
- const closing = (more) => run("closing", removeScript(more ? [REFACTOR_NOTE, HANDOFF] : [
999
+ const closing = (more) => run("closing", removeScript(more ? [
1000
+ REFACTOR_NOTE,
1001
+ HANDOFF,
1002
+ PROTOTYPE
1003
+ ] : [
873
1004
  REQUIREMENTS,
874
1005
  REFACTOR_NOTE,
875
1006
  SLICES,
876
- HANDOFF
1007
+ HANDOFF,
1008
+ PROTOTYPE
877
1009
  ]), { label: "Closing out the implementation" });
878
1010
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
879
1011
  const steer = async (retry) => {
@@ -1238,6 +1370,7 @@ const ordinaryStart = async () => {
1238
1370
  refusePendingHandOff();
1239
1371
  flagModeOn();
1240
1372
  sliceCap();
1373
+ prototypeRounds();
1241
1374
  const stated = statedCriteria(changes(), head());
1242
1375
  await unwind();
1243
1376
  if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
@@ -1,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
2
2
  import { _ as reviewRisks, g as reviewNotes } from "../chunks/steering-DtUVUz9l.mjs";
3
- import { At as defaults, J as fixReviewNotesPrompt, M as agentWithSkills, S as coder, c as baseline, i as skills, jt as envDefaults, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, v as REVIEW } from "../chunks/reserved-CdOzKFVX.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-DRM2eC1z.mjs";
3
+ import { C as coder, It as defaults, J as finisherSystem, Lt as envDefaults, N as agentWithSkills, Y as fixReviewNotesPrompt, c as baseline, i as skills, n as summary, r as access, s as shipped, t as steering, y as REVIEW } from "../chunks/reserved-CUkk9XgL.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-CX6Pdsql.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,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, start } from "../flows.mjs";
2
- import { At as defaults, M as agentWithSkills, S as coder, _ as REQUIREMENTS, i as skills, jt as envDefaults, l as gate, mt as reviewGateBlockedMessage, n as summary, pt as reviewFeedbackFixPrompt, q as finisherSystem, r as access, s as shipped, t as steering } from "../chunks/reserved-CdOzKFVX.mjs";
3
- import { a as refusePendingHandOff } from "../chunks/tail-DRM2eC1z.mjs";
4
- import { i as reUnwind, t as buildTail } from "../chunks/review-BChXEl2Y.mjs";
2
+ import { C as coder, It as defaults, J as finisherSystem, Lt as envDefaults, N as agentWithSkills, i as skills, l as gate, n as summary, r as access, s as shipped, t as steering, v as REQUIREMENTS, vt as reviewFeedbackFixPrompt, yt as reviewGateBlockedMessage } from "../chunks/reserved-CUkk9XgL.mjs";
3
+ import { a as refusePendingHandOff } from "../chunks/tail-CX6Pdsql.mjs";
4
+ import { i as reUnwind, t as buildTail } from "../chunks/review-BaCXlIT2.mjs";
5
5
  //#region src/workflows/review/review.ts
6
6
  const fixFeedback = async () => {
7
7
  await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
@@ -1,5 +1,5 @@
1
1
  import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
2
- import { At as defaults, Ht as flagMentions, Kt as requireFlag, S as coder, Wt as flipProblems, f as FEEDBACK, i as skills, jt as envDefaults, l as gate, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-CdOzKFVX.mjs";
2
+ import { C as coder, It as defaults, Jt as flagMentions, Lt as envDefaults, Qt as requireFlag, Xt as flipProblems, f as FEEDBACK, i as skills, l as gate, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-CUkk9XgL.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.";