@pmelab/gtd 52.0.0 → 53.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 (108) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +96 -131
  3. package/claude/hooks/drive.ts +0 -3
  4. package/claude/hooks/register.tsx +3 -68
  5. package/claude/hooks/ship.ts +85 -1595
  6. package/dist/chunks/{reserved-D0MKVFFa.mjs → reserved-CQ7amcKG.mjs} +87 -133
  7. package/dist/chunks/{review-ZzUQH1KZ.mjs → review-CNgN_V8T.mjs} +2 -2
  8. package/dist/chunks/{tail-I1-vVq0P.mjs → tail-JZ9PAOwp.mjs} +2 -14
  9. package/dist/chunks/{wire-BJuZWR3C.mjs → wire-C12dahrH.mjs} +2 -130
  10. package/dist/flows.mjs +1 -16
  11. package/dist/gtd.bundle.mjs +476 -982
  12. package/dist/testkit.mjs +58 -417
  13. package/dist/wire.mjs +2 -2
  14. package/dist/workflows/cleanup.mjs +2 -2
  15. package/dist/workflows/feature.mjs +159 -363
  16. package/dist/workflows/fix.mjs +2 -2
  17. package/dist/workflows/review.mjs +3 -3
  18. package/dist/workflows/rollout.mjs +1 -1
  19. package/dist/workflows.mjs +400 -675
  20. package/package.json +5 -4
  21. package/src/cli/Cli.test.ts +1 -86
  22. package/src/cli/Cli.ts +13 -133
  23. package/src/cli/Metrics.test.ts +21 -31
  24. package/src/cli/Metrics.ts +25 -36
  25. package/src/cli/Program.test.ts +1 -35
  26. package/src/cli/Program.ts +5 -64
  27. package/src/cli/ToolCommands.ts +0 -3
  28. package/src/edge/Edge.test.ts +20 -322
  29. package/src/edge/Edge.ts +10 -181
  30. package/src/edge/index.ts +0 -1
  31. package/src/flows/runtime.test.ts +0 -27
  32. package/src/flows/runtime.ts +1 -49
  33. package/src/install/Install.ts +15 -24
  34. package/src/replay/Replay.test.ts +0 -169
  35. package/src/replay/Replay.ts +4 -93
  36. package/src/replay/index.ts +0 -1
  37. package/src/testkit/Layers.ts +0 -1
  38. package/src/testkit/steps.ts +13 -0
  39. package/src/wire/BeatDocument.ts +2 -12
  40. package/src/wire/BeatStatus.ts +0 -3
  41. package/src/wire/WireSchema.test.ts +0 -18
  42. package/src/wire/index.ts +0 -2
  43. package/src/wire/types.ts +0 -7
  44. package/src/workflow/Workflow.ts +1 -9
  45. package/src/workflows/feature/acceptance-criteria.feature +159 -3
  46. package/src/workflows/feature/criteria.test.ts +23 -15
  47. package/src/workflows/feature/criteria.ts +72 -39
  48. package/src/workflows/feature/fast-path.feature +4 -0
  49. package/src/workflows/feature/feature.test.ts +6 -5
  50. package/src/workflows/feature/feature.ts +34 -118
  51. package/src/workflows/feature/flag-mode.feature +4 -0
  52. package/src/workflows/feature/hands-on-skip.feature +37 -65
  53. package/src/workflows/feature/index.ts +0 -1
  54. package/src/workflows/feature/lens-models.feature +4 -0
  55. package/src/workflows/feature/lens-review.feature +4 -0
  56. package/src/workflows/feature/naive-acceptance.feature +4 -0
  57. package/src/workflows/feature/one-step-land.feature +4 -4
  58. package/src/workflows/feature/planning.ts +2 -1
  59. package/src/workflows/feature/prototype-rounds.feature +5 -1
  60. package/src/workflows/feature/prototype.feature +11 -3
  61. package/src/workflows/feature/prototype.ts +18 -29
  62. package/src/workflows/feature/refactor-absorbs.feature +7 -8
  63. package/src/workflows/feature/refactor-first.feature +4 -5
  64. package/src/workflows/feature/refactor-lens-models.feature +4 -0
  65. package/src/workflows/feature/refactor-rebuild.feature +4 -6
  66. package/src/workflows/feature/refactor-review.feature +31 -11
  67. package/src/workflows/feature/refactor.ts +30 -64
  68. package/src/workflows/feature/review-answers.feature +4 -0
  69. package/src/workflows/feature/review-gate.feature +13 -1
  70. package/src/workflows/feature/reviewer-walkthrough.feature +4 -0
  71. package/src/workflows/feature/risk-tier.feature +32 -0
  72. package/src/workflows/feature/seam-rejection.feature +8 -4
  73. package/src/workflows/feature/seam.feature +8 -0
  74. package/src/workflows/feature/seam.test.ts +11 -28
  75. package/src/workflows/feature/seam.ts +52 -165
  76. package/src/workflows/feature/severity-budget.feature +4 -0
  77. package/src/workflows/feature/severity-sorted-review.feature +4 -0
  78. package/src/workflows/feature/standards.feature +4 -0
  79. package/src/workflows/feature/tier-pin.feature +4 -0
  80. package/src/workflows/index.ts +0 -1
  81. package/src/workflows/shared/lenses.ts +0 -13
  82. package/src/workflows/shared/options.test.ts +2 -11
  83. package/src/workflows/shared/options.ts +4 -21
  84. package/src/workflows/shared/review.test.ts +3 -3
  85. package/src/workflows/shared/steps.ts +0 -2
  86. package/src/workflows/shared/text.test.ts +41 -71
  87. package/src/workflows/shared/text.ts +82 -212
  88. package/src/workflows/shared/vars.ts +2 -8
  89. package/src/workflows/shared/workflows.steps.ts +0 -13
  90. package/claude/hooks/context.ts +0 -66
  91. package/src/replay/Slices.test.ts +0 -73
  92. package/src/replay/Slices.ts +0 -45
  93. package/src/wire/ContextLimit.fixture.ts +0 -30
  94. package/src/wire/ContextLimit.test.ts +0 -96
  95. package/src/wire/ContextLimit.ts +0 -169
  96. package/src/wire/Slices.ts +0 -25
  97. package/src/wire/golden/1/slices.json +0 -11
  98. package/src/workflows/feature/context-limit.feature +0 -37
  99. package/src/workflows/feature/continuation-cap.feature +0 -65
  100. package/src/workflows/feature/continuation-rebuild.feature +0 -88
  101. package/src/workflows/feature/continuation-slices.feature +0 -64
  102. package/src/workflows/feature/continuation.feature +0 -205
  103. package/src/workflows/feature/slice-findings.feature +0 -252
  104. package/src/workflows/feature/slicing-cap.feature +0 -23
  105. package/src/workflows/feature/slicing.feature +0 -265
  106. package/src/workflows/feature/slicing.test.ts +0 -49
  107. package/src/workflows/feature/slicing.ts +0 -105
  108. package/src/workflows/shared/continuation.ts +0 -92
@@ -6,44 +6,36 @@ import {
6
6
  read,
7
7
  revertScript,
8
8
  run,
9
- slice,
10
9
  start,
11
10
  } from "../../flows/index.js"
12
11
  import { gate } from "../shared/health.js"
13
- import { joinReports, type LensReport } from "../shared/lenses.js"
14
12
  import { flagModeOn, shipped } from "../shared/options.js"
15
13
  import { FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
16
14
  import { refusePendingHandOff, tail } from "../shared/tail.js"
17
15
  import * as t from "../shared/text.js"
18
16
  import {
19
17
  criterionIds,
20
- requireCriteriaFrozen,
21
18
  requireStableIds,
22
19
  type StableIds,
23
20
  skipDesign,
24
- sliceIds,
25
21
  statedCriteria,
26
- type CriteriaSlice,
27
22
  type StatedCriteria,
28
23
  } from "./criteria.js"
29
24
  import { design } from "./planning.js"
30
25
  import { buildTail, humanReview, isCode, reUnwind, type ReviewOutcome } from "../shared/review.js"
31
26
  import { prototypeRounds } from "./prototype.js"
32
27
  import { implement, implementing, seamBuild } from "./seam.js"
33
- import { sliceCap, sliceCriteria, slicing } from "./slicing.js"
34
- import { recordRisk, riskTierOn, type RiskTier } from "./tier.js"
28
+ import { recordRisk, riskTierOn } from "./tier.js"
35
29
 
36
30
  // The ordinary start. Any change to the tree starts a process: `idle` →
37
31
  // `unwind` reverts the sketch (its intent survives in history) → a
38
- // green-baseline gate → design → per slice of the criteria, a naive pass,
39
- // judged with `flagMode` on: it lands as the implementation, or (refactoring
40
- // first when the judge asks) is built fresh from → the health loop → a
41
- // hands-on test of every criterion, fixed and re-tested → the
42
- // triaged quality lap, whose leftovers are carried on → after the last slice,
43
- // a reviewer's walkthrough of the whole change, every slice's findings
44
- // among its chunks, for the human, which
45
- // signs off (the episode ends back at `idle`) or sends a design → implement
46
- // lap.
32
+ // green-baseline gate → design → a naive pass, judged with `flagMode` on: it
33
+ // lands as the implementation, or (refactoring first when the judge asks) is
34
+ // built fresh from → the health loop → a hands-on test of every criterion,
35
+ // fixed and re-tested → the triaged quality lap, whose leftovers are carried
36
+ // on → a reviewer's walkthrough of the whole change, its findings among its
37
+ // chunks, for the human, which signs off (the episode ends back at `idle`) or
38
+ // sends a design → implement lap.
47
39
 
48
40
  /** Revert the sketch that started the process out of the working tree; its intent survives in history. */
49
41
  export const unwind = (): Promise<void> => {
@@ -56,36 +48,32 @@ export const unwind = (): Promise<void> => {
56
48
  export { reUnwind }
57
49
 
58
50
  /**
59
- * The slices `design.slicing` grouped the criteria into, each built in turn
60
- * from where the previous one landed; design must have run since `firstBase`.
51
+ * A naive pass (judged only with `flagMode` on), then implement and the
52
+ * quality lap, then the review; design must have run since `firstBase`.
61
53
  */
62
54
  export const buildFeature = async (firstBase: string): Promise<void> => {
63
55
  const tier = riskTierOn() ? recordRisk() : undefined
64
- const slices = await slicing()
65
- // Every ID handed out, gathered before a later narrowing can drop one: none is ever reused.
56
+ // Every ID handed out, gathered before a later edit can drop one: none is ever reused.
66
57
  const handedOut = new Set(listedIds())
67
- // A single slice keeps the unnumbered names and builds against every criterion.
68
- const one = slices.length === 1
69
- // A steer may delete every criterion of a later slice: there is nothing to build there.
70
- const built = (i: number): boolean => sliceIds(sliceCriteria(slices, i))?.length !== 0
71
- const reports: { slice: string; report: LensReport }[] = []
72
- for (const i of slices.keys()) {
73
- if (!built(i)) continue
74
- const n = one ? "" : `-${i + 1}`
75
- await buildSlice(i === 0 ? firstBase : head(), {
76
- number: `${i + 1}`,
77
- tier,
78
- criteria: sliceCriteria(slices, i),
79
- refactor: `refactor${n}`,
80
- feature: `feature${n}`,
81
- // A steer or a narrowing may empty every later slice.
82
- more: () => slices.some((_, j) => j > i && built(j)),
83
- // Every slice but the one is built unreviewed, so the review covers them all.
84
- reviewBase: one ? undefined : firstBase,
85
- handedOut,
86
- laps: reports,
87
- })
88
- }
58
+ const seam = await seamBuild(firstBase)
59
+ const used = gather(handedOut)
60
+ const closed = await implementing({
61
+ reference: seam.reference,
62
+ tier,
63
+ afterRefactor: seam.refactored,
64
+ naive: seam.land === true ? seam.reference : undefined,
65
+ rebuild: seam.rebuild,
66
+ })
67
+ // A feature with no code beyond the carried-over tests: the refactor carried
68
+ // it, so the lap covers the whole change.
69
+ const carried = seam.rebuild?.acceptance?.tests ?? []
70
+ const base =
71
+ seam.refactored === true &&
72
+ !changesSince(seam.base).some((c) => isCode(c.path) && !carried.includes(c.path))
73
+ ? firstBase
74
+ : seam.base
75
+ const outcome = await reviewedTail(base, base)
76
+ await featureFeedback(outcome, base, { closed, used })
89
77
  }
90
78
 
91
79
  const listedIds = (): readonly string[] => criterionIds(read(REQUIREMENTS) ?? "")
@@ -95,85 +83,14 @@ const gather = (handedOut: Set<string>): readonly string[] => {
95
83
  return [...handedOut]
96
84
  }
97
85
 
98
- interface SliceBuild {
99
- /** The slice's number, from 1. */
100
- readonly number: string
101
- readonly tier: RiskTier | undefined
102
- readonly criteria: CriteriaSlice | undefined
103
- readonly refactor: string
104
- readonly feature: string
105
- readonly more: () => boolean
106
- /** Absent: the slice's own base. */
107
- readonly reviewBase?: string | undefined
108
- /** Every criterion ID handed out so far; the slice adds those a steer adds. */
109
- readonly handedOut: Set<string>
110
- /** Each earlier slice's lap report, by slice number, for the review after the last one. */
111
- readonly laps: { slice: string; report: LensReport }[]
112
- }
113
-
114
- /** A naive pass (judged only with `flagMode` on), then implement and the quality lap; after the last slice, the review. */
115
- const buildSlice = async (sliceBase: string, build: SliceBuild): Promise<void> => {
116
- const seam = await seamBuild(sliceBase, { criteria: build.criteria, refactor: build.refactor })
117
- const used = gather(build.handedOut)
118
- await slice(build.feature, async () => {
119
- const closed = await implementing({
120
- reference: seam.reference,
121
- tier: build.tier,
122
- afterRefactor: seam.refactored,
123
- naive: seam.land === true ? seam.reference : undefined,
124
- rebuild: seam.rebuild,
125
- slice: build.criteria,
126
- more: build.more,
127
- })
128
- // Closing kept the criteria exactly when there is more to build, so this reads as it did.
129
- const more = build.more()
130
- // A feature slice with no code beyond the carried-over tests: the refactor carried the
131
- // feature, so the lap covers the whole slice.
132
- const carried = seam.rebuild?.acceptance?.tests ?? []
133
- const base =
134
- seam.refactored === true &&
135
- !changesSince(seam.base).some((c) => isCode(c.path) && !carried.includes(c.path))
136
- ? sliceBase
137
- : seam.base
138
- if (more) {
139
- // The criteria stay in the tree for the next slice; no quality fix may move them.
140
- const guard = () => requireCriteriaFrozen(REQUIREMENTS)
141
- // No gate between slices: what the lap left waits for the review after the last one.
142
- const report = await tail(async (_, report) => report, {
143
- green: true,
144
- lensBase: base,
145
- guard,
146
- guardRests: false,
147
- })
148
- build.laps.push({ slice: build.number, report })
149
- return
150
- }
151
- const reviewBase = build.reviewBase ?? base
152
- const outcome = await reviewedTail(reviewBase, base, { slice: build.number, laps: build.laps })
153
- await featureFeedback(outcome, base, { closed, used })
154
- })
155
- }
156
-
157
- /**
158
- * The quality lap of slice `last.slice`, then a reviewer's walkthrough since
159
- * `base` for the human, carrying what earlier slices' `laps` left too.
160
- */
86
+ /** The quality lap, then a reviewer's walkthrough since `base` for the human. */
161
87
  const reviewedTail = (
162
88
  base: string,
163
89
  lensBase: string,
164
- last: { readonly slice: string; readonly laps: SliceBuild["laps"] } = { slice: "", laps: [] },
165
90
  lap: "first" | "feedback" = "first",
166
91
  ): Promise<ReviewOutcome> =>
167
92
  tail(
168
- (escalations, report) =>
169
- humanReview(base, escalations, {
170
- report:
171
- last.laps.length === 0
172
- ? report
173
- : joinReports([...last.laps, { slice: last.slice, report }]),
174
- walkthrough: true,
175
- lap,
176
- }),
93
+ (escalations, report) => humanReview(base, escalations, { report, walkthrough: true, lap }),
177
94
  { green: true, lensBase },
178
95
  )
179
96
 
@@ -197,7 +114,7 @@ const featureFeedback = async (
197
114
  })
198
115
  const tier = riskTierOn() ? recordRisk() : undefined
199
116
  closed = await implementing({ tier, amend: true })
200
- outcome = await reviewedTail(outcome.base, lensBase, undefined, "feedback")
117
+ outcome = await reviewedTail(outcome.base, lensBase, "feedback")
201
118
  }
202
119
  }
203
120
 
@@ -209,7 +126,7 @@ export const planAndBuild = async (firstBase: string, stated?: StatedCriteria):
209
126
  }
210
127
 
211
128
  // seam.naive / seam.judge never run here: a re-plan's seam is built unjudged and unrefactored.
212
- /** After a tail that skipped planning: stop on sign-off, re-plan on feedback. Opens no slice: slices cannot nest. */
129
+ /** After a tail that skipped planning: stop on sign-off, re-plan on feedback. */
213
130
  export const afterTail = async (first: ReviewOutcome): Promise<void> => {
214
131
  let outcome = first
215
132
  while (outcome.verdict === "feedback") {
@@ -227,7 +144,6 @@ export const ordinaryStart = async (): Promise<void> => {
227
144
  refusePendingHandOff()
228
145
  // Read right after idle, so a bad value refuses the landing that would pin it.
229
146
  flagModeOn()
230
- sliceCap()
231
147
  prototypeRounds()
232
148
  const stated = statedCriteria(changes(), head())
233
149
  await unwind()
@@ -37,6 +37,10 @@ Feature: flag mode off lands the naive pass with no release flag, seam judge or
37
37
 
38
38
  - `subtract(a, b)` returns `a - b`.
39
39
 
40
+ ## Impact
41
+
42
+ - the greeting module
43
+
40
44
  ## Acceptance Criteria
41
45
 
42
46
  - AC-1: the change behaves as described.
@@ -7,14 +7,13 @@ Feature: the hands-on test tries only the criteria a person can observe
7
7
  per untagged criterion, whether a person can observe it by using the
8
8
  software; a tagged one is never asked about. Every ID it does not answer
9
9
  `no` reaches implement.prototype.testing, which must report each once and
10
- needs no chunk for the rest. A slice with no such criterion is not tested by hand, and
11
- nothing rests to say so.
10
+ needs no chunk for the rest. A change with no such criterion is not tested by
11
+ hand, and nothing rests to say so.
12
12
 
13
13
  Background:
14
14
  Given the shipped workflows, as gtd install scaffolds them
15
15
  And an environment variable "GTD_QUALITYREVIEWS" set to ""
16
16
  And an environment variable "GTD_FLAGMODE" set to "false"
17
- And an environment variable "GTD_SLICEMAXCRITERIA" set to "2"
18
17
  And a file "src/calc.ts" with:
19
18
  """
20
19
  export const add = (a: number, b: number) => a + b
@@ -39,6 +38,10 @@ Feature: the hands-on test tries only the criteria a person can observe
39
38
  """
40
39
  # Requirements
41
40
 
41
+ ## Impact
42
+
43
+ - the greeting module
44
+
42
45
  ## Acceptance Criteria
43
46
 
44
47
  - AC-1: subtract(a, b) returns a - b.
@@ -79,6 +82,10 @@ Feature: the hands-on test tries only the criteria a person can observe
79
82
  """
80
83
  # Requirements
81
84
 
85
+ ## Impact
86
+
87
+ - the greeting module
88
+
82
89
  ## Acceptance Criteria
83
90
 
84
91
  - AC-1: subtract(a, b) returns a - b.
@@ -114,6 +121,10 @@ Feature: the hands-on test tries only the criteria a person can observe
114
121
  """
115
122
  # Requirements
116
123
 
124
+ ## Impact
125
+
126
+ - the greeting module
127
+
117
128
  ## Acceptance Criteria
118
129
 
119
130
  - AC-1: subtract(a, b) returns a - b.
@@ -149,6 +160,10 @@ Feature: the hands-on test tries only the criteria a person can observe
149
160
  """
150
161
  # Requirements
151
162
 
163
+ ## Impact
164
+
165
+ - the greeting module
166
+
152
167
  ## Acceptance Criteria
153
168
 
154
169
  - AC-1: subtract(a, b) returns a - b.
@@ -176,6 +191,10 @@ Feature: the hands-on test tries only the criteria a person can observe
176
191
  """
177
192
  # Requirements
178
193
 
194
+ ## Impact
195
+
196
+ - the greeting module
197
+
179
198
  ## Acceptance Criteria
180
199
 
181
200
  - AC-1: subtract(a, b) returns a - b.
@@ -203,6 +222,10 @@ Feature: the hands-on test tries only the criteria a person can observe
203
222
  """
204
223
  # Requirements
205
224
 
225
+ ## Impact
226
+
227
+ - the greeting module
228
+
206
229
  ## Acceptance Criteria
207
230
 
208
231
  - AC-1: add is unchanged.
@@ -229,6 +252,10 @@ Feature: the hands-on test tries only the criteria a person can observe
229
252
  """
230
253
  # Requirements
231
254
 
255
+ ## Impact
256
+
257
+ - the greeting module
258
+
232
259
  ## Acceptance Criteria
233
260
 
234
261
  - AC-1 (no hands-on): the build prompt names subtract.
@@ -247,73 +274,14 @@ Feature: the hands-on test tries only the criteria a person can observe
247
274
  gtd(check): implement.closing → build.review.reviewing
248
275
  """
249
276
 
250
- Scenario: each slice's hands-on test judges its own criteria afresh
277
+ Scenario: the tag is frozen once design is settled
251
278
  Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
252
279
  """
253
280
  # Requirements
254
281
 
255
- ## Acceptance Criteria
256
-
257
- - AC-1: subtract(a, b) returns a - b.
258
- - AC-2 (no hands-on): the build prompt names subtract.
259
- - AC-3: the review prompt names subtract.
260
- """
261
- And step "design.slicing" writes ".gtd/SLICES.md" with:
262
- """
263
- # Slices
264
-
265
- ## Prompts
266
-
267
- - AC-2
268
- - AC-3
269
-
270
- ## Subtract
271
-
272
- - AC-1
273
- """
274
- And step "seam.naive", when prompted with "This slice builds AC-2, AC-3 only", writes "src/prompts.test.ts" with:
275
- """
276
- it("[AC-2] names subtract in the build", () => {})
277
- it("[AC-3] names subtract in the review", () => {})
278
- """
279
- And step "seam.naive", when prompted with "This slice builds AC-1 only", writes "src/calc.test.ts" with:
280
- """
281
- it("[AC-1] subtracts", () => {})
282
- """
283
- And judge step "implement.prototype.observable", when its questions contain "criterion AC-3 describes", answers:
284
- """
285
- [{"id":"AC-3","answer":"no","p":0.9}]
286
- """
287
- And judge step "implement.prototype.observable", when its questions contain "criterion AC-1 describes", answers:
288
- """
289
- [{"id":"AC-1","answer":"yes","p":0.9}]
290
- """
291
- And step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
292
- """
293
- ## AC-1: pass
294
-
295
- Printed 2.
296
- """
297
- When the process is driven
298
- Then the process rests at "build.review.reviewing"
299
- And the commit subjects end with:
300
- """
301
- gtd(check): implement.health.check → implement.prototype.observable
302
- gtd(judge): implement.prototype.observable → implement.closing
303
- gtd(check): implement.closing → seam.naive
304
- gtd(agent): seam.naive → seam.check
305
- gtd(check): seam.check → implement.applying
306
- gtd(check): implement.applying → implement.health.check
307
- gtd(check): implement.health.check → implement.prototype.observable
308
- gtd(judge): implement.prototype.observable → implement.prototype.testing
309
- gtd(agent): implement.prototype.testing → implement.closing
310
- gtd(check): implement.closing → build.review.reviewing
311
- """
282
+ ## Impact
312
283
 
313
- Scenario: the tag is frozen once design is settled
314
- Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
315
- """
316
- # Requirements
284
+ - the greeting module
317
285
 
318
286
  ## Acceptance Criteria
319
287
 
@@ -337,6 +305,10 @@ Feature: the hands-on test tries only the criteria a person can observe
337
305
  """
338
306
  # Requirements
339
307
 
308
+ ## Impact
309
+
310
+ - the greeting module
311
+
340
312
  ## Acceptance Criteria
341
313
 
342
314
  - AC-1 (no hands-on): subtract(a, b) returns a - b.
@@ -5,7 +5,6 @@ export * from "./criteria.js"
5
5
  export * from "./planning.js"
6
6
  export * from "./refactor.js"
7
7
  export * from "./seam.js"
8
- export * from "./slicing.js"
9
8
  export {
10
9
  buildTail,
11
10
  type BuildTailOptions,
@@ -33,6 +33,10 @@ Feature: each quality lens runs on its own model hint
33
33
  """
34
34
  # Requirements
35
35
 
36
+ ## Impact
37
+
38
+ - the greeting module
39
+
36
40
  ## Acceptance Criteria
37
41
 
38
42
  - AC-1: `subtract(a, b)` returns `a - b`.
@@ -35,6 +35,10 @@ Feature: the quality lap's findings are triaged before the review
35
35
 
36
36
  - `subtract(a, b)` returns `a - b`.
37
37
 
38
+ ## Impact
39
+
40
+ - the greeting module
41
+
38
42
  ## Acceptance Criteria
39
43
 
40
44
  - AC-1: subtract(a, b) returns a - b.
@@ -28,6 +28,10 @@ Feature: a naive pass ends only when a test names every criterion and the suite
28
28
  """
29
29
  # Requirements
30
30
 
31
+ ## Impact
32
+
33
+ - the greeting module
34
+
31
35
  ## Acceptance Criteria
32
36
 
33
37
  - AC-1: `subtract(5, 3)` returns 2.
@@ -36,6 +36,10 @@ Feature: a naive pass the judge rules land is itself the implementation
36
36
 
37
37
  - greeting reads "Hello, world".
38
38
 
39
+ ## Impact
40
+
41
+ - the greeting module
42
+
39
43
  ## Acceptance Criteria
40
44
 
41
45
  - AC-1: the change behaves as described.
@@ -83,8 +87,4 @@ Feature: a naive pass the judge rules land is itself the implementation
83
87
  And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
84
88
  And the commit "gtd(judge): seam.judge → implement.applying" has a body containing "\"answer\":\"land\""
85
89
  And no commit body contains "Gtd-Approved:"
86
- And the output lines of gtd "slices" start with:
87
- """
88
- feature
89
- """
90
90
  And no commit body contains "Gtd-Workflow:"
@@ -8,7 +8,7 @@ import {
8
8
  scope,
9
9
  } from "../../flows/index.js"
10
10
  import { REQUIREMENTS } from "../shared/steps.js"
11
- import { requireCriteria } from "./criteria.js"
11
+ import { requireCriteria, requireImpact } from "./criteria.js"
12
12
  import { answerProductQuestions, triage } from "./steps.js"
13
13
 
14
14
  /**
@@ -33,6 +33,7 @@ export const design = (base: string, guard?: () => void): Promise<void> =>
33
33
  requireProgress(REQUIREMENTS)
34
34
  requireReplies(REQUIREMENTS)
35
35
  requireCriteria(REQUIREMENTS)
36
+ requireImpact(REQUIREMENTS)
36
37
  guard?.()
37
38
  } while (await noteGate(REQUIREMENTS, answerProductQuestions))
38
39
  })
@@ -1,7 +1,7 @@
1
1
  @inmem
2
2
  Feature: past its round cap the hands-on test rests at a person's gate
3
3
 
4
- `prototypeRounds` caps the fix turns a slice's hands-on test gets. Once they
4
+ `prototypeRounds` caps the fix turns the hands-on test gets. Once they
5
5
  are spent and the report still has open items, the process rests at
6
6
  implement.prototype.open naming them; a person's fix there is tested again
7
7
  with a fresh budget.
@@ -28,6 +28,10 @@ Feature: past its round cap the hands-on test rests at a person's gate
28
28
  """
29
29
  # Requirements
30
30
 
31
+ ## Impact
32
+
33
+ - the greeting module
34
+
31
35
  ## Acceptance Criteria
32
36
 
33
37
  - AC-1: subtract(a, b) returns a - b.
@@ -3,10 +3,10 @@ Feature: every observable criterion is tested by hand before the quality lap
3
3
 
4
4
  Once the suite is green, implement.prototype.observable judges which
5
5
  criteria a person can observe, and implement.prototype.testing uses the
6
- slice the way a person would and writes .gtd/PROTOTYPE.md, one chunk per
6
+ change the way a person would and writes .gtd/PROTOTYPE.md, one chunk per
7
7
  such criterion headed `## AC-<n>: pass|fail|blocked`, plus one per problem
8
8
  found beside them. A failure goes to implement.prototype.fix.fixing and is tested again; only a clean
9
- report closes the slice. Past `prototypeRounds` fix rounds, or with nothing a
9
+ report closes the test. Past `prototypeRounds` fix rounds, or with nothing a
10
10
  fixer can take, a person decides at implement.prototype.open.
11
11
 
12
12
  Background:
@@ -30,6 +30,10 @@ Feature: every observable criterion is tested by hand before the quality lap
30
30
  """
31
31
  # Requirements
32
32
 
33
+ ## Impact
34
+
35
+ - the greeting module
36
+
33
37
  ## Acceptance Criteria
34
38
 
35
39
  - AC-1: subtract(a, b) returns a - b.
@@ -63,7 +67,7 @@ Feature: every observable criterion is tested by hand before the quality lap
63
67
  And the resting step's content contains "against the user's real data or this working tree"
64
68
  And the resting step's content contains "`## AC-<n>: pass`"
65
69
 
66
- Scenario: a clean report closes the slice and moves on to the quality lap
70
+ Scenario: a clean report closes the test and moves on to the quality lap
67
71
  Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
68
72
  """
69
73
  # Prototype test
@@ -236,6 +240,10 @@ Feature: every observable criterion is tested by hand before the quality lap
236
240
  """
237
241
  # Requirements
238
242
 
243
+ ## Impact
244
+
245
+ - the greeting module
246
+
239
247
  ## Acceptance Criteria
240
248
 
241
249
  - AC-2: add is unchanged.
@@ -5,14 +5,7 @@ import * as t from "../shared/text.js"
5
5
  import { defaults } from "../shared/vars.js"
6
6
  import type { Guard } from "../shared/health.js"
7
7
  import { green } from "./green.js"
8
- import {
9
- criteriaOf,
10
- criterionIds,
11
- handsOnIds,
12
- requireCriteriaFrozen,
13
- sliceIds,
14
- type CriteriaSlice,
15
- } from "./criteria.js"
8
+ import { criteriaOf, criterionIds, handsOnIds, requireCriteriaFrozen } from "./criteria.js"
16
9
 
17
10
  /** Only a `no` at this or above skips a criterion: no verdict, or an unsure one, sends it to the hands-on test. */
18
11
  const UNOBSERVABLE_MIN_P = 0.5
@@ -102,12 +95,12 @@ const requireReport = (
102
95
  }
103
96
 
104
97
  /**
105
- * The slice's criteria a person can observe by using the change, by the
106
- * judge: those tagged `(no hands-on)` are never asked about.
98
+ * The criteria a person can observe by using the change, by the judge: those
99
+ * tagged `(no hands-on)` are never asked about.
107
100
  */
108
- const observable = async (slice: CriteriaSlice | undefined): Promise<string[]> => {
101
+ const observable = async (): Promise<string[]> => {
109
102
  const requirements = read(REQUIREMENTS) ?? ""
110
- const own = handsOnIds(requirements, slice)
103
+ const own = handsOnIds(requirements)
111
104
  const criteria = (criteriaOf(requirements) ?? []).filter((c) => own.includes(c.id))
112
105
  if (criteria.length === 0) return []
113
106
  const { answers } = await judge("observable", {
@@ -122,13 +115,12 @@ const observable = async (slice: CriteriaSlice | undefined): Promise<string[]> =
122
115
  const frozen = (): void => requireCriteriaFrozen(REQUIREMENTS)
123
116
 
124
117
  const testing = async (
125
- slice: CriteriaSlice | undefined,
126
118
  ids: readonly string[],
127
119
  retest: boolean,
128
120
  ): Promise<ReturnType<typeof requireReport>> => {
129
- const skipped = criterionIds(read(REQUIREMENTS) ?? "", slice).filter((id) => !ids.includes(id))
130
- await t.agentWithSkills("testing", t.prototypeTestingPrompt(sliceIds(slice), skipped, retest), {
131
- label: retest ? "Re-testing the slice by hand" : "Testing the slice by hand",
121
+ const skipped = criterionIds(read(REQUIREMENTS) ?? "").filter((id) => !ids.includes(id))
122
+ await t.agentWithSkills("testing", t.prototypeTestingPrompt(skipped, retest), {
123
+ label: retest ? "Re-testing the change by hand" : "Testing the change by hand",
132
124
  file: PROTOTYPE,
133
125
  model: coder(),
134
126
  system: t.testerSystem(),
@@ -138,9 +130,9 @@ const testing = async (
138
130
  return requireReport(ids, retest)
139
131
  }
140
132
 
141
- const fixing = async (slice: CriteriaSlice | undefined, guard?: Guard): Promise<void> => {
133
+ const fixing = async (guard?: Guard): Promise<void> => {
142
134
  await scope("fix", () =>
143
- t.agentWithSkills("fixing", t.prototypeFixingPrompt(sliceIds(slice)), {
135
+ t.agentWithSkills("fixing", t.prototypeFixingPrompt(), {
144
136
  label: "Fixing what the hands-on test found",
145
137
  file: PROTOTYPE,
146
138
  model: coder(),
@@ -166,25 +158,22 @@ const askPerson = async (name: string, message: string, label: string): Promise<
166
158
  }
167
159
 
168
160
  /**
169
- * Use the slice by hand: a testing turn reports on every criterion a person
170
- * can observe, a fix turn takes each failure, and the slice is tested again —
161
+ * Use the change by hand: a testing turn reports on every criterion a person
162
+ * can observe, a fix turn takes each failure, and the change is tested again —
171
163
  * until the report is clean, or `prototypeRounds` fixes are spent and a person
172
- * decides. A slice with no such criterion is not tested by hand. `guard` holds
173
- * every fix turn, as it holds the implementation's.
164
+ * decides. A change with no such criterion is not tested by hand. `guard`
165
+ * holds every fix turn, as it holds the implementation's.
174
166
  */
175
- export const prototypeTest = async (
176
- slice: CriteriaSlice | undefined,
177
- guard?: Guard,
178
- ): Promise<void> => {
167
+ export const prototypeTest = async (guard?: Guard): Promise<void> => {
179
168
  const cap = prototypeRounds()
180
169
  if (cap === 0) return
181
170
  await scope("prototype", async () => {
182
- const ids = await observable(slice)
171
+ const ids = await observable()
183
172
  if (ids.length === 0) return
184
173
  let fixes = 0
185
174
  let retest = false
186
175
  for (;;) {
187
- const { unguided, open } = await testing(slice, ids, retest)
176
+ const { unguided, open } = await testing(ids, retest)
188
177
  retest = !unguided
189
178
  if (unguided) {
190
179
  const instructed = await askPerson(
@@ -196,7 +185,7 @@ export const prototypeTest = async (
196
185
  } else if (open.length === 0) return
197
186
  else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
198
187
  fixes++
199
- await fixing(slice, guard)
188
+ await fixing(guard)
200
189
  } else {
201
190
  const changed = await askPerson(
202
191
  "open",