@pmelab/gtd 35.0.0 → 36.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 +31 -27
  3. package/dist/chunks/{reserved-C8Fm3s72.mjs → reserved-BAv71I4i.mjs} +10 -9
  4. package/dist/chunks/{tail-oaYewBN_.mjs → tail-kar1zhOb.mjs} +1 -1
  5. package/dist/flows.mjs +15 -10
  6. package/dist/gtd.bundle.mjs +14 -9
  7. package/dist/testkit.mjs +43 -23
  8. package/dist/workflows/cleanup.mjs +2 -2
  9. package/dist/workflows/feature.mjs +18 -11
  10. package/dist/workflows/fix.mjs +2 -2
  11. package/dist/workflows/review.mjs +2 -2
  12. package/dist/workflows/rollout.mjs +1 -1
  13. package/dist/workflows.mjs +37 -25
  14. package/package.json +1 -1
  15. package/skills/authoring/SKILL.md +3 -3
  16. package/src/flows/scripts.ts +31 -9
  17. package/src/testkit/FlowScripts.test.ts +58 -0
  18. package/src/testkit/FlowScripts.ts +61 -24
  19. package/src/workflows/feature/acceptance-criteria.feature +5 -1
  20. package/src/workflows/feature/coverage.feature +6 -3
  21. package/src/workflows/feature/fast-path.feature +11 -19
  22. package/src/workflows/feature/feature.test.ts +24 -4
  23. package/src/workflows/feature/feature.ts +8 -8
  24. package/src/workflows/feature/flag-mode.feature +17 -9
  25. package/src/workflows/feature/lens-models.feature +7 -1
  26. package/src/workflows/feature/naive-acceptance.feature +2 -2
  27. package/src/workflows/feature/one-step-land.feature +10 -12
  28. package/src/workflows/feature/quality-unresolved.feature +7 -1
  29. package/src/workflows/feature/risk-tier.feature +12 -4
  30. package/src/workflows/feature/seam-rejection.feature +4 -9
  31. package/src/workflows/feature/seam.feature +29 -26
  32. package/src/workflows/feature/seam.test.ts +9 -3
  33. package/src/workflows/feature/seam.ts +40 -22
  34. package/src/workflows/review/review-door.feature +12 -4
  35. package/src/workflows/shared/text.test.ts +8 -0
  36. package/src/workflows/shared/text.ts +11 -8
@@ -1,9 +1,9 @@
1
1
  @inmem
2
- Feature: flag mode off builds a feature with no release flag, seam judge or refactor
2
+ Feature: flag mode off lands the naive pass with no release flag, seam judge or refactor
3
3
 
4
4
  `flagMode` is a factory option of the shipped workflows, on by default. Off,
5
5
  the project ships without feature flags: no seam judge and no refactor run,
6
- and the naive implementation goes straight through the health loop and the
6
+ and the naive pass itself lands, straight through the health loop and the
7
7
  quality lap.
8
8
 
9
9
  Background:
@@ -43,17 +43,22 @@ Feature: flag mode off builds a feature with no release flag, seam judge or refa
43
43
  """
44
44
  And step "design.gate.answer" changes nothing
45
45
 
46
- Scenario: flag mode off skips the seam judge and the refactor, and asks for no flag
46
+ Scenario: flag mode off lands the naive pass unjudged, and asks for no flag
47
47
  When the process is driven
48
- Then the process rests at "implement.building"
48
+ Then the process rests at "seam.naive"
49
49
  And the resting step's content contains "the quickest way that works"
50
- And the resting step's content contains "Add no release flag — this project ships without release flags"
51
- And the resting step's content does not contain "new release flag"
52
- Given step "implement.building" writes "src/calc.ts" with:
50
+ And the resting step's content does not contain "release flag"
51
+ Given step "seam.naive" writes "src/calc.test.ts" with:
52
+ """
53
+ it("[AC-1] subtracts", () => {})
54
+ """
55
+ And step "seam.naive" writes "src/calc.ts" with:
53
56
  """
54
57
  export const add = (a: number, b: number) => a + b
55
58
  export const subtract = (a: number, b: number) => a - b
56
59
  """
60
+ And step "seam.check" changes nothing
61
+ And step "implement.applying" runs its script
57
62
  And step "implement.health.check" changes nothing
58
63
  And step "implement.closing" runs its script
59
64
  And judge step "implement.coverage" answers:
@@ -64,6 +69,7 @@ Feature: flag mode off builds a feature with no release flag, seam judge or refa
64
69
  When the process is driven
65
70
  Then the process rests at "idle"
66
71
  And "src/calc.ts" contains "subtract"
72
+ And "src/calc.test.ts" contains "[AC-1] subtracts"
67
73
  And the commit subjects end with:
68
74
  """
69
75
  chore: initial commit
@@ -72,8 +78,10 @@ Feature: flag mode off builds a feature with no release flag, seam judge or refa
72
78
  gtd(check): unwind → start-gate.check
73
79
  gtd(check): start-gate.check → design.triage
74
80
  gtd(agent): design.triage → design.gate.answer
75
- gtd(human): design.gate.answer → implement.building
76
- gtd(agent): implement.building → implement.health.check
81
+ gtd(human): design.gate.answer → seam.naive
82
+ gtd(agent): seam.naive → seam.check
83
+ gtd(check): seam.check → implement.applying
84
+ gtd(check): implement.applying → implement.health.check
77
85
  gtd(check): implement.health.check → implement.coverage
78
86
  gtd(judge): implement.coverage → implement.closing
79
87
  gtd(check): implement.closing → build.quality.correctness.reviewing
@@ -37,11 +37,17 @@ Feature: each quality lens runs on its own model hint
37
37
  - AC-1: `subtract(a, b)` returns `a - b`.
38
38
  """
39
39
  And step "design.gate.answer" changes nothing
40
- And step "implement.building" writes "src/calc.ts" with:
40
+ And step "seam.naive" writes "src/calc.test.ts" with:
41
+ """
42
+ it("[AC-1] subtracts", () => {})
43
+ """
44
+ And step "seam.naive" writes "src/calc.ts" with:
41
45
  """
42
46
  export const add = (a: number, b: number) => a + b
43
47
  export const subtract = (a: number, b: number) => a - b
44
48
  """
49
+ And step "seam.check" changes nothing
50
+ And step "implement.applying" runs its script
45
51
  And step "implement.health.check" changes nothing
46
52
  And step "implement.closing" runs its script
47
53
  And judge step "implement.coverage" answers:
@@ -129,12 +129,12 @@ Feature: a naive pass ends only when a test names every criterion and the suite
129
129
  """
130
130
  And step "seam.check" from now on changes nothing
131
131
  When the process is driven
132
- Then the process rests at "implement.building"
132
+ Then the process rests at "implement.applying"
133
133
  And the commit subjects end with:
134
134
  """
135
135
  gtd(human): design.gate.answer → seam.naive
136
136
  gtd(agent): seam.naive → seam.check
137
137
  gtd(check): seam.check → seam.naive
138
138
  gtd(agent): seam.naive → seam.check
139
- gtd(check): seam.check → implement.building
139
+ gtd(check): seam.check → implement.applying
140
140
  """
@@ -1,11 +1,11 @@
1
1
  # One SDLC path per file, so a changed path reds exactly one fixture — do not merge.
2
2
  @inmem
3
- Feature: a naive pass the judge rules land goes straight to implement with no seam gate
3
+ Feature: a naive pass the judge rules land is itself the implementation
4
4
 
5
5
  With GTD_FLAG_RISK_TIER off, a naive pass that
6
6
  edits an existing source file is judged land: the process skips the approval
7
- gate and any refactor, and runs implement and quality
8
- back to idle, with no review. The flag is pinned here so the per-flag test runs never
7
+ gate and any refactor, applies the kept pass as it is — no build turn — and
8
+ runs the health loop and quality back to idle, with no review. The flag is pinned here so the per-flag test runs never
9
9
  move it off this path.
10
10
 
11
11
  Background:
@@ -53,10 +53,7 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
53
53
  """
54
54
  [{"id":"shape","answer":"land","p":0.9}]
55
55
  """
56
- And step "implement.building" writes "src/greeting.ts" with:
57
- """
58
- export const greeting = "Hello, world!"
59
- """
56
+ And step "implement.applying" runs its script
60
57
  And step "implement.health.check" changes nothing
61
58
  And step "implement.closing" runs its script
62
59
  And judge step "implement.coverage" answers:
@@ -65,10 +62,11 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
65
62
  """
66
63
  And step "build.quality.correctness.reviewing" changes nothing
67
64
 
68
- Scenario: a land judgment runs idle to idle with no seam gate or refactor
65
+ Scenario: a land judgment applies the naive pass and runs idle to idle with no rebuild
69
66
  When the process is driven
70
67
  Then the process rests at "idle"
71
- And "src/greeting.ts" contains "Hello, world!"
68
+ And "src/greeting.ts" contains "Hello, world"
69
+ And "src/acceptance.test.ts" contains "[AC-1] holds"
72
70
  And the commit subjects end with:
73
71
  """
74
72
  gtd(human): idle → unwind
@@ -78,8 +76,8 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
78
76
  gtd(human): design.gate.answer → seam.naive
79
77
  gtd(agent): seam.naive → seam.check
80
78
  gtd(check): seam.check → seam.judge
81
- gtd(judge): seam.judge → implement.building
82
- gtd(agent): implement.building → implement.health.check
79
+ gtd(judge): seam.judge → implement.applying
80
+ gtd(check): implement.applying → implement.health.check
83
81
  gtd(check): implement.health.check → implement.coverage
84
82
  gtd(judge): implement.coverage → implement.closing
85
83
  gtd(check): implement.closing → build.quality.correctness.reviewing
@@ -87,7 +85,7 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
87
85
  """
88
86
  And the commit "gtd(human): idle → unwind" has a body containing "Gtd-Var: flag_risk_tier=0"
89
87
  And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
90
- And the commit "gtd(judge): seam.judge → implement.building" has a body containing "\"answer\":\"land\""
88
+ And the commit "gtd(judge): seam.judge → implement.applying" has a body containing "\"answer\":\"land\""
91
89
  And no commit body contains "Gtd-Approved:"
92
90
  And the output lines of gtd "slices" start with:
93
91
  """
@@ -37,11 +37,17 @@ Feature: quality findings left unresolved hold a feature process short of idle
37
37
  - AC-1: subtract(a, b) returns a - b.
38
38
  """
39
39
  And step "design.gate.answer" changes nothing
40
- And step "implement.building" writes "src/calc.ts" with:
40
+ And step "seam.naive" writes "src/calc.test.ts" with:
41
+ """
42
+ it("[AC-1] subtracts", () => {})
43
+ """
44
+ And step "seam.naive" writes "src/calc.ts" with:
41
45
  """
42
46
  export const add = (a: number, b: number) => a + b
43
47
  export const subtract = (a: number, b: number) => a - b
44
48
  """
49
+ And step "seam.check" changes nothing
50
+ And step "implement.applying" runs its script
45
51
  And step "implement.health.check" changes nothing
46
52
  And step "implement.closing" runs its script
47
53
  And judge step "implement.coverage" answers:
@@ -133,7 +133,7 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
133
133
  | low |
134
134
  | high |
135
135
 
136
- Scenario: the tier comes from the requirements the final design gate lap accepted
136
+ Scenario: the tier a rebuilt pass is told comes from the requirements the final design gate lap accepted
137
137
  Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
138
138
  """
139
139
  # Requirements
@@ -146,7 +146,11 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
146
146
 
147
147
  - AC-1: the change behaves as described.
148
148
  """
149
- And step "seam.check" changes nothing
149
+ And step "seam.check" writes "src/acceptance.test.ts" with:
150
+ """
151
+ it("[AC-1] holds elsewhere", () => {})
152
+ """
153
+ And step "implement.applying" runs its script
150
154
  And step "seam.naive" writes "src/acceptance.test.ts" with:
151
155
  """
152
156
  it("[AC-1] holds", () => {})
@@ -245,7 +249,7 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
245
249
  And the resting step's content does not contain "## Production Signals"
246
250
 
247
251
 
248
- Scenario: the flag-off twin keeps today's flag demand
252
+ Scenario: the flag-off twin keeps today's flag demand on a rebuilt pass
249
253
  Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
250
254
  """
251
255
  # Requirements
@@ -259,7 +263,11 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
259
263
  - AC-1: the change behaves as described.
260
264
  """
261
265
  And step "design.gate.answer" changes nothing
262
- And step "seam.check" changes nothing
266
+ And step "seam.check" writes "src/acceptance.test.ts" with:
267
+ """
268
+ it("[AC-1] holds elsewhere", () => {})
269
+ """
270
+ And step "implement.applying" runs its script
263
271
  And step "seam.naive" writes "src/acceptance.test.ts" with:
264
272
  """
265
273
  it("[AC-1] holds", () => {})
@@ -97,12 +97,7 @@ Feature: rejected naive passes walk one fixed path through the steer gate to idl
97
97
  """
98
98
  [{"id":"shape","answer":"land","p":0.9}]
99
99
  """
100
- And step "implement.building" writes "src/calc.ts" with:
101
- """
102
- export const add = (a: number, b: number) => a + b
103
- export const subtract = (a: number, b: number) => a - b
104
- export const SUBTRACT = true
105
- """
100
+ And step "implement.applying" runs its script
106
101
  When gtd lands
107
102
  And the process is driven
108
103
  Then the process rests at "idle"
@@ -125,8 +120,8 @@ Feature: rejected naive passes walk one fixed path through the steer gate to idl
125
120
  gtd(human): seam.steer → seam.naive
126
121
  gtd(agent): seam.naive → seam.check
127
122
  gtd(check): seam.check → seam.judge
128
- gtd(judge): seam.judge → implement.building
129
- gtd(agent): implement.building → implement.health.check
123
+ gtd(judge): seam.judge → implement.applying
124
+ gtd(check): implement.applying → implement.health.check
130
125
  gtd(check): implement.health.check → implement.coverage
131
126
  gtd(judge): implement.coverage → implement.closing
132
127
  gtd(check): implement.closing → idle
@@ -135,7 +130,7 @@ Feature: rejected naive passes walk one fixed path through the steer gate to idl
135
130
  And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
136
131
  And the commit "gtd(judge): seam.judge → seam.naive" has a body containing "\"answer\":\"reject\""
137
132
  And the commit "gtd(judge): seam.judge → seam.steer" has a body containing "\"answer\":\"reject\""
138
- And the commit "gtd(judge): seam.judge → implement.building" has a body containing "\"answer\":\"land\""
133
+ And the commit "gtd(judge): seam.judge → implement.applying" has a body containing "\"answer\":\"land\""
139
134
  And no commit body contains "Gtd-Approved:"
140
135
  And the output lines of gtd "slices" start with:
141
136
  """
@@ -2,8 +2,8 @@
2
2
  Feature: the naive pass and seam judge decide how a feature is built
3
3
 
4
4
  After design a naive pass is set aside under a kept ref, the judge reads its
5
- diff, and the process either implements straight away, refactors the seam
6
- first behind a human approval, or retries the naive pass.
5
+ diff, and the process either applies the pass as the implementation,
6
+ refactors the seam first behind a human approval, or retries the naive pass.
7
7
 
8
8
  Background:
9
9
  Given the shipped workflows, as gtd install scaffolds them
@@ -50,7 +50,7 @@ Feature: the naive pass and seam judge decide how a feature is built
50
50
  """
51
51
  And step "design.gate.answer" changes nothing
52
52
 
53
- Scenario: a land judgment goes straight to the implementation with no seam gate or refactor
53
+ Scenario: a land judgment applies the naive pass with no seam gate, refactor or build turn
54
54
  Given step "seam.check" changes nothing
55
55
  And step "seam.naive" writes "src/acceptance.test.ts" with:
56
56
  """
@@ -66,7 +66,7 @@ Feature: the naive pass and seam judge decide how a feature is built
66
66
  [{"id":"shape","answer":"land","p":0.9}]
67
67
  """
68
68
  When the process is driven
69
- Then the process rests at "implement.building"
69
+ Then the process rests at "implement.applying"
70
70
  And the last commit body contains "Gtd-Judge:"
71
71
  And the commit subjects end with:
72
72
  """
@@ -79,27 +79,34 @@ Feature: the naive pass and seam judge decide how a feature is built
79
79
  gtd(human): design.gate.answer → seam.naive
80
80
  gtd(agent): seam.naive → seam.check
81
81
  gtd(check): seam.check → seam.judge
82
- gtd(judge): seam.judge → implement.building
82
+ gtd(judge): seam.judge → implement.applying
83
83
  """
84
84
 
85
- Scenario: config skills for the implement scope reach the implement step
86
- Given step "seam.check" changes nothing
87
- And step "seam.naive" writes "src/acceptance.test.ts" with:
85
+ Scenario: a naive pass that no longer applies is built fresh, with the implement scope's skills
86
+ Given step "seam.naive" writes "src/acceptance.test.ts" with:
88
87
  """
89
88
  it("[AC-1] holds", () => {})
90
89
  """
91
- And step "seam.naive" writes "src/calc.ts" with:
90
+ And step "seam.naive" writes "src/subtract.ts" with:
92
91
  """
93
- export const add = (a: number, b: number) => a + b
94
92
  export const subtract = (a: number, b: number) => a - b
95
93
  """
96
- And judge step "seam.judge" answers:
94
+ And step "seam.check" writes "src/subtract.ts" with:
97
95
  """
98
- [{"id":"shape","answer":"land","p":0.9}]
96
+ export const subtract = (a: number, b: number) => b - a
99
97
  """
98
+ And step "implement.applying" runs its script
100
99
  When the process is driven
101
100
  Then the process rests at "implement.building"
102
101
  And the resting step's content contains "my-tdd, my-runbook"
102
+ And the resting step's content contains "git show"
103
+ And ".gtd/FEEDBACK.md" contains "could not apply the naive pass"
104
+ And "src/subtract.ts" contains "b - a"
105
+ And the commit subjects end with:
106
+ """
107
+ gtd(check): seam.check → implement.applying
108
+ gtd(check): implement.applying → implement.building
109
+ """
103
110
 
104
111
  Scenario: the naive pass lands as a marker commit and its turn stays off the branch
105
112
  Given step "seam.check" changes nothing
@@ -122,7 +129,7 @@ Feature: the naive pass and seam judge decide how a feature is built
122
129
  And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
123
130
  And "src/calc.ts" does not contain "subtract"
124
131
 
125
- Scenario: the implementation is one build turn, a green check, then the requirements are closed out
132
+ Scenario: the implementation is the applied naive pass, a green check, then the requirements are closed out
126
133
  Given step "seam.check" changes nothing
127
134
  And step "seam.naive" writes "src/acceptance.test.ts" with:
128
135
  """
@@ -137,12 +144,7 @@ Feature: the naive pass and seam judge decide how a feature is built
137
144
  """
138
145
  [{"id":"shape","answer":"land","p":0.9}]
139
146
  """
140
- And step "implement.building" writes "src/calc.ts" with:
141
- """
142
- export const add = (a: number, b: number) => a + b
143
- export const subtract = (a: number, b: number) => a - b
144
- export const SUBTRACT_ENABLED = process.env.GTD_FLAG_SUBTRACT === "1"
145
- """
147
+ And step "implement.applying" runs its script
146
148
  And step "implement.health.check" changes nothing
147
149
  And step "implement.closing" runs its script
148
150
  And judge step "implement.coverage" answers:
@@ -151,10 +153,11 @@ Feature: the naive pass and seam judge decide how a feature is built
151
153
  """
152
154
  When the process is driven
153
155
  Then the process rests at "idle"
156
+ And "src/calc.ts" contains "subtract"
154
157
  And the commit subjects end with:
155
158
  """
156
- gtd(judge): seam.judge → implement.building
157
- gtd(agent): implement.building → implement.health.check
159
+ gtd(judge): seam.judge → implement.applying
160
+ gtd(check): implement.applying → implement.health.check
158
161
  gtd(check): implement.health.check → implement.coverage
159
162
  gtd(judge): implement.coverage → implement.closing
160
163
  gtd(check): implement.closing → idle
@@ -177,12 +180,12 @@ Feature: the naive pass and seam judge decide how a feature is built
177
180
  it("adds", () => expect(2 + 2).toBe(4))
178
181
  """
179
182
  When the process is driven
180
- Then the process rests at "implement.building"
183
+ Then the process rests at "implement.applying"
181
184
  And the commit subjects end with:
182
185
  """
183
186
  gtd(human): design.gate.answer → seam.naive
184
187
  gtd(agent): seam.naive → seam.check
185
- gtd(check): seam.check → implement.building
188
+ gtd(check): seam.check → implement.applying
186
189
  """
187
190
 
188
191
  Scenario: a naive pass adding a glob-named file does not hide a modified sibling
@@ -258,7 +261,7 @@ Feature: the naive pass and seam judge decide how a feature is built
258
261
  gtd(judge): seam.judge → seam.steer
259
262
  """
260
263
 
261
- Scenario: a clean landing at the steer gate overrules, with no seam gate and no refactor
264
+ Scenario: a clean landing at the steer gate overrules and applies the pass, with no seam gate and no refactor
262
265
  Given step "seam.check" changes nothing
263
266
  And step "seam.naive" writes "src/acceptance.test.ts" with:
264
267
  """
@@ -275,9 +278,9 @@ Feature: the naive pass and seam judge decide how a feature is built
275
278
  When the process is driven
276
279
  Then the process rests at "seam.steer"
277
280
  When gtd lands
278
- Then the process rests at "implement.building"
281
+ Then the process rests at "implement.applying"
279
282
  And the commit subjects end with:
280
283
  """
281
284
  gtd(judge): seam.judge → seam.steer
282
- gtd(human): seam.steer → implement.building
285
+ gtd(human): seam.steer → implement.applying
283
286
  """
@@ -321,13 +321,19 @@ const driveSeam = async (
321
321
  }
322
322
 
323
323
  describe("seamBuild", () => {
324
- it("land resolves the kept tip with the caller's base and enters no slice", async () => {
324
+ it("land resolves the kept tip to land, with the caller's base, and enters no slice", async () => {
325
325
  const h = await driveSeam("land")
326
- expect(h.outcome).toEqual({ reference: "abc", base: "c-base" })
326
+ expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
327
327
  expect(h.slices).toEqual([])
328
328
  expect(h.names).toEqual(["seam.naive", "seam.check", "seam.judge"])
329
329
  })
330
330
 
331
+ it("with flagMode off resolves a vetted pass to land with no judge", async () => {
332
+ const h = await driveSeam("reject", undefined, { flagMode: "false" })
333
+ expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
334
+ expect(h.names).toEqual(["seam.naive", "seam.check"])
335
+ })
336
+
331
337
  it("a red check retries the pass, and the judge sees it only once green", async () => {
332
338
  const h = await driveSeam("land", undefined, {}, 1)
333
339
  expect(h.names).toEqual(["seam.naive", "seam.check", "seam.naive", "seam.check", "seam.judge"])
@@ -355,7 +361,7 @@ describe("seamBuild", () => {
355
361
  "seam.clear",
356
362
  "seam.judge",
357
363
  ])
358
- expect(h.outcome).toEqual({ reference: "abc", base: "c-base" })
364
+ expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
359
365
  })
360
366
 
361
367
  it("refactor-first runs the reviewed refactor in slice refactor and resolves HEAD after it", async () => {
@@ -1,7 +1,9 @@
1
1
  import { posix } from "node:path"
2
2
  import {
3
+ applyScript,
3
4
  changes,
4
5
  check,
6
+ codeChanges,
5
7
  env,
6
8
  head,
7
9
  human,
@@ -14,6 +16,7 @@ import {
14
16
  scope,
15
17
  slice,
16
18
  vars,
19
+ wrote,
17
20
  type Change,
18
21
  type DiffShapeResult,
19
22
  type KeptTip,
@@ -144,7 +147,7 @@ const naiveGreen = (tip: KeptTip): Promise<boolean> => {
144
147
  [
145
148
  ...paths,
146
149
  "dirty=$(git -c core.quotePath=false ls-files -m -d -o --exclude-standard)",
147
- `if ! git diff --binary ${quote(`${tip.hash}^`)} ${quote(tip.hash)} -- | git apply; then`,
150
+ `if ! git diff --binary --no-color --no-ext-diff --no-textconv --src-prefix=a/ --dst-prefix=b/ ${quote(`${tip.hash}^`)} ${quote(tip.hash)} -- | git apply --whitespace=nowarn; then`,
148
151
  " echo 'gtd: the kept naive pass no longer applies to HEAD; no test ran'",
149
152
  " exit 1",
150
153
  "fi",
@@ -169,6 +172,16 @@ export interface ImplementOptions {
169
172
  readonly amend?: boolean | undefined
170
173
  /** After a refactor slice, which may already carry the feature: an empty turn completes the step. */
171
174
  readonly afterRefactor?: boolean | undefined
175
+ /** A kept naive tip to apply as the implementation; built fresh only when it no longer applies. */
176
+ readonly naive?: string | undefined
177
+ }
178
+
179
+ /** Resolves `false` when the tip no longer applies (the failure is in `.gtd/FEEDBACK.md`) or has nothing outside `.gtd/` to apply. */
180
+ const applied = async (tip: string): Promise<boolean> => {
181
+ await run("applying", applyScript(tip, FEEDBACK, t.naiveApplyFailure(tip), [".gtd"]), {
182
+ label: "Applying the naive pass",
183
+ })
184
+ return !wrote(FEEDBACK) && codeChanges().length > 0
172
185
  }
173
186
 
174
187
  export function implement(options?: ImplementOptions): Promise<void>
@@ -186,22 +199,23 @@ export function implement(
186
199
  const options: ImplementOptions =
187
200
  typeof first === "object" && first !== null ? first : { reference: first, tier, amend }
188
201
  return scope("implement", async () => {
189
- await t.agentWithSkills(
190
- "building",
191
- t.implementBuildingPrompt(
192
- options.reference,
193
- options.tier,
194
- options.amend,
195
- options.afterRefactor,
196
- flagModeOn(),
197
- ),
198
- {
199
- label: "Implementing",
200
- model: coder(),
201
- system: t.builderSystem(),
202
- ...(options.afterRefactor === true ? { allowEmpty: true } : {}),
203
- },
204
- )
202
+ if (options.naive === undefined || !(await applied(options.naive)))
203
+ await t.agentWithSkills(
204
+ "building",
205
+ t.implementBuildingPrompt(
206
+ options.reference,
207
+ options.tier,
208
+ options.amend,
209
+ options.afterRefactor,
210
+ flagModeOn(),
211
+ ),
212
+ {
213
+ label: "Implementing",
214
+ model: coder(),
215
+ system: t.builderSystem(),
216
+ ...(options.afterRefactor === true ? { allowEmpty: true } : {}),
217
+ },
218
+ )
205
219
  requireCriteriaFrozen(REQUIREMENTS)
206
220
  await green()
207
221
  await requireCoverage()
@@ -211,14 +225,14 @@ export function implement(
211
225
 
212
226
  interface Decision {
213
227
  readonly reference: string
214
- /** Present only on `refactor-first`: the refactor runs before the implementation. */
228
+ /** Absent on `land` (or with `flagMode` off, unjudged): the pass itself is the implementation. */
215
229
  readonly shape?: DiffShapeResult
216
230
  }
217
231
 
218
232
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
219
233
  const steer = async (retry: t.NaiveRetry): Promise<boolean> => {
220
234
  await human("steer", {
221
- message: t.seamSteerMessage(retry),
235
+ message: t.seamSteerMessage(retry, flagModeOn()),
222
236
  label: "Steer or overrule the naive pass",
223
237
  file: REQUIREMENTS,
224
238
  acceptClean: true,
@@ -285,15 +299,19 @@ const decide = (): Promise<Decision> =>
285
299
  for (;;) {
286
300
  await naivePass(retry)
287
301
  const tip = kept("naive")!
288
- const outcome = (await vetted(tip, vetting)) ?? (await ruled(tip, vetting.again))
302
+ const outcome =
303
+ (await vetted(tip, vetting)) ??
304
+ (flagModeOn() ? await ruled(tip, vetting.again) : { reference: tip.hash })
289
305
  if (!("kind" in outcome)) return outcome
290
306
  retry = outcome.kind === "retry" ? outcome.retry : undefined
291
307
  }
292
308
  })
293
309
 
294
310
  export interface SeamOutcome {
295
- /** The naive pass's kept tip: the first implement turn's reading material. */
311
+ /** The naive pass's kept tip: applied as the implementation when `land`, else the first implement turn's reading material. */
296
312
  readonly reference: string
313
+ /** The naive pass lands as it is: no build turn. */
314
+ readonly land?: boolean
297
315
  /** The feature slice's base: HEAD after the refactor on refactor-first, else the caller's base. */
298
316
  readonly base: string
299
317
  readonly refactored?: boolean
@@ -301,7 +319,7 @@ export interface SeamOutcome {
301
319
 
302
320
  export const seamBuild = async (base: string): Promise<SeamOutcome> => {
303
321
  const { reference, shape } = await decide()
304
- if (shape === undefined) return { reference, base }
322
+ if (shape === undefined) return { reference, base, land: true }
305
323
  if (shape.unreadable.length > 0) {
306
324
  await scope("seam", () =>
307
325
  human("by-hand", {
@@ -37,11 +37,17 @@ Feature: human review is the review door's, never the feature workflow's
37
37
  - AC-1: subtract(a, b) returns a - b.
38
38
  """
39
39
  And step "design.gate.answer" changes nothing
40
- And step "implement.building" writes "src/calc.ts" with:
40
+ And step "seam.naive" writes "src/calc.test.ts" with:
41
+ """
42
+ it("[AC-1] subtracts", () => {})
43
+ """
44
+ And step "seam.naive" writes "src/calc.ts" with:
41
45
  """
42
46
  export const add = (a: number, b: number) => a + b
43
47
  export const subtract = (a: number, b: number) => a - b
44
48
  """
49
+ And step "seam.check" changes nothing
50
+ And step "implement.applying" runs its script
45
51
  And step "implement.health.check" changes nothing
46
52
  And step "implement.closing" runs its script
47
53
  And judge step "implement.coverage" answers:
@@ -56,8 +62,10 @@ Feature: human review is the review door's, never the feature workflow's
56
62
  And ".gtd/REVIEW.md" does not exist
57
63
  And the commit subjects end with:
58
64
  """
59
- gtd(human): design.gate.answer → implement.building
60
- gtd(agent): implement.building → implement.health.check
65
+ gtd(human): design.gate.answer → seam.naive
66
+ gtd(agent): seam.naive → seam.check
67
+ gtd(check): seam.check → implement.applying
68
+ gtd(check): implement.applying → implement.health.check
61
69
  gtd(check): implement.health.check → implement.coverage
62
70
  gtd(judge): implement.coverage → implement.closing
63
71
  gtd(check): implement.closing → idle
@@ -80,7 +88,7 @@ Feature: human review is the review door's, never the feature workflow's
80
88
  - [ ] ./src/calc.ts#2-2
81
89
  new subtract function
82
90
  """
83
- When gtd opens door "review" with "HEAD~9"
91
+ When gtd opens door "review" with "HEAD~11"
84
92
  And the process is driven
85
93
  Then the process rests at "build.review.await-review"
86
94
  And the resting step's content contains the hash of the commit "feat: add calculator"
@@ -1059,6 +1059,14 @@ describe("the seam phase's texts", () => {
1059
1059
  expect(message).not.toContain("outside the expected set")
1060
1060
  })
1061
1061
 
1062
+ it("promises no judge on an overrule with flagMode off", () => {
1063
+ for (const retry of [{ red: "abc123" }, { fault: "acceptance-tests: x", hash: "abc123" }]) {
1064
+ const message = renderText(() => seamSteerMessage(retry, false))
1065
+ expect(message).not.toContain("seam judge")
1066
+ expect(message).toContain("it lands as the implementation")
1067
+ }
1068
+ })
1069
+
1062
1070
  it("names every unreadable path for a by-hand landing", () => {
1063
1071
  const message = renderText(() =>
1064
1072
  seamByHandMessage(["vendor/a.bin", "src/flows/helpers.ts"], ["vendor/a.bin"], "abc123"),