@pmelab/gtd 38.2.0 → 40.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +66 -58
- package/dist/chunks/{reserved-C3PgP7XB.mjs → reserved-Qt5FM6Ia.mjs} +29 -10
- package/dist/chunks/{tail-Bkxki2nK.mjs → tail-tAHx_AHe.mjs} +1 -1
- package/dist/gtd.bundle.mjs +14 -5
- package/dist/testkit.mjs +14 -5
- package/dist/workflow-kit.mjs +12 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +98 -37
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +136 -45
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +3 -2
- package/src/cli/Cli.ts +4 -3
- package/src/cli/Metrics.test.ts +31 -0
- package/src/cli/Metrics.ts +8 -0
- package/src/workflow-kit/health.ts +15 -1
- package/src/workflows/cleanup/cleanup.test.ts +2 -0
- package/src/workflows/feature/coverage.feature +5 -5
- package/src/workflows/feature/criteria.test.ts +20 -1
- package/src/workflows/feature/criteria.ts +24 -0
- package/src/workflows/feature/fast-path.feature +4 -4
- package/src/workflows/feature/feature.ts +82 -22
- package/src/workflows/feature/flag-mode.feature +2 -2
- package/src/workflows/feature/one-step-land.feature +4 -4
- package/src/workflows/feature/planning.ts +3 -2
- package/src/workflows/feature/quality-unresolved.feature +6 -6
- package/src/workflows/feature/refactor-absorbs.feature +3 -3
- package/src/workflows/feature/refactor-first.feature +5 -5
- package/src/workflows/feature/refactor-rebuild.feature +2 -2
- package/src/workflows/feature/refactor-review.feature +8 -8
- package/src/workflows/feature/refactor.test.ts +4 -0
- package/src/workflows/feature/review-gate.feature +271 -0
- package/src/workflows/feature/seam-rejection.feature +4 -4
- package/src/workflows/feature/seam.feature +2 -2
- package/src/workflows/feature/seam.ts +17 -7
- package/src/workflows/feature/slicing.feature +63 -4
- package/src/workflows/rollout/rollout.feature +16 -0
- package/src/workflows/rollout/rollout.test.ts +2 -0
- package/src/workflows/shared/health.test.ts +2 -0
- package/src/workflows/shared/health.ts +34 -5
- package/src/workflows/shared/recheck.feature +60 -0
- package/src/workflows/shared/text.ts +7 -5
- package/src/workflows/review/review-door.feature +0 -121
|
@@ -4,7 +4,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
4
4
|
After a refactor-first judgment the refactor is built, quality-lapped and
|
|
5
5
|
reviewed in its own slice. Review edits re-run the refactor, never the design;
|
|
6
6
|
the feature slice that follows is quality-lapped from the refactor's
|
|
7
|
-
sign-off,
|
|
7
|
+
sign-off, then reviewed. The flags are pinned so the per-flag test runs never move it off
|
|
8
8
|
its path.
|
|
9
9
|
|
|
10
10
|
Background:
|
|
@@ -428,7 +428,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
428
428
|
Then the process rests at "refactor.build.review.await-review"
|
|
429
429
|
When gtd lands
|
|
430
430
|
And the process is driven
|
|
431
|
-
Then the process rests at "
|
|
431
|
+
Then the process rests at "build.review.reviewing"
|
|
432
432
|
|
|
433
433
|
Scenario: a question answered at the refactor review reaches the re-derived package as ### Answered Questions
|
|
434
434
|
When the process is driven
|
|
@@ -727,7 +727,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
727
727
|
"""
|
|
728
728
|
When gtd lands
|
|
729
729
|
And the process is driven
|
|
730
|
-
Then the process rests at "
|
|
730
|
+
Then the process rests at "build.review.reviewing"
|
|
731
731
|
And "src/calc.ts" contains "subtract"
|
|
732
732
|
|
|
733
733
|
Scenario: a praise-only one-shot footnote signs off and the feature slice follows
|
|
@@ -769,7 +769,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
769
769
|
"""
|
|
770
770
|
When gtd lands
|
|
771
771
|
And the process is driven
|
|
772
|
-
Then the process rests at "
|
|
772
|
+
Then the process rests at "build.review.reviewing"
|
|
773
773
|
And "src/calc.ts" contains "subtract"
|
|
774
774
|
|
|
775
775
|
Scenario: a one-shot footnote judged edit that collecting leaves out refuses naming its anchor
|
|
@@ -1084,7 +1084,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1084
1084
|
"""
|
|
1085
1085
|
When gtd lands
|
|
1086
1086
|
And the process is driven
|
|
1087
|
-
Then the process rests at "
|
|
1087
|
+
Then the process rests at "build.review.reviewing"
|
|
1088
1088
|
And "src/calc.ts" contains "subtract"
|
|
1089
1089
|
|
|
1090
1090
|
Scenario: a praise-only refactor review still signs off and the feature slice follows
|
|
@@ -1119,7 +1119,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1119
1119
|
"""
|
|
1120
1120
|
When gtd lands
|
|
1121
1121
|
And the process is driven
|
|
1122
|
-
Then the process rests at "
|
|
1122
|
+
Then the process rests at "build.review.reviewing"
|
|
1123
1123
|
And "src/calc.ts" contains "subtract"
|
|
1124
1124
|
|
|
1125
1125
|
Scenario: a nit fix in the refactor slice that changes a non-stray is refused, though a glob stray widens the write access
|
|
@@ -1309,11 +1309,11 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1309
1309
|
And the resting step's content contains the hash of the commit "gtd(check): refactor.build.review.closing → implement.building"
|
|
1310
1310
|
Given step "build.quality.correctness.reviewing" changes nothing
|
|
1311
1311
|
When the process is driven
|
|
1312
|
-
Then the process rests at "
|
|
1312
|
+
Then the process rests at "build.review.reviewing"
|
|
1313
1313
|
And the commit subjects end with:
|
|
1314
1314
|
"""
|
|
1315
1315
|
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
1316
|
-
gtd(agent): build.quality.correctness.reviewing →
|
|
1316
|
+
gtd(agent): build.quality.correctness.reviewing → build.review.reviewing
|
|
1317
1317
|
"""
|
|
1318
1318
|
|
|
1319
1319
|
Rule: a refactor package never carries a test file, and demoted headings stop at ######
|
|
@@ -436,6 +436,8 @@ const drive = async (
|
|
|
436
436
|
checks++
|
|
437
437
|
phase = "check"
|
|
438
438
|
}
|
|
439
|
+
// Re-runs the red check it follows, so it reads red as well.
|
|
440
|
+
if (request.kind === "run" && name === "health.recheck") phase = "check"
|
|
439
441
|
return Promise.resolve(
|
|
440
442
|
request.kind === "judge" ? { answers: {}, truncated: [] } : undefined,
|
|
441
443
|
)
|
|
@@ -806,6 +808,8 @@ const driveReviewed = async (
|
|
|
806
808
|
// Red checks apply to the tail's own runs, never the refactor lap's.
|
|
807
809
|
checking = request.name === "health.check" && stack.includes("build")
|
|
808
810
|
if (checking && reds > 0) reds--
|
|
811
|
+
// Re-runs the red check it follows, so it reads red as well.
|
|
812
|
+
if (request.name === "health.recheck" && stack.includes("build")) checking = true
|
|
809
813
|
}
|
|
810
814
|
const lensEffects = ["correctness", "ponytail-review", "conventions"].map(
|
|
811
815
|
(lens): [string, () => void] => [
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
@inmem
|
|
2
|
+
Feature: a feature process ends at the human review
|
|
3
|
+
|
|
4
|
+
After the last slice's quality lap the process rests at the review gate,
|
|
5
|
+
`.gtd/REVIEW.md`. A sign-off ends the process. A note asking for a change in
|
|
6
|
+
behaviour starts a feedback lap — design, then implement with no new naive
|
|
7
|
+
pass or seam judge, the coverage judge over every criterion, the quality lap
|
|
8
|
+
— and the review again. Its design keeps a built criterion's ID and numbers
|
|
9
|
+
new behaviour above every ID handed out.
|
|
10
|
+
|
|
11
|
+
Background:
|
|
12
|
+
Given the shipped workflows, as gtd install scaffolds them
|
|
13
|
+
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
14
|
+
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
15
|
+
And a file "src/calc.ts" with:
|
|
16
|
+
"""
|
|
17
|
+
export const add = (a: number, b: number) => a + b
|
|
18
|
+
"""
|
|
19
|
+
And the working tree is committed as "feat: add calculator"
|
|
20
|
+
And a file "TODO.md" with:
|
|
21
|
+
"""
|
|
22
|
+
# Add subtract
|
|
23
|
+
|
|
24
|
+
subtract(a, b) returns the difference.
|
|
25
|
+
"""
|
|
26
|
+
And step "unwind" changes nothing
|
|
27
|
+
And step "start-gate.check" changes nothing
|
|
28
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
29
|
+
"""
|
|
30
|
+
# Requirements
|
|
31
|
+
|
|
32
|
+
## Concerns
|
|
33
|
+
|
|
34
|
+
### Subtract
|
|
35
|
+
|
|
36
|
+
- `subtract(a, b)` returns `a - b`.
|
|
37
|
+
|
|
38
|
+
## Acceptance Criteria
|
|
39
|
+
|
|
40
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
41
|
+
"""
|
|
42
|
+
And step "design.gate.answer" changes nothing
|
|
43
|
+
And step "seam.naive" writes "src/calc.test.ts" with:
|
|
44
|
+
"""
|
|
45
|
+
it("[AC-1] subtracts", () => {})
|
|
46
|
+
"""
|
|
47
|
+
And step "seam.naive" writes "src/calc.ts" with:
|
|
48
|
+
"""
|
|
49
|
+
export const add = (a: number, b: number) => a + b
|
|
50
|
+
export const subtract = (a: number, b: number) => a - b
|
|
51
|
+
"""
|
|
52
|
+
And step "seam.check" changes nothing
|
|
53
|
+
And step "implement.applying" runs its script
|
|
54
|
+
And step "implement.health.check" changes nothing
|
|
55
|
+
And step "implement.closing" runs its script
|
|
56
|
+
And judge step "implement.coverage" answers:
|
|
57
|
+
"""
|
|
58
|
+
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
59
|
+
"""
|
|
60
|
+
And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
61
|
+
"""
|
|
62
|
+
# Review: abc1234
|
|
63
|
+
|
|
64
|
+
<!-- base: 0000000 -->
|
|
65
|
+
|
|
66
|
+
## calc
|
|
67
|
+
|
|
68
|
+
- [ ] ./src/calc.ts#2-2
|
|
69
|
+
new subtract function
|
|
70
|
+
"""
|
|
71
|
+
And step "build.review.closing" runs its script
|
|
72
|
+
|
|
73
|
+
Scenario: a feature process ends at the review gate
|
|
74
|
+
When the process is driven
|
|
75
|
+
Then the process rests at "build.review.await-review"
|
|
76
|
+
And ".gtd/REQUIREMENTS.md" does not exist
|
|
77
|
+
And the resting step's content contains the hash of the commit "feat: add calculator"
|
|
78
|
+
And the commit subjects end with:
|
|
79
|
+
"""
|
|
80
|
+
gtd(judge): implement.coverage → implement.closing
|
|
81
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
82
|
+
gtd(agent): build.review.reviewing → build.review.await-review
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
Scenario: a sign-off ends the process at idle
|
|
86
|
+
When the process is driven
|
|
87
|
+
Then the process rests at "build.review.await-review"
|
|
88
|
+
Given a file ".gtd/REVIEW.md" with:
|
|
89
|
+
"""
|
|
90
|
+
# Review: abc1234
|
|
91
|
+
|
|
92
|
+
<!-- base: 0000000 -->
|
|
93
|
+
|
|
94
|
+
## calc
|
|
95
|
+
|
|
96
|
+
- [x] ./src/calc.ts#2-2
|
|
97
|
+
new subtract function
|
|
98
|
+
"""
|
|
99
|
+
When gtd lands
|
|
100
|
+
And the process is driven
|
|
101
|
+
Then the process rests at "idle"
|
|
102
|
+
And ".gtd/REVIEW.md" does not exist
|
|
103
|
+
And the commit subjects end with:
|
|
104
|
+
"""
|
|
105
|
+
gtd(agent): build.review.reviewing → build.review.await-review
|
|
106
|
+
gtd(human): build.review.await-review → build.review.closing
|
|
107
|
+
gtd(check): build.review.closing → idle
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
Rule: a note asking for new behaviour runs the feedback lap
|
|
111
|
+
|
|
112
|
+
Background:
|
|
113
|
+
Given step "build.review.collecting" writes ".gtd/REQUIREMENTS.md" with:
|
|
114
|
+
"""
|
|
115
|
+
# Requirements
|
|
116
|
+
|
|
117
|
+
## Concerns
|
|
118
|
+
|
|
119
|
+
### Multiply
|
|
120
|
+
|
|
121
|
+
- Export `multiply()`.
|
|
122
|
+
"""
|
|
123
|
+
And step "build.review.collecting" writes ".gtd/REVIEW.md" with:
|
|
124
|
+
"""
|
|
125
|
+
# Review: abc1234
|
|
126
|
+
|
|
127
|
+
<!-- base: 0000000 -->
|
|
128
|
+
|
|
129
|
+
## calc
|
|
130
|
+
|
|
131
|
+
- [ ] ./src/calc.ts#2-2
|
|
132
|
+
new subtract function[^why]
|
|
133
|
+
|
|
134
|
+
[^why]:
|
|
135
|
+
- H: also export multiply
|
|
136
|
+
- A: noted in the requirements.
|
|
137
|
+
"""
|
|
138
|
+
And step "re-unwind" runs its script
|
|
139
|
+
When the process is driven
|
|
140
|
+
Then the process rests at "build.review.await-review"
|
|
141
|
+
Given a file ".gtd/REVIEW.md" with:
|
|
142
|
+
"""
|
|
143
|
+
# Review: abc1234
|
|
144
|
+
|
|
145
|
+
<!-- base: 0000000 -->
|
|
146
|
+
|
|
147
|
+
## calc
|
|
148
|
+
|
|
149
|
+
- [ ] ./src/calc.ts#2-2
|
|
150
|
+
new subtract function[^why]
|
|
151
|
+
|
|
152
|
+
[^why]:
|
|
153
|
+
- H: also export multiply
|
|
154
|
+
"""
|
|
155
|
+
When gtd lands
|
|
156
|
+
And the process is driven
|
|
157
|
+
Then the process rests at "build.review.await-review"
|
|
158
|
+
And ".gtd/REQUIREMENTS.md" contains "multiply"
|
|
159
|
+
Given a file ".gtd/REVIEW.md" with:
|
|
160
|
+
"""
|
|
161
|
+
# Review: abc1234
|
|
162
|
+
|
|
163
|
+
<!-- base: 0000000 -->
|
|
164
|
+
|
|
165
|
+
## calc
|
|
166
|
+
|
|
167
|
+
- [ ] ./src/calc.ts#2-2
|
|
168
|
+
new subtract function
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
Scenario: the lap keeps AC-1, adds AC-2, and covers both before the next review
|
|
172
|
+
Given step "design.triage" from now on changes nothing
|
|
173
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
174
|
+
"""
|
|
175
|
+
# Requirements
|
|
176
|
+
|
|
177
|
+
## Concerns
|
|
178
|
+
|
|
179
|
+
### Multiply
|
|
180
|
+
|
|
181
|
+
- `multiply(a, b)` returns `a * b`.
|
|
182
|
+
|
|
183
|
+
## Acceptance Criteria
|
|
184
|
+
|
|
185
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
186
|
+
- AC-2: multiply(a, b) returns a * b.
|
|
187
|
+
"""
|
|
188
|
+
And step "implement.building" writes "src/calc.ts" with:
|
|
189
|
+
"""
|
|
190
|
+
export const add = (a: number, b: number) => a + b
|
|
191
|
+
export const subtract = (a: number, b: number) => a - b
|
|
192
|
+
export const multiply = (a: number, b: number) => a * b
|
|
193
|
+
"""
|
|
194
|
+
And step "implement.building" writes "src/multiply.test.ts" with:
|
|
195
|
+
"""
|
|
196
|
+
it("[AC-2] multiplies", () => {})
|
|
197
|
+
"""
|
|
198
|
+
And judge step "implement.coverage" answers:
|
|
199
|
+
"""
|
|
200
|
+
[{"id":"AC-1","answer":"yes","p":0.9},{"id":"AC-2","answer":"yes","p":0.9}]
|
|
201
|
+
"""
|
|
202
|
+
When gtd lands
|
|
203
|
+
And the process is driven
|
|
204
|
+
Then the process rests at "build.review.await-review"
|
|
205
|
+
And ".gtd/REQUIREMENTS.md" does not exist
|
|
206
|
+
And the commit "gtd(judge): implement.coverage → implement.closing" has a body containing "Gtd-Mark: covers=AC-1"
|
|
207
|
+
And the commit subjects end with:
|
|
208
|
+
"""
|
|
209
|
+
gtd(human): build.review.await-review → build.review.closing
|
|
210
|
+
gtd(check): build.review.closing → re-unwind
|
|
211
|
+
gtd(check): re-unwind → design.triage
|
|
212
|
+
gtd(agent): design.triage → design.gate.answer
|
|
213
|
+
gtd(human): design.gate.answer → implement.building
|
|
214
|
+
gtd(agent): implement.building → implement.health.check
|
|
215
|
+
gtd(check): implement.health.check → implement.coverage
|
|
216
|
+
gtd(judge): implement.coverage → implement.closing
|
|
217
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
218
|
+
gtd(agent): build.review.reviewing → build.review.await-review
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
Scenario: a design numbering new behaviour at or below an earlier ID is refused
|
|
222
|
+
Given step "design.triage" from now on changes nothing
|
|
223
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
224
|
+
"""
|
|
225
|
+
# Requirements
|
|
226
|
+
|
|
227
|
+
## Concerns
|
|
228
|
+
|
|
229
|
+
### Multiply
|
|
230
|
+
|
|
231
|
+
- `multiply(a, b)` returns `a * b`.
|
|
232
|
+
|
|
233
|
+
## Acceptance Criteria
|
|
234
|
+
|
|
235
|
+
- AC-0: multiply(a, b) returns a * b.
|
|
236
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
237
|
+
"""
|
|
238
|
+
When gtd lands
|
|
239
|
+
Then driving the process is refused with "criteria-ids"
|
|
240
|
+
|
|
241
|
+
Scenario: the lap holds a new criterion to coverage
|
|
242
|
+
Given step "design.triage" from now on changes nothing
|
|
243
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
244
|
+
"""
|
|
245
|
+
# Requirements
|
|
246
|
+
|
|
247
|
+
## Concerns
|
|
248
|
+
|
|
249
|
+
### Multiply
|
|
250
|
+
|
|
251
|
+
- `multiply(a, b)` returns `a * b`.
|
|
252
|
+
|
|
253
|
+
## Acceptance Criteria
|
|
254
|
+
|
|
255
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
256
|
+
- AC-2: multiply(a, b) returns a * b.
|
|
257
|
+
"""
|
|
258
|
+
And step "implement.building" writes "src/calc.ts" with:
|
|
259
|
+
"""
|
|
260
|
+
export const add = (a: number, b: number) => a + b
|
|
261
|
+
export const subtract = (a: number, b: number) => a - b
|
|
262
|
+
export const multiply = (a: number, b: number) => a * b
|
|
263
|
+
"""
|
|
264
|
+
And judge step "implement.coverage" answers:
|
|
265
|
+
"""
|
|
266
|
+
[{"id":"AC-1","answer":"yes","p":0.9},{"id":"AC-2","answer":"no","p":0.9}]
|
|
267
|
+
"""
|
|
268
|
+
When gtd lands
|
|
269
|
+
And the process is driven
|
|
270
|
+
Then the process rests at "implement.covering"
|
|
271
|
+
And the resting step's content contains "AC-2"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# One SDLC path per file, so a changed path reds exactly one fixture — do not merge.
|
|
2
2
|
@inmem
|
|
3
|
-
Feature: rejected naive passes walk one fixed path through the steer gate to
|
|
3
|
+
Feature: rejected naive passes walk one fixed path through the steer gate to the review gate
|
|
4
4
|
|
|
5
5
|
With GTD_FLAG_RISK_TIER off, three rejected naive
|
|
6
6
|
passes rest at the steer gate, where a human edit restarts the seam and the
|
|
@@ -40,7 +40,7 @@ Feature: rejected naive passes walk one fixed path through the steer gate to idl
|
|
|
40
40
|
"""
|
|
41
41
|
And step "design.gate.answer" changes nothing
|
|
42
42
|
|
|
43
|
-
Scenario: three rejects rest at the steer gate, a steered rerun lands and runs to
|
|
43
|
+
Scenario: three rejects rest at the steer gate, a steered rerun lands and runs to the review gate
|
|
44
44
|
Given step "seam.check" changes nothing
|
|
45
45
|
And step "seam.naive" writes "src/acceptance.test.ts" with:
|
|
46
46
|
"""
|
|
@@ -100,7 +100,7 @@ Feature: rejected naive passes walk one fixed path through the steer gate to idl
|
|
|
100
100
|
And step "implement.applying" runs its script
|
|
101
101
|
When gtd lands
|
|
102
102
|
And the process is driven
|
|
103
|
-
Then the process rests at "
|
|
103
|
+
Then the process rests at "build.review.reviewing"
|
|
104
104
|
And the commit subjects end with:
|
|
105
105
|
"""
|
|
106
106
|
gtd(human): idle → unwind
|
|
@@ -124,7 +124,7 @@ Feature: rejected naive passes walk one fixed path through the steer gate to idl
|
|
|
124
124
|
gtd(check): implement.applying → implement.health.check
|
|
125
125
|
gtd(check): implement.health.check → implement.coverage
|
|
126
126
|
gtd(judge): implement.coverage → implement.closing
|
|
127
|
-
gtd(check): implement.closing →
|
|
127
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
128
128
|
"""
|
|
129
129
|
And the commit "gtd(human): idle → unwind" has a body containing "Gtd-Var: flag_risk_tier=0"
|
|
130
130
|
And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
|
|
@@ -152,7 +152,7 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
152
152
|
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
153
153
|
"""
|
|
154
154
|
When the process is driven
|
|
155
|
-
Then the process rests at "
|
|
155
|
+
Then the process rests at "build.review.reviewing"
|
|
156
156
|
And "src/calc.ts" contains "subtract"
|
|
157
157
|
And the commit subjects end with:
|
|
158
158
|
"""
|
|
@@ -160,7 +160,7 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
160
160
|
gtd(check): implement.applying → implement.health.check
|
|
161
161
|
gtd(check): implement.health.check → implement.coverage
|
|
162
162
|
gtd(judge): implement.coverage → implement.closing
|
|
163
|
-
gtd(check): implement.closing →
|
|
163
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
164
164
|
"""
|
|
165
165
|
And ".gtd/REQUIREMENTS.md" does not exist
|
|
166
166
|
|
|
@@ -283,7 +283,7 @@ export interface ImplementOptions {
|
|
|
283
283
|
/** Where the slice began: the coverage judge reads only the tests changed since. */
|
|
284
284
|
readonly base?: string | undefined
|
|
285
285
|
/** Not the last slice: `.gtd/REQUIREMENTS.md` and `.gtd/SLICES.md` stay for the next one. */
|
|
286
|
-
readonly more?: boolean | undefined
|
|
286
|
+
readonly more?: boolean | (() => boolean) | undefined
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
/** Resolves `false` when the tip no longer applies (the failure is in `.gtd/FEEDBACK.md`) or has nothing outside `.gtd/` to apply. */
|
|
@@ -351,14 +351,19 @@ export function implement(
|
|
|
351
351
|
tier?: RiskTier,
|
|
352
352
|
amend?: boolean,
|
|
353
353
|
): Promise<void>
|
|
354
|
-
export function implement(
|
|
354
|
+
export async function implement(
|
|
355
355
|
first?: string | ImplementOptions,
|
|
356
356
|
tier?: RiskTier,
|
|
357
357
|
amend?: boolean,
|
|
358
358
|
): Promise<void> {
|
|
359
|
-
|
|
360
|
-
typeof first === "object" && first !== null ? first : { reference: first, tier, amend }
|
|
361
|
-
|
|
359
|
+
await implementing(
|
|
360
|
+
typeof first === "object" && first !== null ? first : { reference: first, tier, amend },
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** `implement`, resolving with the criterion IDs the requirements listed when it closed. */
|
|
365
|
+
export const implementing = (options: ImplementOptions): Promise<readonly string[]> =>
|
|
366
|
+
scope("implement", async () => {
|
|
362
367
|
const since = head()
|
|
363
368
|
const acceptance = options.rebuild?.acceptance
|
|
364
369
|
const built = options.naive === undefined || !(await applied(options.naive))
|
|
@@ -372,9 +377,14 @@ export function implement(
|
|
|
372
377
|
built && acceptance !== undefined ? () => requireAcceptanceKept(acceptance) : undefined,
|
|
373
378
|
)
|
|
374
379
|
await requireCoverage(options.base, options.slice)
|
|
375
|
-
|
|
380
|
+
const ids = criterionIds(read(REQUIREMENTS) ?? "")
|
|
381
|
+
await closing(moreToBuild(options.more))
|
|
382
|
+
return ids
|
|
376
383
|
})
|
|
377
|
-
|
|
384
|
+
|
|
385
|
+
// Read at closing: a narrowing at `implement.oversized` may have emptied every later slice.
|
|
386
|
+
const moreToBuild = (more: ImplementOptions["more"]): boolean =>
|
|
387
|
+
typeof more === "function" ? more() : more === true
|
|
378
388
|
|
|
379
389
|
/** The last slice's closing, for a last slice a steer left with no criterion to build. */
|
|
380
390
|
export const closeOut = (): Promise<void> => scope("implement", () => closing(false))
|
|
@@ -51,7 +51,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
51
51
|
[{"id":"AC-1","answer":"yes","p":0.9},{"id":"AC-2","answer":"yes","p":0.9}]
|
|
52
52
|
"""
|
|
53
53
|
When the process is driven
|
|
54
|
-
Then the process rests at "
|
|
54
|
+
Then the process rests at "build.review.reviewing"
|
|
55
55
|
And the commit subjects end with:
|
|
56
56
|
"""
|
|
57
57
|
gtd(human): design.gate.answer → seam.naive
|
|
@@ -60,7 +60,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
60
60
|
gtd(check): implement.applying → implement.health.check
|
|
61
61
|
gtd(check): implement.health.check → implement.coverage
|
|
62
62
|
gtd(judge): implement.coverage → implement.closing
|
|
63
|
-
gtd(check): implement.closing →
|
|
63
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
64
64
|
"""
|
|
65
65
|
And the output lines of gtd "slices" start with:
|
|
66
66
|
"""
|
|
@@ -112,7 +112,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
112
112
|
[{"id":"AC-2","answer":"yes","p":0.9}]
|
|
113
113
|
"""
|
|
114
114
|
When the process is driven
|
|
115
|
-
Then the process rests at "
|
|
115
|
+
Then the process rests at "build.review.reviewing"
|
|
116
116
|
And the commit subjects end with:
|
|
117
117
|
"""
|
|
118
118
|
gtd(agent): design.triage → design.gate.answer
|
|
@@ -129,7 +129,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
129
129
|
gtd(check): implement.applying → implement.health.check
|
|
130
130
|
gtd(check): implement.health.check → implement.coverage
|
|
131
131
|
gtd(judge): implement.coverage → implement.closing
|
|
132
|
-
gtd(check): implement.closing →
|
|
132
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
133
133
|
"""
|
|
134
134
|
And the output lines of gtd "slices" start with:
|
|
135
135
|
"""
|
|
@@ -223,3 +223,62 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
223
223
|
- AC-2
|
|
224
224
|
"""
|
|
225
225
|
Then driving the process is refused with "wrote outside its write access"
|
|
226
|
+
|
|
227
|
+
Scenario: a steer that empties the last slice still ends at the review of the whole change
|
|
228
|
+
Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
229
|
+
"""
|
|
230
|
+
# Requirements
|
|
231
|
+
|
|
232
|
+
## Acceptance Criteria
|
|
233
|
+
|
|
234
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
235
|
+
- AC-2: multiply(a, b) returns a * b.
|
|
236
|
+
- AC-3: divide(a, b) returns a / b.
|
|
237
|
+
"""
|
|
238
|
+
And step "design.slicing" writes ".gtd/SLICES.md" with:
|
|
239
|
+
"""
|
|
240
|
+
# Slices
|
|
241
|
+
|
|
242
|
+
## Subtract and divide
|
|
243
|
+
|
|
244
|
+
- AC-1
|
|
245
|
+
- AC-3
|
|
246
|
+
|
|
247
|
+
## Multiply
|
|
248
|
+
|
|
249
|
+
- AC-2
|
|
250
|
+
"""
|
|
251
|
+
And step "seam.naive" writes "src/sub.test.ts" with:
|
|
252
|
+
"""
|
|
253
|
+
it("[AC-1] subtracts", () => {})
|
|
254
|
+
"""
|
|
255
|
+
And judge step "implement.coverage" answers:
|
|
256
|
+
"""
|
|
257
|
+
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
258
|
+
"""
|
|
259
|
+
When the process is driven
|
|
260
|
+
Then the process rests at "seam.steer"
|
|
261
|
+
Given a file ".gtd/REQUIREMENTS.md" with:
|
|
262
|
+
"""
|
|
263
|
+
# Requirements
|
|
264
|
+
|
|
265
|
+
## Acceptance Criteria
|
|
266
|
+
|
|
267
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
268
|
+
"""
|
|
269
|
+
When gtd lands
|
|
270
|
+
And the process is driven
|
|
271
|
+
Then the process rests at "build.review.reviewing"
|
|
272
|
+
And the commit subjects end with:
|
|
273
|
+
"""
|
|
274
|
+
gtd(human): seam.steer → seam.naive
|
|
275
|
+
gtd(agent): seam.naive → seam.check
|
|
276
|
+
gtd(check): seam.check → implement.applying
|
|
277
|
+
gtd(check): implement.applying → implement.health.check
|
|
278
|
+
gtd(check): implement.health.check → implement.coverage
|
|
279
|
+
gtd(judge): implement.coverage → implement.closing
|
|
280
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
281
|
+
"""
|
|
282
|
+
And ".gtd/REQUIREMENTS.md" does not exist
|
|
283
|
+
And ".gtd/SLICES.md" does not exist
|
|
284
|
+
And the output of gtd "slices" does not contain "feature-2"
|
|
@@ -157,6 +157,10 @@ Feature: rollout turns one release flag default-on and touches nothing else
|
|
|
157
157
|
"""
|
|
158
158
|
1 test failing
|
|
159
159
|
"""
|
|
160
|
+
And step "rollout.suite.recheck" writes ".gtd/FEEDBACK.md" with:
|
|
161
|
+
"""
|
|
162
|
+
1 test still failing
|
|
163
|
+
"""
|
|
160
164
|
When gtd starts workflow "rollout" with "--var flag=alpha"
|
|
161
165
|
And the process is driven
|
|
162
166
|
Then the process rests at "rollout.suite.blocked"
|
|
@@ -176,6 +180,10 @@ Feature: rollout turns one release flag default-on and touches nothing else
|
|
|
176
180
|
"""
|
|
177
181
|
1 test failing
|
|
178
182
|
"""
|
|
183
|
+
And step "rollout.suite.recheck" writes ".gtd/FEEDBACK.md" with:
|
|
184
|
+
"""
|
|
185
|
+
1 test still failing
|
|
186
|
+
"""
|
|
179
187
|
When gtd starts workflow "rollout" with "--var flag=alpha"
|
|
180
188
|
And the process is driven
|
|
181
189
|
Then the process rests at "rollout.suite.blocked"
|
|
@@ -200,6 +208,10 @@ Feature: rollout turns one release flag default-on and touches nothing else
|
|
|
200
208
|
"""
|
|
201
209
|
1 test failing
|
|
202
210
|
"""
|
|
211
|
+
And step "rollout.suite.recheck" writes ".gtd/FEEDBACK.md" with:
|
|
212
|
+
"""
|
|
213
|
+
1 test still failing
|
|
214
|
+
"""
|
|
203
215
|
When gtd starts workflow "rollout" with "--var flag=alpha"
|
|
204
216
|
And the process is driven
|
|
205
217
|
Then the process rests at "rollout.suite.blocked"
|
|
@@ -229,6 +241,10 @@ Feature: rollout turns one release flag default-on and touches nothing else
|
|
|
229
241
|
"""
|
|
230
242
|
1 test failing
|
|
231
243
|
"""
|
|
244
|
+
And step "rollout.suite.recheck" writes ".gtd/FEEDBACK.md" with:
|
|
245
|
+
"""
|
|
246
|
+
1 test still failing
|
|
247
|
+
"""
|
|
232
248
|
When gtd starts workflow "rollout" with "--var flag=alpha"
|
|
233
249
|
And the process is driven
|
|
234
250
|
Then the process rests at "rollout.suite.blocked"
|
|
@@ -114,6 +114,7 @@ describe("the rollout workflow", () => {
|
|
|
114
114
|
const rest = await repo.drive({
|
|
115
115
|
"rollout.flipping": (_, r) => r.write("src/flags/index.ts", FLIPPED),
|
|
116
116
|
"rollout.suite.check": (_, r) => r.write(".gtd/FEEDBACK.md", "1 failing\n"),
|
|
117
|
+
"rollout.suite.recheck": (_, r) => r.write(".gtd/FEEDBACK.md", "still 1 failing\n"),
|
|
117
118
|
})
|
|
118
119
|
expect(rest).toMatchObject({ state: "rollout.suite.blocked", actor: "human" })
|
|
119
120
|
})
|
|
@@ -125,6 +126,7 @@ describe("the rollout workflow", () => {
|
|
|
125
126
|
await repo.drive({
|
|
126
127
|
"rollout.flipping": (_, r) => r.write("src/flags/index.ts", FLIPPED),
|
|
127
128
|
"rollout.suite.check": (_, r) => r.write(".gtd/FEEDBACK.md", "1 failing\n"),
|
|
129
|
+
"rollout.suite.recheck": (_, r) => r.write(".gtd/FEEDBACK.md", "still 1 failing\n"),
|
|
128
130
|
})
|
|
129
131
|
return repo
|
|
130
132
|
}
|
|
@@ -27,6 +27,7 @@ describe("gate guard", () => {
|
|
|
27
27
|
const red = {
|
|
28
28
|
first: (_: unknown, r: W) => r.write("f", "1\n"),
|
|
29
29
|
"suite.check": (_: unknown, r: W) => r.write(".gtd/FEEDBACK.md", "red\n"),
|
|
30
|
+
"suite.recheck": (_: unknown, r: W) => r.write(".gtd/FEEDBACK.md", "red again\n"),
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
it("runs the guard after a blocked landing and lets its refusal stop it", async () => {
|
|
@@ -66,6 +67,7 @@ describe("healthy describeGuard", () => {
|
|
|
66
67
|
let runs = 0
|
|
67
68
|
const handlers = (extra: Record<string, unknown>) => ({
|
|
68
69
|
"health.check": (_: unknown, r: W) => r.write(".gtd/FEEDBACK.md", `red ${runs++}\n`),
|
|
70
|
+
"health.recheck": (_: unknown, r: W) => r.write(".gtd/FEEDBACK.md", `red ${runs++}\n`),
|
|
69
71
|
"health.judge": () => '[{"id":"verdict","answer":"progress","p":0.9}]',
|
|
70
72
|
fix: (_: unknown, r: W) => r.write("f", `${runs}\n`),
|
|
71
73
|
first: (_: unknown, r: W) => r.write("start", "1\n"),
|