@pmelab/gtd 52.0.0 → 52.1.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 (51) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +5 -2
  3. package/dist/chunks/{reserved-D0MKVFFa.mjs → reserved-DxB5CROj.mjs} +46 -5
  4. package/dist/chunks/{review-ZzUQH1KZ.mjs → review-CKMSdvgW.mjs} +2 -2
  5. package/dist/chunks/{tail-I1-vVq0P.mjs → tail-QZc7Uz2U.mjs} +1 -1
  6. package/dist/workflows/cleanup.mjs +2 -2
  7. package/dist/workflows/feature.mjs +51 -4
  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 +93 -5
  12. package/package.json +5 -4
  13. package/src/testkit/steps.ts +13 -0
  14. package/src/workflows/feature/acceptance-criteria.feature +159 -3
  15. package/src/workflows/feature/continuation-cap.feature +4 -0
  16. package/src/workflows/feature/continuation-rebuild.feature +4 -0
  17. package/src/workflows/feature/continuation-slices.feature +4 -0
  18. package/src/workflows/feature/continuation.feature +4 -0
  19. package/src/workflows/feature/criteria.test.ts +22 -1
  20. package/src/workflows/feature/criteria.ts +66 -1
  21. package/src/workflows/feature/fast-path.feature +4 -0
  22. package/src/workflows/feature/feature.test.ts +6 -5
  23. package/src/workflows/feature/flag-mode.feature +4 -0
  24. package/src/workflows/feature/hands-on-skip.feature +40 -0
  25. package/src/workflows/feature/lens-models.feature +4 -0
  26. package/src/workflows/feature/lens-review.feature +4 -0
  27. package/src/workflows/feature/naive-acceptance.feature +4 -0
  28. package/src/workflows/feature/one-step-land.feature +4 -0
  29. package/src/workflows/feature/planning.ts +2 -1
  30. package/src/workflows/feature/prototype-rounds.feature +4 -0
  31. package/src/workflows/feature/prototype.feature +8 -0
  32. package/src/workflows/feature/refactor-absorbs.feature +4 -0
  33. package/src/workflows/feature/refactor-first.feature +4 -0
  34. package/src/workflows/feature/refactor-lens-models.feature +4 -0
  35. package/src/workflows/feature/refactor-rebuild.feature +4 -0
  36. package/src/workflows/feature/refactor-review.feature +20 -0
  37. package/src/workflows/feature/review-answers.feature +4 -0
  38. package/src/workflows/feature/review-gate.feature +12 -0
  39. package/src/workflows/feature/reviewer-walkthrough.feature +4 -0
  40. package/src/workflows/feature/risk-tier.feature +32 -0
  41. package/src/workflows/feature/seam-rejection.feature +8 -0
  42. package/src/workflows/feature/seam.feature +8 -0
  43. package/src/workflows/feature/seam.test.ts +4 -2
  44. package/src/workflows/feature/severity-budget.feature +4 -0
  45. package/src/workflows/feature/severity-sorted-review.feature +4 -0
  46. package/src/workflows/feature/slice-findings.feature +4 -0
  47. package/src/workflows/feature/slicing.feature +32 -0
  48. package/src/workflows/feature/standards.feature +4 -0
  49. package/src/workflows/feature/tier-pin.feature +4 -0
  50. package/src/workflows/shared/text.test.ts +40 -0
  51. package/src/workflows/shared/text.ts +50 -4
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "52.0.0",
3
+ "version": "52.1.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
@@ -441,9 +441,12 @@ noted in steps 3 and 4 below.
441
441
  entry is the agent's, moving on is refused. Leave the file untouched and
442
442
  start the loop to accept the plan as-is, unanswered questions and all. The
443
443
  file lists numbered acceptance criteria (`AC-1`, `AC-2`, …) that bind the
444
- build. Sketch your own under an `Acceptance criteria` heading and, when a
444
+ build, and an `## Impact` section mapping the units and public surfaces the
445
+ change touches — correct it here and the next lap re-explores from your edit.
446
+ Sketch your own criteria under an `Acceptance criteria` heading and, when a
445
447
  judge finds every one clear and checkable, they are recorded as they are and
446
- this moment is skipped; one vague criterion brings it back. See
448
+ this moment is skipped: a `design.impact` agent turn writes Impact instead,
449
+ unreviewed. One vague criterion brings the moment back. See
447
450
  [Acceptance criteria](docs/configuration.md#acceptance-criteria).
448
451
 
449
452
  3. **You wait.** The agent first implements the requirements the quickest way
@@ -774,6 +774,19 @@ writing \`(no hands-on)\` between its ID and the colon:
774
774
  Leave the tag off whatever a person can see by running the change. A
775
775
  tag the human added or removed at the gate stays as they left it.
776
776
  `;
777
+ const impactBlock = `
778
+ ## Impact
779
+
780
+ \`.gtd/REQUIREMENTS.md\` carries a \`## Impact\` section: explore the
781
+ codebase (not just the start diff) and name the units and boundaries
782
+ this change touches, the public surfaces it affects (CLI commands and
783
+ flags, config keys, driver protocol, exported API), and anything
784
+ surprising. Name the project's own declared units first —
785
+ dependency-cruiser rules, workspace packages, module boundaries named
786
+ in its docs or agent instructions (\`AGENTS.md\`, \`CLAUDE.md\`). When
787
+ none is declared, fall back to directories and packages. Landing is
788
+ refused while the section is missing.
789
+ `;
777
790
  const riskTierBlock = `
778
791
  ## Risk tier and production signals
779
792
 
@@ -813,7 +826,14 @@ ${footnoteFoldIn}${codeThreadReplies()}
813
826
  or both
814
827
  - Whichever lap this is, leave \`.gtd/REQUIREMENTS.md\`
815
828
  uncommitted and finish once it reflects this lap's own work
816
- ${acceptanceCriteriaBlock}
829
+ ${acceptanceCriteriaBlock}${impactBlock}
830
+ Every lap re-explores and leaves \`## Impact\` present. On a return
831
+ lap or review loop-back the human may have edited \`## Impact\` (or
832
+ footnoted it): read that edit from history (\`git log -1 -p --
833
+ .gtd/REQUIREMENTS.md\`), keep the human's correction, and
834
+ re-explore from it. A surprise the human must settle becomes an
835
+ entry under \`## Open Questions\`.
836
+
817
837
  ## First lap
818
838
 
819
839
  \`.gtd/REQUIREMENTS.md\` does not exist yet (or holds whatever the
@@ -868,6 +888,21 @@ ${questionBarReturn}
868
888
  made). If red, make that breakage the first concern, ahead of
869
889
  everything else — the build assumes the suite started green
870
890
  ${riskTier ? riskTierBlock : ""}`;
891
+ const designImpactPrompt = () => `${styleBlock}
892
+
893
+ ${styleFormatContract}
894
+
895
+ ${stateFileRules}
896
+ - The only state file this turn touches is \`.gtd/REQUIREMENTS.md\`
897
+ — no other files for notes or output
898
+ - \`.gtd/REQUIREMENTS.md\` already holds the settled acceptance
899
+ criteria. Add a \`## Impact\` section and change nothing else — a
900
+ turn that touches anything outside it is refused
901
+ - No human reviews this before the build: put any surprise in the
902
+ Impact prose, never as a question
903
+ ${impactBlock}
904
+ - Leave \`.gtd/REQUIREMENTS.md\` uncommitted and finish once it holds \`## Impact\`
905
+ `;
871
906
  const clearCriteriaQuestion = {
872
907
  id: "clear",
873
908
  primitive: "noul",
@@ -900,9 +935,9 @@ const slicingPrompt = (cap) => `${stateFileRules}
900
935
  - Read \`.gtd/REQUIREMENTS.md\`: its \`## Acceptance Criteria\` are settled
901
936
  and more than ${cap}, too many to build in one slice
902
937
  - Group them into slices in \`.gtd/SLICES.md\`, one \`## <title>\` heading per
903
- slice over a \`- AC-<n>\` list item per criterion it builds. Put criteria of
904
- the same flow or subsystem together; every criterion in exactly one slice,
905
- at most ${cap} per slice
938
+ slice over a \`- AC-<n>\` list item per criterion it builds. Read the
939
+ \`## Impact\` section of \`.gtd/REQUIREMENTS.md\` and slice along the units it
940
+ names; every criterion in exactly one slice, at most ${cap} per slice
906
941
  - Keep it coarse: as few slices as the cap allows, never one per criterion
907
942
  - Order the slices so each builds on the ones before: they run in that order,
908
943
  each from where the previous one landed
@@ -1568,6 +1603,9 @@ const listed = (paths, unreadable = []) => paths.map((path) => ` - \`${displayP
1568
1603
  const isRed = (retry) => "red" in retry;
1569
1604
  const isFault = (retry) => "fault" in retry;
1570
1605
  const pickUp = (hash, exclude) => `Pick it up — \`git diff --binary --no-color --no-ext-diff --no-textconv ${hash}^ ${hash}${exclude === void 0 ? "" : ` -- ${quote(":(top)")} ${quote(`:(top,exclude)${exclude}`)}`} | git apply\``;
1606
+ const impactHints = `- Its \`## Impact\` section maps where the change lands — reviewed at the design
1607
+ gate when design ran. A starting point and hints, not a list of allowed files
1608
+ — change whatever the work needs`;
1571
1609
  const criteriaBinding = (refused) => `- Its \`## Acceptance Criteria\` (\`AC-<n>\`) bind this turn: re-read them from
1572
1610
  the file, never from memory, and stop only when each one holds. They are
1573
1611
  settled — ${refused ? "a turn that changes them is refused" : "leave them as they are"}`;
@@ -1614,6 +1652,7 @@ the context limit first if the refactor is too big`}. \`gtd abandon\` gives the
1614
1652
  /** `from`: a continuation of a pass that stopped at its context limit, in place of `retry`'s pick-up. */
1615
1653
  const naivePassPrompt = (retry, slice, from) => `${stateFileRules}
1616
1654
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1655
+ ${impactHints}
1617
1656
  ${sliceLine(slice)}${criteriaBinding(false)}
1618
1657
  - Implement them the quickest way that works — the naive version. Do not
1619
1658
  restructure, abstract or refactor around the change; it may land exactly as
@@ -1681,6 +1720,7 @@ const flagLine = (flagMode, tier, amend, sliced) => !flagMode ? "- Add no releas
1681
1720
  repository's flag conventions`;
1682
1721
  const implementBuildingPrompt = (reference, tier, amend, afterRefactor, flagMode = true, rebuild = {}, slice, from) => `${stateFileRules}
1683
1722
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1723
+ ${impactHints}
1684
1724
  ${sliceLine(slice)}${criteriaBinding(true)}
1685
1725
  ${afterRefactor === true ? afterRefactorLine(rebuild) : ""}${amend === true ? `- The change already exists in the tree from an earlier lap; amend it to meet
1686
1726
  the re-derived requirements instead of rewriting it
@@ -1816,6 +1856,7 @@ ${retest ? `- This is a re-test: \`.gtd/PROTOTYPE.md\` holds the last round's re
1816
1856
  `;
1817
1857
  const prototypeFixingPrompt = (slice) => `${stateFileRules}
1818
1858
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1859
+ ${impactHints}
1819
1860
  ${sliceLine(slice)}${criteriaBinding(true)}
1820
1861
  - Read \`.gtd/PROTOTYPE.md\`: a hands-on test of the change. Fix every
1821
1862
  \`fail\` chunk of this change's criteria${slice === void 0 ? "" : " (this slice's only)"} or a problem found
@@ -2214,4 +2255,4 @@ const steering = {
2214
2255
  [REVIEW]: "review"
2215
2256
  };
2216
2257
  //#endregion
2217
- export { normaliseCriteriaScript as $, reviewMissing as A, unwindFailure as At, composedReview as B, conflictTitle as Bt, coder as C, seamSteerMessage as Ct, fixQuality as D, titledNotes as Dt, fixNits as E, testerSystem as Et, buildReviewTriageMessage as F, envDefaults as Ft, finisherSystem as G, standardKey as Gt, designGateAnswerMessage as H, oneLine as Ht, builderSystem as I, testFileGlobs as It, idleMessage as J, flipProblems as Jt, fixReviewNotesPrompt as K, flagMentions as Kt, chunkTexts as L, STANDARDS as Lt, reviewing as M, withLapFindings as Mt, agentWithSkills as N, withWalkthrough as Nt, fixRisks as O, unranked as Ot, buildReviewCollectingPrompt as P, defaults as Pt, naivePassPrompt as Q, clearCriteriaMessage as R, conflictBody as Rt, awaitReview as S, seamByHandMessage as St, fixCheck as T, startGateBlockedMessage as Tt, designSystem as U, parseStandards as Ut, criterionLine as V, isConflictTitle as Vt, designTriagePrompt as W, renderStandards as Wt, implementFixPrompt as X, requireFlag as Xt, implementBuildingPrompt as Y, registryEntry as Yt, naiveApplyFailure as Z, REFACTOR_NOTE as _, reviewFeedbackFixPrompt as _t, flagModeOn as a, prototypeTestingPrompt as at, SLICES as b, reviewRankingMessage as bt, baseline as c, qualityTriageMessage as ct, healthy as d, refactorDroppedRefusal as dt, observableMessage as et, FEEDBACK as f, refactorFixPrompt as ft, REFACTOR as g, reviewEditsCapture as gt, QUALITY as h, reviewEditNotesCapture as ht, skills as i, prototypeOpenMessage as it, reviewQuality as j, walkthroughBlock as jt, planner as k, unwindFailedMessage as kt, gate as l, rankedReview as lt, PROTOTYPE as m, refactorRules as mt, summary as n, oversizedMessage as nt, flagShipped as o, prototypeUnguidedMessage as ot, HANDOFF as p, refactorNotePrompt as pt, handOffAwaitReviewMessage as q, flagReferences as qt, access as r, prototypeFixingPrompt as rt, shipped as s, qualityFindingsFile as st, steering as t, observableQuestion as tt, guarded as u, refactorBuildingPrompt as ut, REQUIREMENTS as v, reviewGateBlockedMessage as vt, collecting as w, slicingPrompt as wt, answerReviewQuestions as x, reviewerSystem as xt, REVIEW as y, reviewNotesCapture as yt, clearCriteriaQuestion as z, conflictFor as zt };
2258
+ export { naivePassPrompt as $, reviewMissing as A, unwindFailedMessage as At, composedReview as B, conflictFor as Bt, coder as C, seamByHandMessage as Ct, fixQuality as D, testerSystem as Dt, fixNits as E, startGateBlockedMessage as Et, buildReviewTriageMessage as F, defaults as Ft, designTriagePrompt as G, renderStandards as Gt, designGateAnswerMessage as H, isConflictTitle as Ht, builderSystem as I, envDefaults as It, handOffAwaitReviewMessage as J, flagReferences as Jt, finisherSystem as K, standardKey as Kt, chunkTexts as L, testFileGlobs as Lt, reviewing as M, walkthroughBlock as Mt, agentWithSkills as N, withLapFindings as Nt, fixRisks as O, titledNotes as Ot, buildReviewCollectingPrompt as P, withWalkthrough as Pt, naiveApplyFailure as Q, clearCriteriaMessage as R, STANDARDS as Rt, awaitReview as S, reviewerSystem as St, fixCheck as T, slicingPrompt as Tt, designImpactPrompt as U, oneLine as Ut, criterionLine as V, conflictTitle as Vt, designSystem as W, parseStandards as Wt, implementBuildingPrompt as X, registryEntry as Xt, idleMessage as Y, flipProblems as Yt, implementFixPrompt as Z, requireFlag 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, normaliseCriteriaScript 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, unwindFailure as jt, planner as k, unranked as kt, gate as l, qualityTriageMessage as lt, PROTOTYPE as m, refactorNotePrompt as mt, summary as n, observableQuestion as nt, flagShipped as o, prototypeTestingPrompt as ot, HANDOFF as p, refactorFixPrompt as pt, fixReviewNotesPrompt as q, flagMentions as qt, access as r, oversizedMessage as rt, shipped as s, prototypeUnguidedMessage as st, steering as t, observableMessage 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, conflictBody 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 { b as sectionedReview, h as reviewChunks, m as REVIEW_SECTIONS, v as reviewRisks, y as reviewSections } from "./steering-CLhu8C7q.mjs";
3
3
  import { removedAssertions, review } from "../workflow-kit.mjs";
4
- import { A as reviewMissing, B as composedReview, Bt as conflictTitle, Dt as titledNotes, E as fixNits, F as buildReviewTriageMessage, Gt as standardKey, Ht as oneLine, L as chunkTexts, Lt as STANDARDS, M as reviewing, Nt as withWalkthrough, O as fixRisks, Ot as unranked, Rt as conflictBody, S as awaitReview, T as fixCheck, Ut as parseStandards, Vt as isConflictTitle, Wt as renderStandards, bt as reviewRankingMessage, d as healthy, gt as reviewEditsCapture, ht as reviewEditNotesCapture, jt as walkthroughBlock, lt as rankedReview, u as guarded, w as collecting, x as answerReviewQuestions, y as REVIEW, yt as reviewNotesCapture, zt as conflictFor } from "./reserved-D0MKVFFa.mjs";
5
- import { i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "./tail-I1-vVq0P.mjs";
4
+ import { A as reviewMissing, B as composedReview, Bt as conflictFor, E as fixNits, F as buildReviewTriageMessage, Gt as renderStandards, Ht as isConflictTitle, Kt as standardKey, L as chunkTexts, M as reviewing, Mt as walkthroughBlock, O as fixRisks, Ot as titledNotes, Pt as withWalkthrough, Rt as STANDARDS, S as awaitReview, T as fixCheck, Ut as oneLine, Vt as conflictTitle, Wt as parseStandards, _t as reviewEditsCapture, bt as reviewNotesCapture, d as healthy, gt as reviewEditNotesCapture, kt as unranked, u as guarded, ut as rankedReview, w as collecting, x as answerReviewQuestions, xt as reviewRankingMessage, y as REVIEW, zt as conflictBody } from "./reserved-DxB5CROj.mjs";
5
+ import { i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "./tail-QZc7Uz2U.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 reviewMissing, D as fixQuality, M as reviewing, Mt as withLapFindings, O as fixRisks, S as awaitReview, T as fixCheck, ct as qualityTriageMessage, d as healthy, h as QUALITY, j as reviewQuality, q as handOffAwaitReviewMessage, st as qualityFindingsFile, u as guarded, y as REVIEW } from "./reserved-D0MKVFFa.mjs";
3
+ import { A as reviewMissing, D as fixQuality, J as handOffAwaitReviewMessage, M as reviewing, Nt as withLapFindings, O as fixRisks, S as awaitReview, T as fixCheck, ct as qualityFindingsFile, d as healthy, h as QUALITY, j as reviewQuality, lt as qualityTriageMessage, u as guarded, y as REVIEW } from "./reserved-DxB5CROj.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 { C as coder, Ft as envDefaults, Pt as defaults, Xt as requireFlag, Yt as registryEntry, i as skills, n as summary, o as flagShipped, qt as flagReferences, r as access, t as steering } from "../chunks/reserved-D0MKVFFa.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-I1-vVq0P.mjs";
2
+ import { C as coder, Ft as defaults, It as envDefaults, Jt as flagReferences, Xt as registryEntry, Zt as requireFlag, i as skills, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-DxB5CROj.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-QZc7Uz2U.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 normaliseCriteriaScript, At as unwindFailure, C as coder, Ct as seamSteerMessage, E as fixNits, Et as testerSystem, Ft as envDefaults, H as designGateAnswerMessage, I as builderSystem, It as testFileGlobs, J as idleMessage, N as agentWithSkills, P as buildReviewCollectingPrompt, Pt as defaults, Q as naivePassPrompt, R as clearCriteriaMessage, St as seamByHandMessage, Tt as startGateBlockedMessage, U as designSystem, V as criterionLine, W as designTriagePrompt, X as implementFixPrompt, Y as implementBuildingPrompt, Z as naiveApplyFailure, _ as REFACTOR_NOTE, a as flagModeOn, at as prototypeTestingPrompt, b as SLICES, d as healthy, dt as refactorDroppedRefusal, et as observableMessage, f as FEEDBACK, ft as refactorFixPrompt, g as REFACTOR, i as skills, it as prototypeOpenMessage, k as planner, kt as unwindFailedMessage, l as gate, m as PROTOTYPE, mt as refactorRules, n as summary, nt as oversizedMessage, ot as prototypeUnguidedMessage, p as HANDOFF, pt as refactorNotePrompt, r as access, rt as prototypeFixingPrompt, s as shipped, t as steering, tt as observableQuestion, ut as refactorBuildingPrompt, v as REQUIREMENTS, w as collecting, wt as slicingPrompt, x as answerReviewQuestions, xt as reviewerSystem, z as clearCriteriaQuestion } from "../chunks/reserved-D0MKVFFa.mjs";
4
- import { a as refusePendingHandOff, c as joinReports, o as tail } from "../chunks/tail-I1-vVq0P.mjs";
5
- import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-ZzUQH1KZ.mjs";
3
+ import { $ as naivePassPrompt, At as unwindFailedMessage, C as coder, Ct as seamByHandMessage, Dt as testerSystem, E as fixNits, Et as startGateBlockedMessage, Ft as defaults, G as designTriagePrompt, H as designGateAnswerMessage, I as builderSystem, It as envDefaults, Lt as testFileGlobs, N as agentWithSkills, P as buildReviewCollectingPrompt, Q as naiveApplyFailure, R as clearCriteriaMessage, St as reviewerSystem, Tt as slicingPrompt, U as designImpactPrompt, V as criterionLine, W as designSystem, X as implementBuildingPrompt, Y as idleMessage, Z as implementFixPrompt, _ as REFACTOR_NOTE, a as flagModeOn, at as prototypeOpenMessage, b as SLICES, d as healthy, dt as refactorBuildingPrompt, et as normaliseCriteriaScript, f as FEEDBACK, ft as refactorDroppedRefusal, g as REFACTOR, ht as refactorRules, i as skills, it as prototypeFixingPrompt, jt as unwindFailure, k as planner, l as gate, m as PROTOTYPE, mt as refactorNotePrompt, n as summary, nt as observableQuestion, ot as prototypeTestingPrompt, p as HANDOFF, pt as refactorFixPrompt, r as access, rt as oversizedMessage, s as shipped, st as prototypeUnguidedMessage, t as steering, tt as observableMessage, v as REQUIREMENTS, w as collecting, wt as seamSteerMessage, x as answerReviewQuestions, z as clearCriteriaQuestion } from "../chunks/reserved-DxB5CROj.mjs";
4
+ import { a as refusePendingHandOff, c as joinReports, o as tail } from "../chunks/tail-QZc7Uz2U.mjs";
5
+ import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-CKMSdvgW.mjs";
6
6
  import { posix } from "node:path";
7
7
  //#region src/workflows/shared/fences.ts
8
8
  const markFenced = (text) => {
@@ -121,6 +121,38 @@ const requireCriteria = (file) => {
121
121
  if (criteria.length > 0 && unnamed.length === 0 && twice.length === 0) return;
122
122
  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`);
123
123
  };
124
+ const isImpactHeading = (h) => h !== null && h[1].length === 2 && /^impact$/i.test(h[2].trim());
125
+ /** Whether `text` has an unfenced, level-2 `## Impact` heading. */
126
+ const hasImpact = (text) => markFenced(text).some((l) => !l.fenced && isImpactHeading(HEADING.exec(l.line)));
127
+ /** `text` without its `## Impact` section, whitespace collapsed. */
128
+ const withoutImpact = (text) => {
129
+ let inImpact = false;
130
+ return markFenced(text).filter((l) => {
131
+ const h = l.fenced ? null : HEADING.exec(l.line);
132
+ if (h !== null && h[1].length <= 2) inImpact = isImpactHeading(h);
133
+ return !inImpact;
134
+ }).map((l) => l.line).join(" ").replace(/\s+/g, " ").trim();
135
+ };
136
+ /**
137
+ * Refuse a turn that leaves `file` without a `## Impact` section. Only the
138
+ * pending turn (`head()` is `""`): a triage lap recorded before Impact existed
139
+ * replays without one, and refusing there would fail every in-flight process.
140
+ * A human's edit at a later gate is not checked — the human owns that file then.
141
+ */
142
+ const requireImpact = (file) => {
143
+ if (head() !== "") return;
144
+ const text = read(file);
145
+ if (text === void 0) return;
146
+ if (hasImpact(text)) return;
147
+ refuse(`gtd land: impact: ${file} has no "## Impact" section — explore the codebase and add "## Impact" naming the units, public surfaces and surprises this change touches`);
148
+ };
149
+ /** Refuse a turn that changed `file` beyond its `## Impact` section. */
150
+ const requireImpactOnly = (file) => {
151
+ const change = changes().get(file);
152
+ if (change === void 0) return;
153
+ if (withoutImpact(change.before ?? "") === withoutImpact(change.after ?? "")) return;
154
+ refuse(`gtd land: impact-only: this turn may only add "## Impact" to ${file} — restore everything else`);
155
+ };
124
156
  const idNumber = (id) => Number(id.slice(3));
125
157
  /** Why `listed` renumbers or reuses an earlier ID; `undefined` when it does neither. */
126
158
  const unstableIds = (listed, ids) => {
@@ -154,6 +186,19 @@ const clear = async (stated) => {
154
186
  });
155
187
  return truncated.length === 0 && answered(answers.clear, "yes", CLEAR_MIN_P);
156
188
  };
189
+ /** The fast path's impact-only turn: add `## Impact`, leave the criteria alone. */
190
+ const impact = async () => {
191
+ await agentWithSkills("impact", designImpactPrompt(), {
192
+ label: "Mapping the change's impact",
193
+ file: REQUIREMENTS,
194
+ model: planner(),
195
+ system: designSystem()
196
+ });
197
+ requireProgress(REQUIREMENTS);
198
+ requireCriteriaFrozen(REQUIREMENTS);
199
+ requireImpact(REQUIREMENTS);
200
+ requireImpactOnly(REQUIREMENTS);
201
+ };
157
202
  /**
158
203
  * The skip path: when the sketch stated clear, checkable criteria, write them
159
204
  * to `.gtd/REQUIREMENTS.md` as they are and resolve `true` — no triage, no
@@ -165,6 +210,7 @@ const skipDesign = (stated) => stated === void 0 ? Promise.resolve(false) : scop
165
210
  label: "Recording the stated acceptance criteria",
166
211
  file: REQUIREMENTS
167
212
  });
213
+ await impact();
168
214
  return true;
169
215
  });
170
216
  /** After a gate that invites narrowing the requirements: an edit must leave valid criteria. */
@@ -261,6 +307,7 @@ const design = (base, guard) => scope("design", async () => {
261
307
  requireProgress(REQUIREMENTS);
262
308
  requireReplies(REQUIREMENTS);
263
309
  requireCriteria(REQUIREMENTS);
310
+ requireImpact(REQUIREMENTS);
264
311
  guard?.();
265
312
  } while (await noteGate(REQUIREMENTS, answerProductQuestions));
266
313
  });
@@ -1345,4 +1392,4 @@ const feature = shipped(ordinaryStart);
1345
1392
  //#region src/workflows/feature/index.ts
1346
1393
  var feature_default = feature;
1347
1394
  //#endregion
1348
- export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults, design, envDefaults, feature, fixNits, handsOnIds, 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 };
1395
+ export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults, design, envDefaults, feature, fixNits, handsOnIds, hasImpact, holdsCallSites, implement, implementing, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireImpact, requireImpactOnly, 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 { _ as reviewNotes, v as reviewRisks } from "../chunks/steering-CLhu8C7q.mjs";
3
- import { C as coder, Ft as envDefaults, G as finisherSystem, K as fixReviewNotesPrompt, N as agentWithSkills, Pt as defaults, c as baseline, i as skills, n as summary, r as access, s as shipped, t as steering, y as REVIEW } from "../chunks/reserved-D0MKVFFa.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-I1-vVq0P.mjs";
3
+ import { C as coder, Ft as defaults, It as envDefaults, K as finisherSystem, N as agentWithSkills, c as baseline, i as skills, n as summary, q as fixReviewNotesPrompt, r as access, s as shipped, t as steering, y as REVIEW } from "../chunks/reserved-DxB5CROj.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-QZc7Uz2U.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 { C as coder, Ft as envDefaults, G as finisherSystem, N as agentWithSkills, Pt as defaults, _t as reviewFeedbackFixPrompt, i as skills, l as gate, n as summary, r as access, s as shipped, t as steering, v as REQUIREMENTS, vt as reviewGateBlockedMessage } from "../chunks/reserved-D0MKVFFa.mjs";
3
- import { a as refusePendingHandOff } from "../chunks/tail-I1-vVq0P.mjs";
4
- import { i as reUnwind, t as buildTail } from "../chunks/review-ZzUQH1KZ.mjs";
2
+ import { C as coder, Ft as defaults, It as envDefaults, K as finisherSystem, 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-DxB5CROj.mjs";
3
+ import { a as refusePendingHandOff } from "../chunks/tail-QZc7Uz2U.mjs";
4
+ import { i as reUnwind, t as buildTail } from "../chunks/review-CKMSdvgW.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 { C as coder, Ft as envDefaults, Jt as flipProblems, Kt as flagMentions, Pt as defaults, Xt as requireFlag, f as FEEDBACK, i as skills, l as gate, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-D0MKVFFa.mjs";
2
+ import { C as coder, Ft as defaults, It as envDefaults, Yt as flipProblems, Zt as requireFlag, f as FEEDBACK, i as skills, l as gate, n as summary, o as flagShipped, qt as flagMentions, r as access, t as steering } from "../chunks/reserved-DxB5CROj.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.";
@@ -17982,6 +17982,19 @@ writing \`(no hands-on)\` between its ID and the colon:
17982
17982
  Leave the tag off whatever a person can see by running the change. A
17983
17983
  tag the human added or removed at the gate stays as they left it.
17984
17984
  `;
17985
+ const impactBlock = `
17986
+ ## Impact
17987
+
17988
+ \`.gtd/REQUIREMENTS.md\` carries a \`## Impact\` section: explore the
17989
+ codebase (not just the start diff) and name the units and boundaries
17990
+ this change touches, the public surfaces it affects (CLI commands and
17991
+ flags, config keys, driver protocol, exported API), and anything
17992
+ surprising. Name the project's own declared units first —
17993
+ dependency-cruiser rules, workspace packages, module boundaries named
17994
+ in its docs or agent instructions (\`AGENTS.md\`, \`CLAUDE.md\`). When
17995
+ none is declared, fall back to directories and packages. Landing is
17996
+ refused while the section is missing.
17997
+ `;
17985
17998
  const riskTierBlock = `
17986
17999
  ## Risk tier and production signals
17987
18000
 
@@ -18021,7 +18034,14 @@ ${footnoteFoldIn}${codeThreadReplies()}
18021
18034
  or both
18022
18035
  - Whichever lap this is, leave \`.gtd/REQUIREMENTS.md\`
18023
18036
  uncommitted and finish once it reflects this lap's own work
18024
- ${acceptanceCriteriaBlock}
18037
+ ${acceptanceCriteriaBlock}${impactBlock}
18038
+ Every lap re-explores and leaves \`## Impact\` present. On a return
18039
+ lap or review loop-back the human may have edited \`## Impact\` (or
18040
+ footnoted it): read that edit from history (\`git log -1 -p --
18041
+ .gtd/REQUIREMENTS.md\`), keep the human's correction, and
18042
+ re-explore from it. A surprise the human must settle becomes an
18043
+ entry under \`## Open Questions\`.
18044
+
18025
18045
  ## First lap
18026
18046
 
18027
18047
  \`.gtd/REQUIREMENTS.md\` does not exist yet (or holds whatever the
@@ -18076,6 +18096,21 @@ ${questionBarReturn}
18076
18096
  made). If red, make that breakage the first concern, ahead of
18077
18097
  everything else — the build assumes the suite started green
18078
18098
  ${riskTier ? riskTierBlock : ""}`;
18099
+ const designImpactPrompt = () => `${styleBlock}
18100
+
18101
+ ${styleFormatContract}
18102
+
18103
+ ${stateFileRules}
18104
+ - The only state file this turn touches is \`.gtd/REQUIREMENTS.md\`
18105
+ — no other files for notes or output
18106
+ - \`.gtd/REQUIREMENTS.md\` already holds the settled acceptance
18107
+ criteria. Add a \`## Impact\` section and change nothing else — a
18108
+ turn that touches anything outside it is refused
18109
+ - No human reviews this before the build: put any surprise in the
18110
+ Impact prose, never as a question
18111
+ ${impactBlock}
18112
+ - Leave \`.gtd/REQUIREMENTS.md\` uncommitted and finish once it holds \`## Impact\`
18113
+ `;
18079
18114
  const clearCriteriaQuestion = {
18080
18115
  id: "clear",
18081
18116
  primitive: "noul",
@@ -18108,9 +18143,9 @@ const slicingPrompt = (cap) => `${stateFileRules}
18108
18143
  - Read \`.gtd/REQUIREMENTS.md\`: its \`## Acceptance Criteria\` are settled
18109
18144
  and more than ${cap}, too many to build in one slice
18110
18145
  - Group them into slices in \`.gtd/SLICES.md\`, one \`## <title>\` heading per
18111
- slice over a \`- AC-<n>\` list item per criterion it builds. Put criteria of
18112
- the same flow or subsystem together; every criterion in exactly one slice,
18113
- at most ${cap} per slice
18146
+ slice over a \`- AC-<n>\` list item per criterion it builds. Read the
18147
+ \`## Impact\` section of \`.gtd/REQUIREMENTS.md\` and slice along the units it
18148
+ names; every criterion in exactly one slice, at most ${cap} per slice
18114
18149
  - Keep it coarse: as few slices as the cap allows, never one per criterion
18115
18150
  - Order the slices so each builds on the ones before: they run in that order,
18116
18151
  each from where the previous one landed
@@ -18776,6 +18811,9 @@ const listed = (paths, unreadable = []) => paths.map((path) => ` - \`${displayP
18776
18811
  const isRed = (retry) => "red" in retry;
18777
18812
  const isFault = (retry) => "fault" in retry;
18778
18813
  const pickUp = (hash, exclude) => `Pick it up — \`git diff --binary --no-color --no-ext-diff --no-textconv ${hash}^ ${hash}${exclude === void 0 ? "" : ` -- ${quote(":(top)")} ${quote(`:(top,exclude)${exclude}`)}`} | git apply\``;
18814
+ const impactHints = `- Its \`## Impact\` section maps where the change lands — reviewed at the design
18815
+ gate when design ran. A starting point and hints, not a list of allowed files
18816
+ — change whatever the work needs`;
18779
18817
  const criteriaBinding = (refused) => `- Its \`## Acceptance Criteria\` (\`AC-<n>\`) bind this turn: re-read them from
18780
18818
  the file, never from memory, and stop only when each one holds. They are
18781
18819
  settled — ${refused ? "a turn that changes them is refused" : "leave them as they are"}`;
@@ -18822,6 +18860,7 @@ the context limit first if the refactor is too big`}. \`gtd abandon\` gives the
18822
18860
  /** `from`: a continuation of a pass that stopped at its context limit, in place of `retry`'s pick-up. */
18823
18861
  const naivePassPrompt = (retry, slice, from) => `${stateFileRules}
18824
18862
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
18863
+ ${impactHints}
18825
18864
  ${sliceLine(slice)}${criteriaBinding(false)}
18826
18865
  - Implement them the quickest way that works — the naive version. Do not
18827
18866
  restructure, abstract or refactor around the change; it may land exactly as
@@ -18889,6 +18928,7 @@ const flagLine = (flagMode, tier, amend, sliced) => !flagMode ? "- Add no releas
18889
18928
  repository's flag conventions`;
18890
18929
  const implementBuildingPrompt = (reference, tier, amend, afterRefactor, flagMode = true, rebuild = {}, slice, from) => `${stateFileRules}
18891
18930
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
18931
+ ${impactHints}
18892
18932
  ${sliceLine(slice)}${criteriaBinding(true)}
18893
18933
  ${afterRefactor === true ? afterRefactorLine(rebuild) : ""}${amend === true ? `- The change already exists in the tree from an earlier lap; amend it to meet
18894
18934
  the re-derived requirements instead of rewriting it
@@ -19024,6 +19064,7 @@ ${retest ? `- This is a re-test: \`.gtd/PROTOTYPE.md\` holds the last round's re
19024
19064
  `;
19025
19065
  const prototypeFixingPrompt = (slice) => `${stateFileRules}
19026
19066
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
19067
+ ${impactHints}
19027
19068
  ${sliceLine(slice)}${criteriaBinding(true)}
19028
19069
  - Read \`.gtd/PROTOTYPE.md\`: a hands-on test of the change. Fix every
19029
19070
  \`fail\` chunk of this change's criteria${slice === void 0 ? "" : " (this slice's only)"} or a problem found
@@ -39090,6 +39131,38 @@ const requireCriteria = (file) => {
39090
39131
  if (criteria.length > 0 && unnamed.length === 0 && twice.length === 0) return;
39091
39132
  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`);
39092
39133
  };
39134
+ const isImpactHeading = (h) => h !== null && h[1].length === 2 && /^impact$/i.test(h[2].trim());
39135
+ /** Whether `text` has an unfenced, level-2 `## Impact` heading. */
39136
+ const hasImpact = (text) => markFenced(text).some((l) => !l.fenced && isImpactHeading(HEADING.exec(l.line)));
39137
+ /** `text` without its `## Impact` section, whitespace collapsed. */
39138
+ const withoutImpact = (text) => {
39139
+ let inImpact = false;
39140
+ return markFenced(text).filter((l) => {
39141
+ const h = l.fenced ? null : HEADING.exec(l.line);
39142
+ if (h !== null && h[1].length <= 2) inImpact = isImpactHeading(h);
39143
+ return !inImpact;
39144
+ }).map((l) => l.line).join(" ").replace(/\s+/g, " ").trim();
39145
+ };
39146
+ /**
39147
+ * Refuse a turn that leaves `file` without a `## Impact` section. Only the
39148
+ * pending turn (`head()` is `""`): a triage lap recorded before Impact existed
39149
+ * replays without one, and refusing there would fail every in-flight process.
39150
+ * A human's edit at a later gate is not checked — the human owns that file then.
39151
+ */
39152
+ const requireImpact = (file) => {
39153
+ if (head$1() !== "") return;
39154
+ const text = read(file);
39155
+ if (text === void 0) return;
39156
+ if (hasImpact(text)) return;
39157
+ refuse(`gtd land: impact: ${file} has no "## Impact" section — explore the codebase and add "## Impact" naming the units, public surfaces and surprises this change touches`);
39158
+ };
39159
+ /** Refuse a turn that changed `file` beyond its `## Impact` section. */
39160
+ const requireImpactOnly = (file) => {
39161
+ const change = changes().get(file);
39162
+ if (change === void 0) return;
39163
+ if (withoutImpact(change.before ?? "") === withoutImpact(change.after ?? "")) return;
39164
+ refuse(`gtd land: impact-only: this turn may only add "## Impact" to ${file} — restore everything else`);
39165
+ };
39093
39166
  const idNumber = (id) => Number(id.slice(3));
39094
39167
  /** Why `listed` renumbers or reuses an earlier ID; `undefined` when it does neither. */
39095
39168
  const unstableIds = (listed, ids) => {
@@ -39123,6 +39196,19 @@ const clear = async (stated) => {
39123
39196
  });
39124
39197
  return truncated.length === 0 && answered(answers.clear, "yes", CLEAR_MIN_P);
39125
39198
  };
39199
+ /** The fast path's impact-only turn: add `## Impact`, leave the criteria alone. */
39200
+ const impact = async () => {
39201
+ await agentWithSkills("impact", designImpactPrompt(), {
39202
+ label: "Mapping the change's impact",
39203
+ file: REQUIREMENTS,
39204
+ model: planner(),
39205
+ system: designSystem()
39206
+ });
39207
+ requireProgress(REQUIREMENTS);
39208
+ requireCriteriaFrozen(REQUIREMENTS);
39209
+ requireImpact(REQUIREMENTS);
39210
+ requireImpactOnly(REQUIREMENTS);
39211
+ };
39126
39212
  /**
39127
39213
  * The skip path: when the sketch stated clear, checkable criteria, write them
39128
39214
  * to `.gtd/REQUIREMENTS.md` as they are and resolve `true` — no triage, no
@@ -39134,6 +39220,7 @@ const skipDesign = (stated) => stated === void 0 ? Promise.resolve(false) : scop
39134
39220
  label: "Recording the stated acceptance criteria",
39135
39221
  file: REQUIREMENTS
39136
39222
  });
39223
+ await impact();
39137
39224
  return true;
39138
39225
  });
39139
39226
  /** After a gate that invites narrowing the requirements: an edit must leave valid criteria. */
@@ -39230,6 +39317,7 @@ const design = (base, guard) => scope("design", async () => {
39230
39317
  requireProgress(REQUIREMENTS);
39231
39318
  requireReplies(REQUIREMENTS);
39232
39319
  requireCriteria(REQUIREMENTS);
39320
+ requireImpact(REQUIREMENTS);
39233
39321
  guard?.();
39234
39322
  } while (await noteGate(REQUIREMENTS, answerProductQuestions));
39235
39323
  });
@@ -41023,4 +41111,4 @@ var workflows_default = feature;
41023
41111
  */
41024
41112
  const doors = {};
41025
41113
  //#endregion
41026
- export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, PROTOTYPE, QUALITY, REFACTOR, REFACTOR_NOTE, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, SLICES, ShippedOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeOut, closeReview, coder, collecting, criteriaOf, criterionIds, workflows_default as default, defaults, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, handsOnIds, healthy, holdsCallSites, implement, implementing, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, recheck, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, tail, threads, triage, unstableIds, unwind };
41114
+ export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, PROTOTYPE, QUALITY, REFACTOR, REFACTOR_NOTE, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, SLICES, ShippedOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeOut, closeReview, coder, collecting, criteriaOf, criterionIds, workflows_default as default, defaults, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, handsOnIds, hasImpact, healthy, holdsCallSites, implement, implementing, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, recheck, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireImpact, requireImpactOnly, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, tail, threads, triage, unstableIds, unwind };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "52.0.0",
3
+ "version": "52.1.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {
@@ -165,8 +165,9 @@
165
165
  "@types/react": "^19.2.7",
166
166
  "@types/react-dom": "^19.2.3",
167
167
  "@vitejs/plugin-react": "^5.2.0",
168
- "@vitest/browser": "^3.2.7",
169
- "@vitest/coverage-v8": "^3.2.7",
168
+ "@vitest/browser": "^4.1.12",
169
+ "@vitest/browser-playwright": "^4.1.12",
170
+ "@vitest/coverage-v8": "^4.1.12",
170
171
  "dependency-cruiser": "^18.3.1",
171
172
  "fallow": "3.25.0",
172
173
  "fast-check": "^4.8.0",
@@ -186,7 +187,7 @@
186
187
  "tailwindcss": "^4.3.3",
187
188
  "tsdown": "^0.22.8",
188
189
  "turbo": "^2.10.9",
189
- "vitest": "^3.2.0",
190
+ "vitest": "^4.1.12",
190
191
  "yaml": "^2.8.2",
191
192
  "zustand": "^5.0.15"
192
193
  },
@@ -149,6 +149,10 @@ Given("the working tree is committed as {string}", (world: TestkitWorld, message
149
149
  world.repo().commit(message)
150
150
  })
151
151
 
152
+ Given("the working tree is committed with the message:", (world: TestkitWorld, message: string) => {
153
+ world.repo().commit(message)
154
+ })
155
+
152
156
  Given("step {string} changes nothing", (world: TestkitWorld, step: string) => {
153
157
  world.act(step, () => undefined)
154
158
  })
@@ -180,6 +184,15 @@ Given(
180
184
  },
181
185
  )
182
186
 
187
+ Given(
188
+ "step {string} appends to {string}:",
189
+ (world: TestkitWorld, step: string, path: string, text: string) => {
190
+ world.act(step, (_, repo) => {
191
+ repo.write(path, `${(repo.read(path) ?? "").trimEnd()}\n\n${withNewline(text)}`)
192
+ })
193
+ },
194
+ )
195
+
183
196
  Given("step {string} deletes {string}", (world: TestkitWorld, step: string, path: string) => {
184
197
  world.act(step, (_, repo) => repo.remove(path))
185
198
  })