@pmelab/gtd 53.0.0 → 55.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 (36) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +56 -50
  3. package/dist/chunks/{reserved-CQ7amcKG.mjs → reserved-DHafcW7t.mjs} +47 -27
  4. package/dist/chunks/{review-CNgN_V8T.mjs → review-BJGKwlYv.mjs} +12 -5
  5. package/dist/chunks/{tail-JZ9PAOwp.mjs → tail-B9Hw2AFB.mjs} +12 -8
  6. package/dist/workflows/cleanup.mjs +2 -2
  7. package/dist/workflows/feature.mjs +41 -94
  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 +107 -129
  12. package/package.json +1 -1
  13. package/src/testkit/steps.ts +23 -0
  14. package/src/workflows/feature/criteria-coverage.feature +109 -0
  15. package/src/workflows/feature/feature.ts +9 -4
  16. package/src/workflows/feature/green.ts +2 -5
  17. package/src/workflows/feature/lens-review.feature +50 -0
  18. package/src/workflows/feature/naive-acceptance.feature +31 -32
  19. package/src/workflows/feature/prototype.ts +5 -8
  20. package/src/workflows/feature/refactor-rebuild.feature +16 -12
  21. package/src/workflows/feature/refactor-review.feature +38 -0
  22. package/src/workflows/feature/reviewer-walkthrough.feature +0 -31
  23. package/src/workflows/feature/seam.test.ts +19 -101
  24. package/src/workflows/feature/seam.ts +13 -89
  25. package/src/workflows/fix/fix-quality.feature +4 -2
  26. package/src/workflows/shared/access.test.ts +1 -0
  27. package/src/workflows/shared/review.test.ts +3 -1
  28. package/src/workflows/shared/review.ts +12 -2
  29. package/src/workflows/shared/skills.test.ts +3 -1
  30. package/src/workflows/shared/steps.test.ts +9 -6
  31. package/src/workflows/shared/steps.ts +12 -3
  32. package/src/workflows/shared/tail.test.ts +20 -2
  33. package/src/workflows/shared/tail.ts +20 -7
  34. package/src/workflows/shared/text.test.ts +19 -6
  35. package/src/workflows/shared/text.ts +50 -29
  36. package/src/workflows/shared/vars.ts +1 -1
@@ -57,6 +57,8 @@ export const buildFeature = async (firstBase: string): Promise<void> => {
57
57
  const handedOut = new Set(listedIds())
58
58
  const seam = await seamBuild(firstBase)
59
59
  const used = gather(handedOut)
60
+ // Read before the implementation closes the file: the quality lap's coverage lens checks them.
61
+ const requirements = read(REQUIREMENTS)
60
62
  const closed = await implementing({
61
63
  reference: seam.reference,
62
64
  tier,
@@ -72,7 +74,7 @@ export const buildFeature = async (firstBase: string): Promise<void> => {
72
74
  !changesSince(seam.base).some((c) => isCode(c.path) && !carried.includes(c.path))
73
75
  ? firstBase
74
76
  : seam.base
75
- const outcome = await reviewedTail(base, base)
77
+ const outcome = await reviewedTail(base, base, requirements)
76
78
  await featureFeedback(outcome, base, { closed, used })
77
79
  }
78
80
 
@@ -87,11 +89,12 @@ const gather = (handedOut: Set<string>): readonly string[] => {
87
89
  const reviewedTail = (
88
90
  base: string,
89
91
  lensBase: string,
92
+ requirements: string | undefined,
90
93
  lap: "first" | "feedback" = "first",
91
94
  ): Promise<ReviewOutcome> =>
92
95
  tail(
93
96
  (escalations, report) => humanReview(base, escalations, { report, walkthrough: true, lap }),
94
- { green: true, lensBase },
97
+ { green: true, lensBase, requirements },
95
98
  )
96
99
 
97
100
  // seam.naive / seam.judge never run on a feedback lap: its design → implement is built unjudged
@@ -113,8 +116,9 @@ const featureFeedback = async (
113
116
  gather(handedOut)
114
117
  })
115
118
  const tier = riskTierOn() ? recordRisk() : undefined
119
+ const requirements = read(REQUIREMENTS)
116
120
  closed = await implementing({ tier, amend: true })
117
- outcome = await reviewedTail(outcome.base, lensBase, "feedback")
121
+ outcome = await reviewedTail(outcome.base, lensBase, requirements, "feedback")
118
122
  }
119
123
  }
120
124
 
@@ -133,8 +137,9 @@ export const afterTail = async (first: ReviewOutcome): Promise<void> => {
133
137
  await reUnwind(outcome)
134
138
  await design(outcome.base)
135
139
  const tier = riskTierOn() ? recordRisk() : undefined
140
+ const requirements = read(REQUIREMENTS)
136
141
  await implement({ tier, amend: true })
137
- outcome = await buildTail(outcome.base, { green: true })
142
+ outcome = await buildTail(outcome.base, { green: true, requirements })
138
143
  }
139
144
  }
140
145
 
@@ -3,7 +3,7 @@ import { coder, FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
3
3
  import * as t from "../shared/text.js"
4
4
  import { requireCriteriaFrozen } from "./criteria.js"
5
5
 
6
- export const green = (guard?: () => void): Promise<void> =>
6
+ export const green = (): Promise<void> =>
7
7
  healthy(
8
8
  () =>
9
9
  t.agentWithSkills("fix.suite.fixing", t.implementFixPrompt(), {
@@ -13,10 +13,7 @@ export const green = (guard?: () => void): Promise<void> =>
13
13
  system: t.builderSystem(),
14
14
  }),
15
15
  {
16
- guard: () => {
17
- requireCriteriaFrozen(REQUIREMENTS)
18
- guard?.()
19
- },
16
+ guard: () => requireCriteriaFrozen(REQUIREMENTS),
20
17
  guardRests: false,
21
18
  },
22
19
  )
@@ -161,6 +161,56 @@ Feature: the quality lap's findings are triaged before the review
161
161
  gtd(check): build.review.carrying → build.review.await-review
162
162
  """
163
163
 
164
+ Scenario: a risk is fixed before the human sees the review, re-reviewed in the reviewer's own draft
165
+ Given judge step "build.quality.triage" answers:
166
+ """
167
+ [{"id":"Q1","answer":"mechanical","p":0.9},{"id":"Q2","answer":"noise","p":0.85},{"id":"Q3","answer":"design","p":0.8}]
168
+ """
169
+ And step "build.fix.quality.fixing" writes "src/calc.test.ts" with:
170
+ """
171
+ it("[AC-1] subtracts", () => {})
172
+ it("subtracts a larger number", () => {})
173
+ """
174
+ And step "build.fix.quality.fixing" deletes ".gtd/QUALITY.md"
175
+ And step "build.health.check" changes nothing
176
+ And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
177
+ """
178
+ # Review: abc1234
179
+
180
+ <!-- base: 0000000 -->
181
+
182
+ ## Subtract
183
+
184
+ - [ ] ./src/calc.ts#2-2 — Risk: overflows on large numbers
185
+
186
+ ## AC-1 ignores floating point
187
+
188
+ - [ ] ./src/calc.ts#2-2 — where it applies
189
+ """
190
+ When the process is driven
191
+ Then the process rests at "build.review.fix.risks.fixing"
192
+ Given step "build.review.reviewing" is no longer scripted
193
+ And step "build.review.reviewing" replaces the text "Risk: overflows on large numbers" in ".gtd/REVIEW.md" with "subtract(a, b), now guarded"
194
+ And step "build.review.fix.risks.fixing" changes nothing
195
+ When the process is driven
196
+ Then the process rests at "build.review.await-review"
197
+ And ".gtd/REVIEW.md" contains "subtract(a, b), now guarded"
198
+ And ".gtd/REVIEW.md" contains "## Fixed automatically" exactly once
199
+ And ".gtd/REVIEW.md" contains "## Dropped as noise" exactly once
200
+ And ".gtd/REVIEW.md" contains "## Walkthrough" exactly once
201
+ And ".gtd/REVIEW.md" contains "Should subtract round? The criterion does not say." exactly once
202
+ And ".gtd/REVIEW.md" contains "Tick exactly one:" exactly once
203
+ And the commit subjects end with:
204
+ """
205
+ gtd(check): build.health.check → build.review.reviewing
206
+ gtd(agent): build.review.reviewing → build.review.fix.risks.fixing
207
+ gtd(agent): build.review.fix.risks.fixing → build.health.check
208
+ gtd(check): build.health.check → build.review.reviewing
209
+ gtd(agent): build.review.reviewing → build.review.ranking
210
+ gtd(judge): build.review.ranking → build.review.carrying
211
+ gtd(check): build.review.carrying → build.review.await-review
212
+ """
213
+
164
214
  Scenario: an unsure verdict makes a finding a note, never a fix or a drop
165
215
  Given judge step "build.quality.triage" answers:
166
216
  """
@@ -1,11 +1,10 @@
1
1
  @inmem
2
- Feature: a naive pass ends only when a test names every criterion and the suite is green
2
+ Feature: a naive pass ends only when its tests reach no file it added and the suite is green
3
3
 
4
- `seam.naive` writes behaviour-level acceptance tests, each naming its
5
- criterion's ID. A pass whose tests leave an ID out or import a file the
6
- pass added is sent back; `seam.check` then runs the suite with the kept
7
- pass applied, and a red run sends it back too, before the judge sees it.
8
- Three failed attempts rest at `seam.steer`.
4
+ `seam.naive` needs no test at all. A pass whose test imports a file the pass
5
+ added is sent back; `seam.check` then runs the suite with the kept pass
6
+ applied, and a red run sends it back too, before the judge sees it. Three
7
+ failed attempts rest at `seam.steer`.
9
8
 
10
9
  Background:
11
10
  Given the shipped workflows, as gtd install scaffolds them
@@ -44,15 +43,30 @@ Feature: a naive pass ends only when a test names every criterion and the suite
44
43
  export const subtract = (a: number, b: number) => a - b
45
44
  """
46
45
 
47
- Scenario: a pass whose tests leave a criterion's ID out is sent back, and an overrule runs the check and sticks
48
- Given step "seam.naive" writes "tests/calc.test.ts" with:
46
+ Scenario: a pass without a test goes on to the check and the judge
47
+ Given step "seam.check" changes nothing
48
+ When the process is driven
49
+ Then the process rests at "seam.judge"
50
+ And the commit subjects end with:
49
51
  """
50
- import { add, subtract } from "../src/calc.js"
51
- it("[AC-1] subtracts", () => expect(subtract(5, 3)).toBe(2))
52
+ gtd(human): design.gate.answer → seam.naive
53
+ gtd(agent): seam.naive → seam.check
54
+ gtd(check): seam.check → seam.judge
55
+ """
56
+
57
+ Scenario: a test importing a file the pass added is sent back, and an overrule runs the check and sticks
58
+ Given step "seam.naive" writes "src/subtract.ts" with:
59
+ """
60
+ export const subtract = (a: number, b: number) => a - b
61
+ """
62
+ And step "seam.naive" writes "src/subtract.test.ts" with:
63
+ """
64
+ import { subtract } from "./subtract.js"
65
+ it("subtracts", () => expect(subtract(5, 3)).toBe(2))
52
66
  """
53
67
  When the process is driven
54
68
  Then the process rests at "seam.steer"
55
- And the resting step's content contains "acceptance-tests: no test of the naive pass names AC-2"
69
+ And the resting step's content contains "src/subtract.test.ts → src/subtract.ts"
56
70
 
57
71
  Given step "seam.check" changes nothing
58
72
  When gtd lands
@@ -87,27 +101,12 @@ Feature: a naive pass ends only when a test names every criterion and the suite
87
101
  gtd(judge): seam.judge → seam.steer
88
102
  """
89
103
 
90
- Scenario: a test importing a file the pass added is sent back
91
- Given step "seam.naive" writes "src/subtract.ts" with:
92
- """
93
- export const subtract = (a: number, b: number) => a - b
94
- """
95
- And step "seam.naive" writes "src/subtract.test.ts" with:
96
- """
97
- import { subtract } from "./subtract.js"
98
- it("[AC-1] subtracts", () => expect(subtract(5, 3)).toBe(2))
99
- it("[AC-2] still adds", () => {})
100
- """
101
- When the process is driven
102
- Then the process rests at "seam.steer"
103
- And the resting step's content contains "src/subtract.test.ts → src/subtract.ts"
104
-
105
104
  Scenario: a red suite sends the pass back, and it moves on only once green
106
105
  Given step "seam.naive" writes "tests/calc.test.ts" with:
107
106
  """
108
107
  import { add, subtract } from "../src/calc.js"
109
- it("[AC-1] subtracts", () => expect(subtract(5, 3)).toBe(2))
110
- it("[AC-2] still adds", () => expect(add(1, 1)).toBe(2))
108
+ it("subtracts", () => expect(subtract(5, 3)).toBe(2))
109
+ it("still adds", () => expect(add(1, 1)).toBe(2))
111
110
  """
112
111
  When the process is driven
113
112
  Then the process rests at "seam.check"
@@ -118,18 +117,18 @@ Feature: a naive pass ends only when a test names every criterion and the suite
118
117
  Given step "seam.naive" is no longer scripted
119
118
  And step "seam.check" writes ".gtd/FEEDBACK.md" with:
120
119
  """
121
- FAIL tests/calc.test.ts > [AC-1] subtracts
120
+ FAIL tests/calc.test.ts > subtracts
122
121
  """
123
122
  When the process is driven
124
123
  Then the process rests at "seam.naive"
125
124
  And the resting step's content contains "left the suite red"
126
- And ".gtd/FEEDBACK.md" contains "[AC-1] subtracts"
125
+ And ".gtd/FEEDBACK.md" contains "> subtracts"
127
126
 
128
127
  Given step "seam.naive" writes "tests/calc.test.ts" with:
129
128
  """
130
129
  import { add, subtract } from "../src/calc.js"
131
- it("[AC-1] subtracts", () => expect(subtract(5, 3)).toBe(2))
132
- it("[AC-2] still adds", () => expect(add(2, 2)).toBe(4))
130
+ it("subtracts", () => expect(subtract(5, 3)).toBe(2))
131
+ it("still adds", () => expect(add(2, 2)).toBe(4))
133
132
  """
134
133
  And step "seam.check" from now on changes nothing
135
134
  When the process is driven
@@ -3,7 +3,6 @@ import { HEADING, markFenced } from "../shared/fences.js"
3
3
  import { coder, PROTOTYPE, REQUIREMENTS } from "../shared/steps.js"
4
4
  import * as t from "../shared/text.js"
5
5
  import { defaults } from "../shared/vars.js"
6
- import type { Guard } from "../shared/health.js"
7
6
  import { green } from "./green.js"
8
7
  import { criteriaOf, criterionIds, handsOnIds, requireCriteriaFrozen } from "./criteria.js"
9
8
 
@@ -130,7 +129,7 @@ const testing = async (
130
129
  return requireReport(ids, retest)
131
130
  }
132
131
 
133
- const fixing = async (guard?: Guard): Promise<void> => {
132
+ const fixing = async (): Promise<void> => {
134
133
  await scope("fix", () =>
135
134
  t.agentWithSkills("fixing", t.prototypeFixingPrompt(), {
136
135
  label: "Fixing what the hands-on test found",
@@ -145,7 +144,6 @@ const fixing = async (guard?: Guard): Promise<void> => {
145
144
  if (changes().some((c) => c.path === PROTOTYPE))
146
145
  refuse(`gtd land: prototype-fix: leave ${PROTOTYPE} as it is — the next round re-tests from it`)
147
146
  frozen()
148
- guard?.()
149
147
  }
150
148
 
151
149
  /** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
@@ -161,10 +159,9 @@ const askPerson = async (name: string, message: string, label: string): Promise<
161
159
  * Use the change by hand: a testing turn reports on every criterion a person
162
160
  * can observe, a fix turn takes each failure, and the change is tested again —
163
161
  * until the report is clean, or `prototypeRounds` fixes are spent and a person
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.
162
+ * decides. A change with no such criterion is not tested by hand.
166
163
  */
167
- export const prototypeTest = async (guard?: Guard): Promise<void> => {
164
+ export const prototypeTest = async (): Promise<void> => {
168
165
  const cap = prototypeRounds()
169
166
  if (cap === 0) return
170
167
  await scope("prototype", async () => {
@@ -185,7 +182,7 @@ export const prototypeTest = async (guard?: Guard): Promise<void> => {
185
182
  } else if (open.length === 0) return
186
183
  else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
187
184
  fixes++
188
- await fixing(guard)
185
+ await fixing()
189
186
  } else {
190
187
  const changed = await askPerson(
191
188
  "open",
@@ -195,7 +192,7 @@ export const prototypeTest = async (guard?: Guard): Promise<void> => {
195
192
  if (!changed) return
196
193
  fixes = 0
197
194
  }
198
- await green(guard)
195
+ await green()
199
196
  }
200
197
  })
201
198
  }
@@ -5,8 +5,9 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
5
5
  On a refactor-first judgment the naive pass is never applied. `seam.note`
6
6
  reads the whole naive diff and writes `.gtd/REFACTOR-NOTE.md`; the refactor
7
7
  lands on its own with the existing suite green; the rebuild re-implements the
8
- change on the refactored base and lands only once it carries the naive pass's
9
- acceptance tests and the suite is green. The note reaches both turns.
8
+ change on the refactored base, is asked to carry over every test file the
9
+ naive pass added or changed, and lands once the suite is green. The note
10
+ reaches both turns.
10
11
 
11
12
  Background:
12
13
  Given the shipped workflows, as gtd install scaffolds them
@@ -18,6 +19,10 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
18
19
  """
19
20
  export const add = (a: number, b: number) => a + b
20
21
  """
22
+ And a file "src/calc.test.ts" with:
23
+ """
24
+ it("adds", () => {})
25
+ """
21
26
  And the working tree is committed as "feat: add calculator"
22
27
  And a file "TODO.md" with:
23
28
  """
@@ -49,7 +54,12 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
49
54
  And step "seam.check" changes nothing
50
55
  And step "seam.naive" writes "src/acceptance.test.ts" with:
51
56
  """
52
- it("[AC-1] subtracts", () => {})
57
+ it("subtracts", () => {})
58
+ """
59
+ And step "seam.naive" writes "src/calc.test.ts" with:
60
+ """
61
+ it("adds", () => {})
62
+ it("subtracts a larger number", () => {})
53
63
  """
54
64
  And step "seam.naive" writes "src/calc.ts" with:
55
65
  """
@@ -125,7 +135,8 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
125
135
  And the process is driven
126
136
  Then the process rests at "implement.building"
127
137
  And the resting step's content contains "shared `op(a, b, sign)`"
128
- And the resting step's content contains "acceptance tests for AC-1"
138
+ And the resting step's content contains "Carry over the naive pass's acceptance tests"
139
+ And the resting step's content contains "'src/acceptance.test.ts' 'src/calc.test.ts'"
129
140
 
130
141
  Scenario: a note turn that changes anything but the note is refused
131
142
  Given step "seam.note" writes "src/calc.ts" with:
@@ -198,7 +209,7 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
198
209
  And "src/calc.ts" contains "op(a, b, 1)"
199
210
  And "src/calc.ts" does not contain "subtract"
200
211
 
201
- Scenario: the rebuild lands only once it carries the naive pass's acceptance tests, green
212
+ Scenario: the rebuild lands once green, whichever tests it carried
202
213
  Given step "seam.note" writes ".gtd/REFACTOR-NOTE.md" with:
203
214
  """
204
215
  Seam: a shared `op` in src/calc.ts.
@@ -254,18 +265,11 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
254
265
  export const add = (a: number, b: number) => op(a, b, 1)
255
266
  export const subtract = (a: number, b: number) => op(a, b, -1)
256
267
  """
257
- Then driving the process is refused with "rebuild-acceptance"
258
-
259
- Given step "implement.building" writes "src/acceptance.test.ts" with:
260
- """
261
- it("[AC-1] subtracts", () => {})
262
- """
263
268
  And step "implement.health.check" changes nothing
264
269
  And step "implement.closing" runs its script
265
270
  When the process is driven
266
271
  Then the process rests at "build.review.reviewing"
267
272
  And "src/calc.ts" contains "op(a, b, -1)"
268
- And "src/acceptance.test.ts" contains "[AC-1]"
269
273
  And ".gtd/REFACTOR-NOTE.md" does not exist
270
274
  And the commit subjects end with:
271
275
  """
@@ -1185,6 +1185,44 @@ Feature: the refactor is reviewed on its own before the feature is implemented
1185
1185
  """
1186
1186
  And no commit body contains "refactor.build.review.composing"
1187
1187
 
1188
+ Scenario: a risk the refactor's re-review marks again reaches the human unfixed
1189
+ Given step "refactor.build.review.reviewing" writes ".gtd/REVIEW.md" with:
1190
+ """
1191
+ # Review: abc1234
1192
+
1193
+ <!-- base: 0000000 -->
1194
+
1195
+ ## calc
1196
+
1197
+ - [ ] ./src/calc.ts#1-2 — Risk: drops the sign
1198
+ """
1199
+ When the process is driven
1200
+ Then the process rests at "refactor.build.review.fix.risks.fixing"
1201
+ Given step "refactor.build.review.reviewing" is no longer scripted
1202
+ And step "refactor.build.review.reviewing" replaces the text "Risk: drops the sign" in ".gtd/REVIEW.md" with "Risk: still drops the sign"
1203
+ And step "refactor.build.review.fix.risks.fixing" changes nothing
1204
+ And step "refactor.build.health.check" changes nothing
1205
+ When the process is driven
1206
+ Then the process rests at "refactor.build.review.await-review"
1207
+ And ".gtd/REVIEW.md" contains:
1208
+ """
1209
+ ## Walkthrough
1210
+
1211
+ ### calc
1212
+
1213
+ - [ ] ./src/calc.ts#1-2 — Risk: still drops the sign
1214
+ """
1215
+ And ".gtd/REVIEW.md" contains "## Walkthrough" exactly once
1216
+ And the commit subjects end with:
1217
+ """
1218
+ gtd(agent): refactor.build.review.reviewing → refactor.build.review.fix.risks.fixing
1219
+ gtd(agent): refactor.build.review.fix.risks.fixing → refactor.build.health.check
1220
+ gtd(check): refactor.build.health.check → refactor.build.review.reviewing
1221
+ gtd(agent): refactor.build.review.reviewing → refactor.build.review.ranking
1222
+ gtd(judge): refactor.build.review.ranking → refactor.build.review.carrying
1223
+ gtd(check): refactor.build.review.carrying → refactor.build.review.await-review
1224
+ """
1225
+
1188
1226
  Rule: with a quality lens on both the refactor and the feature
1189
1227
 
1190
1228
  Background:
@@ -179,37 +179,6 @@ Feature: a reviewer walks the human through the feature's code
179
179
  """
180
180
  Then driving the process is refused with "AC-1 ignores floating point — no chunk of its own"
181
181
 
182
- Scenario: a risk the reviewer marks is fixed before the human sees the review
183
- Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
184
- """
185
- # Review: abc1234
186
-
187
- <!-- base: 0000000 -->
188
-
189
- ## Subtract
190
-
191
- - [ ] ./src/calc.ts#2-2 — Risk: overflows on large numbers
192
-
193
- ## AC-1 ignores floating point
194
-
195
- - [ ] ./src/calc.ts#2-2 — returns the raw difference
196
- """
197
- And step "build.review.fix.risks.fixing" changes nothing
198
- And step "build.health.check" changes nothing
199
- When the process is driven
200
- Then the process rests at "build.review.await-review"
201
- And the commit subjects end with:
202
- """
203
- gtd(agent): build.review.reviewing → build.review.ranking
204
- gtd(judge): build.review.ranking → build.review.carrying
205
- gtd(check): build.review.carrying → build.review.fix.risks.fixing
206
- gtd(agent): build.review.fix.risks.fixing → build.health.check
207
- gtd(check): build.health.check → build.review.reviewing
208
- gtd(agent): build.review.reviewing → build.review.ranking
209
- gtd(judge): build.review.ranking → build.review.carrying
210
- gtd(check): build.review.carrying → build.review.await-review
211
- """
212
-
213
182
  Scenario: a standard conflict the reviewer writes itself is refused
214
183
  Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
215
184
  """
@@ -14,8 +14,6 @@ import {
14
14
  naivePass,
15
15
  acceptanceFault,
16
16
  naiveAcceptance,
17
- requireAcceptance,
18
- requireAcceptanceKept,
19
17
  seamBuild,
20
18
  type SeamOutcome,
21
19
  } from "./seam.js"
@@ -108,71 +106,24 @@ describe("naivePass", () => {
108
106
  })
109
107
  })
110
108
 
111
- describe("naiveAcceptance and requireAcceptance", () => {
112
- const requirements =
113
- "# R\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: adds.\n- AC-2: subtracts.\n"
114
- const file = (path: string, before: string | undefined, after: string): Change => ({
115
- path,
116
- status: before === undefined ? "added" : "modified",
117
- before,
118
- after,
119
- })
120
- const install = (since: Change[] = []) =>
121
- installContext({
122
- ...fixtureContext({}),
123
- matches: globMatches,
124
- read: (path: string) => (path === ".gtd/REQUIREMENTS.md" ? requirements : undefined),
125
- changesSince: () => since,
126
- changes: () => since,
127
- refuse: (message: string) => {
128
- throw new Error(message)
129
- },
130
- } as unknown as FlowContext)
131
-
132
- it("takes the IDs the pass's tests name on an added line, and only those test files", () => {
133
- install()
109
+ describe("naiveAcceptance", () => {
110
+ it("carries every test file the pass added or changed, named or not", () => {
111
+ installContext({ ...fixtureContext({}), matches: globMatches } as unknown as FlowContext)
134
112
  expect(
135
113
  naiveAcceptance(
136
114
  tip(
137
- file("src/a.test.ts", 'it("[AC-2] old")', 'it("[AC-2] old")\nit("[AC-1] adds")'),
138
- file("src/b.test.ts", undefined, 'it("helper")'),
139
- file("src/calc.ts", "x", "AC-2"),
115
+ change("src/a.test.ts", "modified"),
116
+ change("tests/b.feature", "added"),
117
+ change("src/gone.test.ts", "deleted"),
118
+ change("src/calc.ts", "modified"),
119
+ change(".gtd/x.test.ts", "added"),
140
120
  ),
141
121
  ),
142
- ).toEqual({ hash: "abc", tests: ["src/a.test.ts"], ids: ["AC-1"] })
143
- })
144
-
145
- it("refuses a rebuild whose tests leave out an ID, naming the naive tests to carry over", () => {
146
- install([file("src/calc.test.ts", undefined, 'it("[AC-1] adds")')])
147
- expect(() =>
148
- requireAcceptance({ hash: "abc", tests: ["src/a.test.ts"], ids: ["AC-1", "AC-2"] }, "c0"),
149
- ).toThrow(
150
- /^gtd land: rebuild-acceptance: .*leave out AC-2 — .*git show abc -- 'src\/a\.test\.ts'/,
151
- )
152
- })
153
-
154
- it("refuses a fix turn that itself drops a carried ID, never one that leaves it alone", () => {
155
- const acceptance = { hash: "abc", tests: [], ids: ["AC-1"] }
156
- install([file("src/calc.test.ts", 'it("[AC-1] adds")', 'it("adds")')])
157
- expect(() => requireAcceptanceKept(acceptance)).toThrow(/drops AC-1/)
158
- install([file("src/calc.ts", "a", "b")])
159
- expect(() => requireAcceptanceKept(acceptance)).not.toThrow()
160
- })
161
-
162
- it("passes a rebuild whose tests name every ID, wherever they live", () => {
163
- install([
164
- file("tests/x.test.ts", undefined, 'it("[AC-1] adds")'),
165
- file("tests/y.feature", "", "Scenario: [AC-2] subtracts"),
166
- ])
167
- expect(() =>
168
- requireAcceptance({ hash: "abc", tests: ["src/a.test.ts"], ids: ["AC-1", "AC-2"] }, "c0"),
169
- ).not.toThrow()
122
+ ).toEqual({ hash: "abc", tests: ["src/a.test.ts", "tests/b.feature"] })
170
123
  })
171
124
  })
172
125
 
173
126
  describe("acceptanceFault", () => {
174
- const requirements =
175
- "# R\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: adds.\n- AC-2: subtracts.\n"
176
127
  const file = (path: string, status: Change["status"], after: string): Change => ({
177
128
  path,
178
129
  status,
@@ -180,30 +131,10 @@ describe("acceptanceFault", () => {
180
131
  after,
181
132
  })
182
133
  const refusal = (...changes: Change[]): string | undefined => {
183
- installContext({
184
- ...fixtureContext({}),
185
- matches: globMatches,
186
- read: (path: string) => (path === ".gtd/REQUIREMENTS.md" ? requirements : undefined),
187
- } as unknown as FlowContext)
188
- return acceptanceFault(".gtd/REQUIREMENTS.md", tip(...changes))
134
+ installContext({ ...fixtureContext({}), matches: globMatches } as unknown as FlowContext)
135
+ return acceptanceFault(tip(...changes))
189
136
  }
190
- const tagged = 'import { add } from "../src/calc.js"\nit("[AC-1] adds")\nit("[AC-2] subtracts")'
191
-
192
- it("passes when every ID is named in a test the pass wrote", () => {
193
- expect(
194
- refusal(file("src/calc.ts", "modified", "x"), file("tests/calc.test.ts", "added", tagged)),
195
- ).toBe(undefined)
196
- })
197
-
198
- it("names every ID no test of the pass mentions", () => {
199
- const message = refusal(file("tests/calc.test.ts", "added", 'it("[AC-12] x")'))
200
- expect(message).toMatch(/^acceptance-tests: /)
201
- expect(message).toContain("AC-1, AC-2")
202
- })
203
-
204
- it("does not count an ID in a non-test file", () => {
205
- expect(refusal(file("src/calc.ts", "modified", "AC-1 AC-2"))).toContain("AC-1, AC-2")
206
- })
137
+ const tagged = 'import { add } from "../src/calc.js"\nit("adds")'
207
138
 
208
139
  it("refuses a test importing a file the pass added, resolved from the test's directory", () => {
209
140
  const message = refusal(
@@ -224,17 +155,6 @@ describe("acceptanceFault", () => {
224
155
  ).toContain("acceptance-internals")
225
156
  })
226
157
 
227
- it("counts only the lines the pass added: an ID an earlier feature left does not", () => {
228
- const old = 'it("[AC-1] old")\nit("[AC-2] old")'
229
- const message = refusal({
230
- path: "tests/calc.test.ts",
231
- status: "modified",
232
- before: old,
233
- after: `${old}\nit("[AC-2] new")`,
234
- })
235
- expect(message).toContain("names AC-1 —")
236
- })
237
-
238
158
  it("refuses a bare . or .. import of an added index", () => {
239
159
  expect(
240
160
  refusal(
@@ -424,7 +344,7 @@ describe("seamBuild", () => {
424
344
  reference: "abc",
425
345
  base: "after-refactor",
426
346
  refactored: true,
427
- rebuild: { note: "Seam: a shared op", acceptance: { hash: "abc", tests: [], ids: [] } },
347
+ rebuild: { note: "Seam: a shared op", acceptance: { hash: "abc", tests: [] } },
428
348
  })
429
349
  expect(h.names.indexOf("seam.note")).toBe(h.names.indexOf("seam.judge") + 1)
430
350
  expect(h.names.indexOf("refactor.deriving")).toBe(h.names.indexOf("seam.note") + 1)
@@ -440,7 +360,7 @@ describe("seamBuild", () => {
440
360
  expect(h.outcome).toEqual({
441
361
  reference: "abc",
442
362
  base: "c-base",
443
- rebuild: { acceptance: { hash: "abc", tests: [], ids: [] } },
363
+ rebuild: { acceptance: { hash: "abc", tests: ["src/a.test.ts"] } },
444
364
  })
445
365
  expect(h.names).not.toContain("seam.by-hand")
446
366
  expect(h.names.some((n) => n.startsWith("refactor"))).toBe(false)
@@ -466,7 +386,7 @@ describe("seamBuild", () => {
466
386
  expect(h.outcome).toEqual({
467
387
  reference: "abc",
468
388
  base: "c-base",
469
- rebuild: { acceptance: { hash: "abc", tests: [], ids: [] } },
389
+ rebuild: { acceptance: { hash: "abc", tests: [] } },
470
390
  })
471
391
  })
472
392
  })
@@ -543,19 +463,17 @@ describe("implement", () => {
543
463
  reference: "abc",
544
464
  rebuild: {
545
465
  note: "# Note\n\nSeam: a shared op",
546
- acceptance: { hash: "abc", tests: ["src/a.test.ts"], ids: ["AC-1"] },
466
+ acceptance: { hash: "abc", tests: ["src/a.test.ts"] },
547
467
  },
548
- // The fixture's turn carries no test, so the acceptance guard refuses it after the prompt.
549
- }).catch(() => undefined),
468
+ }),
550
469
  )
551
470
  expect(prompt).toContain(" > Seam: a shared op")
552
- expect(prompt).toContain("acceptance tests for AC-1")
553
471
  expect(prompt).toContain("git show abc -- 'src/a.test.ts'")
554
472
  })
555
473
 
556
- it("a rebuild whose naive tests named no ID asks to carry nothing over", async () => {
474
+ it("a rebuild whose naive pass wrote no test asks to carry nothing over", async () => {
557
475
  const prompt = await building("abc", undefined, undefined, () =>
558
- implement({ reference: "abc", rebuild: { acceptance: { hash: "abc", tests: [], ids: [] } } }),
476
+ implement({ reference: "abc", rebuild: { acceptance: { hash: "abc", tests: [] } } }),
559
477
  )
560
478
  expect(prompt).not.toContain("Carry over")
561
479
  expect(prompt).not.toContain("refactor note")