@pmelab/gtd 56.0.0 → 58.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 +29 -35
- package/dist/chunks/{reserved-B8EC-WE8.mjs → reserved-BzAFXuz_.mjs} +56 -131
- package/dist/chunks/{review-HdrX0FJ7.mjs → review-CVqRS0mc.mjs} +2 -2
- package/dist/chunks/{tail-BSLah2mA.mjs → tail-JQzB21Q3.mjs} +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +99 -170
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +150 -297
- package/package.json +1 -1
- package/src/workflows/feature/acceptance-criteria.feature +32 -0
- package/src/workflows/feature/criteria-coverage.feature +0 -2
- package/src/workflows/feature/criteria.test.ts +1 -41
- package/src/workflows/feature/criteria.ts +12 -18
- package/src/workflows/feature/fast-path.feature +4 -1
- package/src/workflows/feature/feature.test.ts +3 -39
- package/src/workflows/feature/feature.ts +6 -8
- package/src/workflows/feature/lens-models.feature +0 -2
- package/src/workflows/feature/lens-review.feature +0 -2
- package/src/workflows/feature/naive-acceptance.feature +4 -0
- package/src/workflows/feature/naive-test.feature +263 -0
- package/src/workflows/feature/one-step-land.feature +4 -1
- package/src/workflows/feature/planning.ts +2 -0
- package/src/workflows/feature/refactor-absorbs.feature +4 -1
- package/src/workflows/feature/refactor-first.feature +4 -1
- package/src/workflows/feature/refactor-lens-models.feature +4 -0
- package/src/workflows/feature/refactor-rebuild.feature +4 -1
- package/src/workflows/feature/refactor-review.feature +20 -1
- package/src/workflows/feature/release-flag.feature +167 -0
- package/src/workflows/feature/release-flag.test.ts +49 -0
- package/src/workflows/feature/release-flag.ts +46 -0
- package/src/workflows/feature/review-gate.feature +1 -3
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -2
- package/src/workflows/feature/risk-tier.feature +32 -0
- package/src/workflows/feature/seam-rejection.feature +8 -1
- package/src/workflows/feature/seam.feature +8 -1
- package/src/workflows/feature/seam.test.ts +21 -9
- package/src/workflows/feature/seam.ts +60 -14
- package/src/workflows/feature/standards.feature +0 -2
- package/src/workflows/shared/options.test.ts +3 -10
- package/src/workflows/shared/options.ts +5 -6
- package/src/workflows/shared/prose.ts +0 -6
- package/src/workflows/shared/skills.test.ts +0 -4
- package/src/workflows/shared/skills.ts +0 -5
- package/src/workflows/shared/steps.ts +1 -1
- package/src/workflows/shared/text.test.ts +58 -69
- package/src/workflows/shared/text.ts +79 -162
- package/src/workflows/shared/vars.ts +2 -4
- package/src/workflows/feature/flag-mode.feature +0 -88
- package/src/workflows/feature/hands-on-skip.feature +0 -316
- package/src/workflows/feature/prototype-rounds.feature +0 -156
- package/src/workflows/feature/prototype.feature +0 -428
- package/src/workflows/feature/prototype.test.ts +0 -43
- package/src/workflows/feature/prototype.ts +0 -198
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
@inmem
|
|
2
|
-
Feature: every observable criterion is tested by hand before the quality lap
|
|
3
|
-
|
|
4
|
-
Once the suite is green, implement.prototype.observable judges which
|
|
5
|
-
criteria a person can observe, and implement.prototype.testing uses the
|
|
6
|
-
change the way a person would and writes .gtd/PROTOTYPE.md, one chunk per
|
|
7
|
-
such criterion headed `## AC-<n>: pass|fail|blocked`, plus one per problem
|
|
8
|
-
found beside them. A failure goes to implement.prototype.fix.fixing and is tested again; only a clean
|
|
9
|
-
report closes the test. Past `prototypeRounds` fix rounds, or with nothing a
|
|
10
|
-
fixer can take, a person decides at implement.prototype.open.
|
|
11
|
-
|
|
12
|
-
Background:
|
|
13
|
-
Given the shipped workflows, as gtd install scaffolds them
|
|
14
|
-
And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
|
|
15
|
-
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
16
|
-
And a file "src/calc.ts" with:
|
|
17
|
-
"""
|
|
18
|
-
export const add = (a: number, b: number) => a + b
|
|
19
|
-
"""
|
|
20
|
-
And the working tree is committed as "feat: add calculator"
|
|
21
|
-
And a file "TODO.md" with:
|
|
22
|
-
"""
|
|
23
|
-
# Add subtract
|
|
24
|
-
|
|
25
|
-
subtract(a, b) returns the difference.
|
|
26
|
-
"""
|
|
27
|
-
And step "unwind" changes nothing
|
|
28
|
-
And step "start-gate.check" changes nothing
|
|
29
|
-
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
30
|
-
"""
|
|
31
|
-
# Requirements
|
|
32
|
-
|
|
33
|
-
## Impact
|
|
34
|
-
|
|
35
|
-
- the greeting module
|
|
36
|
-
|
|
37
|
-
## Acceptance Criteria
|
|
38
|
-
|
|
39
|
-
- AC-1: subtract(a, b) returns a - b.
|
|
40
|
-
- AC-2: add is unchanged.
|
|
41
|
-
"""
|
|
42
|
-
And step "design.gate.answer" changes nothing
|
|
43
|
-
And step "seam.naive" writes "src/calc.ts" with:
|
|
44
|
-
"""
|
|
45
|
-
export const add = (a: number, b: number) => a + b
|
|
46
|
-
export const subtract = (a: number, b: number) => a + b
|
|
47
|
-
"""
|
|
48
|
-
And step "seam.naive" writes "src/calc.test.ts" with:
|
|
49
|
-
"""
|
|
50
|
-
it("[AC-1] subtracts", () => expect(subtract(3, 1)).toBe(2))
|
|
51
|
-
it("[AC-2] adds", () => expect(add(1, 1)).toBe(2))
|
|
52
|
-
"""
|
|
53
|
-
And step "seam.check" changes nothing
|
|
54
|
-
And step "implement.applying" runs its script
|
|
55
|
-
And step "implement.health.check" changes nothing
|
|
56
|
-
And judge step "implement.prototype.observable" answers:
|
|
57
|
-
"""
|
|
58
|
-
[{"id":"AC-1","answer":"yes","p":0.9},{"id":"AC-2","answer":"yes","p":0.9}]
|
|
59
|
-
"""
|
|
60
|
-
And step "implement.closing" runs its script
|
|
61
|
-
|
|
62
|
-
Scenario: the testing turn is told to use every criterion for real, in a disposable place
|
|
63
|
-
When the process is driven
|
|
64
|
-
Then the process rests at "implement.prototype.testing"
|
|
65
|
-
And the last commit subject is "gtd(judge): implement.prototype.observable → implement.prototype.testing"
|
|
66
|
-
And the resting step's content contains "Running the test suite is not this"
|
|
67
|
-
And the resting step's content contains "against the user's real data or this working tree"
|
|
68
|
-
And the resting step's content contains "`## AC-<n>: pass`"
|
|
69
|
-
|
|
70
|
-
Scenario: a clean report closes the test and moves on to the quality lap
|
|
71
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
72
|
-
"""
|
|
73
|
-
# Prototype test
|
|
74
|
-
|
|
75
|
-
## AC-1: pass
|
|
76
|
-
|
|
77
|
-
Ran `node -e 'console.log(subtract(3, 1))'` in a scratch directory; printed 2.
|
|
78
|
-
|
|
79
|
-
## AC-2: pass
|
|
80
|
-
|
|
81
|
-
Ran `node -e 'console.log(add(1, 1))'`; printed 2.
|
|
82
|
-
"""
|
|
83
|
-
When the process is driven
|
|
84
|
-
Then the process rests at "build.quality.correctness.reviewing"
|
|
85
|
-
And the commit subjects end with:
|
|
86
|
-
"""
|
|
87
|
-
gtd(judge): implement.prototype.observable → implement.prototype.testing
|
|
88
|
-
gtd(agent): implement.prototype.testing → implement.closing
|
|
89
|
-
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
90
|
-
"""
|
|
91
|
-
And ".gtd/PROTOTYPE.md" does not exist
|
|
92
|
-
|
|
93
|
-
Scenario: a failed criterion is fixed and re-tested before the quality lap
|
|
94
|
-
Given step "implement.prototype.testing", when prompted with "Find how this project", writes ".gtd/PROTOTYPE.md" with:
|
|
95
|
-
"""
|
|
96
|
-
# Prototype test
|
|
97
|
-
|
|
98
|
-
## AC-1: fail
|
|
99
|
-
|
|
100
|
-
Ran `subtract(3, 1)` in a scratch directory; printed 4. Expected 2.
|
|
101
|
-
|
|
102
|
-
## AC-2: pass
|
|
103
|
-
|
|
104
|
-
Ran `add(1, 1)`; printed 2.
|
|
105
|
-
"""
|
|
106
|
-
And step "implement.prototype.fix.fixing" writes "src/calc.ts" with:
|
|
107
|
-
"""
|
|
108
|
-
export const add = (a: number, b: number) => a + b
|
|
109
|
-
export const subtract = (a: number, b: number) => a - b
|
|
110
|
-
"""
|
|
111
|
-
And step "implement.prototype.fix.fixing" is prompted with "Read `.gtd/PROTOTYPE.md`: a hands-on test"
|
|
112
|
-
And step "implement.prototype.health.check" changes nothing
|
|
113
|
-
And step "implement.prototype.testing", when prompted with "This is a re-test", writes ".gtd/PROTOTYPE.md" with:
|
|
114
|
-
"""
|
|
115
|
-
# Prototype test
|
|
116
|
-
|
|
117
|
-
## AC-1: pass
|
|
118
|
-
|
|
119
|
-
Re-tested: `subtract(3, 1)` printed 2.
|
|
120
|
-
|
|
121
|
-
## AC-2: pass
|
|
122
|
-
|
|
123
|
-
Re-ran `add(1, 1)`; printed 2.
|
|
124
|
-
"""
|
|
125
|
-
When the process is driven
|
|
126
|
-
Then the process rests at "build.quality.correctness.reviewing"
|
|
127
|
-
And the commit subjects end with:
|
|
128
|
-
"""
|
|
129
|
-
gtd(judge): implement.prototype.observable → implement.prototype.testing
|
|
130
|
-
gtd(agent): implement.prototype.testing → implement.prototype.fix.fixing
|
|
131
|
-
gtd(agent): implement.prototype.fix.fixing → implement.prototype.health.check
|
|
132
|
-
gtd(check): implement.prototype.health.check → implement.prototype.testing
|
|
133
|
-
gtd(agent): implement.prototype.testing → implement.closing
|
|
134
|
-
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
135
|
-
"""
|
|
136
|
-
|
|
137
|
-
Scenario: a problem outside any criterion is fixed too
|
|
138
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
139
|
-
"""
|
|
140
|
-
## AC-1: pass
|
|
141
|
-
|
|
142
|
-
Printed 2.
|
|
143
|
-
|
|
144
|
-
## AC-2: pass
|
|
145
|
-
|
|
146
|
-
Printed 2.
|
|
147
|
-
|
|
148
|
-
## Problem a warning on every call: fail
|
|
149
|
-
|
|
150
|
-
Every call printed a deprecation warning.
|
|
151
|
-
"""
|
|
152
|
-
When the process is driven
|
|
153
|
-
Then the process rests at "implement.prototype.fix.fixing"
|
|
154
|
-
|
|
155
|
-
Scenario: a blocked criterion goes to a person, not a fixer
|
|
156
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
157
|
-
"""
|
|
158
|
-
## AC-1: blocked
|
|
159
|
-
|
|
160
|
-
The criterion expects a - b, but the sketch asked for the absolute difference.
|
|
161
|
-
|
|
162
|
-
## AC-2: pass
|
|
163
|
-
|
|
164
|
-
Printed 2.
|
|
165
|
-
"""
|
|
166
|
-
When the process is driven
|
|
167
|
-
Then the process rests at "implement.prototype.open"
|
|
168
|
-
And the resting step's content contains "- AC-1: blocked"
|
|
169
|
-
|
|
170
|
-
Scenario: overruling the open items goes on to the quality lap
|
|
171
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
172
|
-
"""
|
|
173
|
-
## AC-1: blocked
|
|
174
|
-
|
|
175
|
-
Could not tell.
|
|
176
|
-
|
|
177
|
-
## AC-2: pass
|
|
178
|
-
|
|
179
|
-
Printed 2.
|
|
180
|
-
"""
|
|
181
|
-
And step "implement.prototype.open" changes nothing
|
|
182
|
-
When the process is driven
|
|
183
|
-
Then the process rests at "build.quality.correctness.reviewing"
|
|
184
|
-
And the commit subjects end with:
|
|
185
|
-
"""
|
|
186
|
-
gtd(agent): implement.prototype.testing → implement.prototype.open
|
|
187
|
-
gtd(human): implement.prototype.open → implement.closing
|
|
188
|
-
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
189
|
-
"""
|
|
190
|
-
|
|
191
|
-
Scenario: with no testing instructions the step rests at a gate saying so
|
|
192
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
193
|
-
"""
|
|
194
|
-
## No testing instructions
|
|
195
|
-
|
|
196
|
-
No project skill, no manual testing section, no recognised project shape.
|
|
197
|
-
"""
|
|
198
|
-
When the process is driven
|
|
199
|
-
Then the process rests at "implement.prototype.unguided"
|
|
200
|
-
And the resting step's content contains "no way to exercise this project for real"
|
|
201
|
-
|
|
202
|
-
Scenario: a report missing a criterion is refused
|
|
203
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
204
|
-
"""
|
|
205
|
-
## AC-1: pass
|
|
206
|
-
|
|
207
|
-
Printed 2.
|
|
208
|
-
"""
|
|
209
|
-
Then driving the process is refused with "exactly one chunk headed `## <ID>: pass|fail|blocked` for AC-2"
|
|
210
|
-
|
|
211
|
-
Scenario: a testing turn that changes the working tree is refused
|
|
212
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
213
|
-
"""
|
|
214
|
-
## AC-1: pass
|
|
215
|
-
|
|
216
|
-
Printed 2.
|
|
217
|
-
|
|
218
|
-
## AC-2: pass
|
|
219
|
-
|
|
220
|
-
Printed 2.
|
|
221
|
-
"""
|
|
222
|
-
And step "implement.prototype.testing" writes "scratch.txt" with:
|
|
223
|
-
"""
|
|
224
|
-
left behind
|
|
225
|
-
"""
|
|
226
|
-
Then driving the process is refused with "change nothing but .gtd/PROTOTYPE.md"
|
|
227
|
-
|
|
228
|
-
Scenario: a fix turn may not change the acceptance criteria
|
|
229
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
230
|
-
"""
|
|
231
|
-
## AC-1: fail
|
|
232
|
-
|
|
233
|
-
Printed 4.
|
|
234
|
-
|
|
235
|
-
## AC-2: pass
|
|
236
|
-
|
|
237
|
-
Printed 2.
|
|
238
|
-
"""
|
|
239
|
-
And step "implement.prototype.fix.fixing" writes ".gtd/REQUIREMENTS.md" with:
|
|
240
|
-
"""
|
|
241
|
-
# Requirements
|
|
242
|
-
|
|
243
|
-
## Impact
|
|
244
|
-
|
|
245
|
-
- the greeting module
|
|
246
|
-
|
|
247
|
-
## Acceptance Criteria
|
|
248
|
-
|
|
249
|
-
- AC-2: add is unchanged.
|
|
250
|
-
"""
|
|
251
|
-
Then driving the process is refused with "criteria-frozen"
|
|
252
|
-
|
|
253
|
-
Scenario: a fix turn may not edit the report it fixes from
|
|
254
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
255
|
-
"""
|
|
256
|
-
## AC-1: fail
|
|
257
|
-
|
|
258
|
-
Printed 4.
|
|
259
|
-
|
|
260
|
-
## AC-2: pass
|
|
261
|
-
|
|
262
|
-
Printed 2.
|
|
263
|
-
"""
|
|
264
|
-
And step "implement.prototype.fix.fixing" writes "src/calc.ts" with:
|
|
265
|
-
"""
|
|
266
|
-
export const add = (a: number, b: number) => a + b
|
|
267
|
-
export const subtract = (a: number, b: number) => a - b
|
|
268
|
-
"""
|
|
269
|
-
And step "implement.prototype.fix.fixing" writes ".gtd/PROTOTYPE.md" with:
|
|
270
|
-
"""
|
|
271
|
-
## AC-1: pass
|
|
272
|
-
|
|
273
|
-
Fixed.
|
|
274
|
-
|
|
275
|
-
## AC-2: pass
|
|
276
|
-
|
|
277
|
-
Printed 2.
|
|
278
|
-
"""
|
|
279
|
-
Then driving the process is refused with "prototype-fix: leave .gtd/PROTOTYPE.md as it is"
|
|
280
|
-
|
|
281
|
-
Scenario: a chunk naming no criterion of the change is still an open item
|
|
282
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
283
|
-
"""
|
|
284
|
-
## AC-1: pass
|
|
285
|
-
|
|
286
|
-
Printed 2.
|
|
287
|
-
|
|
288
|
-
## AC-2: pass
|
|
289
|
-
|
|
290
|
-
Printed 2.
|
|
291
|
-
|
|
292
|
-
## AC-9: fail
|
|
293
|
-
|
|
294
|
-
No such command yet.
|
|
295
|
-
"""
|
|
296
|
-
When the process is driven
|
|
297
|
-
Then the process rests at "implement.prototype.fix.fixing"
|
|
298
|
-
|
|
299
|
-
Scenario: instructions added at the unguided gate run the suite, then a fresh test
|
|
300
|
-
Given step "implement.prototype.testing", when prompted with "Find how this project", writes ".gtd/PROTOTYPE.md" with:
|
|
301
|
-
"""
|
|
302
|
-
## No testing instructions
|
|
303
|
-
|
|
304
|
-
Nothing found.
|
|
305
|
-
"""
|
|
306
|
-
And step "implement.prototype.unguided" writes "AGENTS.md" with:
|
|
307
|
-
"""
|
|
308
|
-
## Manual testing
|
|
309
|
-
|
|
310
|
-
Run `node -e` against the calculator in a scratch directory.
|
|
311
|
-
"""
|
|
312
|
-
And step "implement.prototype.health.check" changes nothing
|
|
313
|
-
And step "implement.prototype.testing" is prompted with "Find how this project"
|
|
314
|
-
When the process is driven
|
|
315
|
-
# The fresh test still finds no way in; the gate's second visit adds nothing new and goes on.
|
|
316
|
-
Then the process rests at "build.quality.correctness.reviewing"
|
|
317
|
-
And the commit subjects end with:
|
|
318
|
-
"""
|
|
319
|
-
gtd(agent): implement.prototype.testing → implement.prototype.unguided
|
|
320
|
-
gtd(human): implement.prototype.unguided → implement.prototype.health.check
|
|
321
|
-
gtd(check): implement.prototype.health.check → implement.prototype.testing
|
|
322
|
-
gtd(agent): implement.prototype.testing → implement.prototype.unguided
|
|
323
|
-
gtd(human): implement.prototype.unguided → implement.closing
|
|
324
|
-
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
325
|
-
"""
|
|
326
|
-
|
|
327
|
-
Scenario: a clean land at the unguided gate goes on untested
|
|
328
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
329
|
-
"""
|
|
330
|
-
## No testing instructions
|
|
331
|
-
|
|
332
|
-
Nothing found.
|
|
333
|
-
"""
|
|
334
|
-
And step "implement.prototype.unguided" changes nothing
|
|
335
|
-
When the process is driven
|
|
336
|
-
Then the process rests at "build.quality.correctness.reviewing"
|
|
337
|
-
And the commit subjects end with:
|
|
338
|
-
"""
|
|
339
|
-
gtd(agent): implement.prototype.testing → implement.prototype.unguided
|
|
340
|
-
gtd(human): implement.prototype.unguided → implement.closing
|
|
341
|
-
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
342
|
-
"""
|
|
343
|
-
|
|
344
|
-
Scenario: a no-instructions chunk beside tested criteria is read as an ordinary report
|
|
345
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
346
|
-
"""
|
|
347
|
-
## AC-1: fail
|
|
348
|
-
|
|
349
|
-
Printed 4.
|
|
350
|
-
|
|
351
|
-
## AC-2: pass
|
|
352
|
-
|
|
353
|
-
Printed 2.
|
|
354
|
-
|
|
355
|
-
## No testing instructions
|
|
356
|
-
|
|
357
|
-
Found none for the edge cases; used the CLI.
|
|
358
|
-
"""
|
|
359
|
-
When the process is driven
|
|
360
|
-
Then the process rests at "implement.prototype.fix.fixing"
|
|
361
|
-
|
|
362
|
-
Scenario: a fix turn that finds nothing to fix goes on to the re-test
|
|
363
|
-
Given step "implement.prototype.testing", when prompted with "Find how this project", writes ".gtd/PROTOTYPE.md" with:
|
|
364
|
-
"""
|
|
365
|
-
## AC-1: fail
|
|
366
|
-
|
|
367
|
-
Printed 4, I think.
|
|
368
|
-
|
|
369
|
-
## AC-2: pass
|
|
370
|
-
|
|
371
|
-
Printed 2.
|
|
372
|
-
"""
|
|
373
|
-
And step "implement.prototype.fix.fixing" changes nothing
|
|
374
|
-
And step "implement.prototype.health.check" changes nothing
|
|
375
|
-
And step "implement.prototype.testing", when prompted with "This is a re-test", writes ".gtd/PROTOTYPE.md" with:
|
|
376
|
-
"""
|
|
377
|
-
## AC-1: pass
|
|
378
|
-
|
|
379
|
-
Looked again: printed 2.
|
|
380
|
-
|
|
381
|
-
## AC-2: pass
|
|
382
|
-
|
|
383
|
-
Printed 2.
|
|
384
|
-
"""
|
|
385
|
-
When the process is driven
|
|
386
|
-
Then the process rests at "build.quality.correctness.reviewing"
|
|
387
|
-
|
|
388
|
-
Scenario: a problem heading that misses the shape is refused, not dropped
|
|
389
|
-
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
390
|
-
"""
|
|
391
|
-
## AC-1: pass
|
|
392
|
-
|
|
393
|
-
Printed 2.
|
|
394
|
-
|
|
395
|
-
## AC-2: pass
|
|
396
|
-
|
|
397
|
-
Printed 2.
|
|
398
|
-
|
|
399
|
-
## Problem every call warns — fail
|
|
400
|
-
|
|
401
|
-
Each call printed a deprecation warning.
|
|
402
|
-
"""
|
|
403
|
-
Then driving the process is refused with "these miss it:"
|
|
404
|
-
|
|
405
|
-
Scenario: a re-test that finds no way to test is refused, keeping the open items in view
|
|
406
|
-
Given step "implement.prototype.testing", when prompted with "Find how this project", writes ".gtd/PROTOTYPE.md" with:
|
|
407
|
-
"""
|
|
408
|
-
## AC-1: fail
|
|
409
|
-
|
|
410
|
-
Printed 4.
|
|
411
|
-
|
|
412
|
-
## AC-2: pass
|
|
413
|
-
|
|
414
|
-
Printed 2.
|
|
415
|
-
"""
|
|
416
|
-
And step "implement.prototype.fix.fixing" writes "src/calc.ts" with:
|
|
417
|
-
"""
|
|
418
|
-
export const add = (a: number, b: number) => a + b
|
|
419
|
-
export const subtract = (a: number, b: number) => a - b
|
|
420
|
-
"""
|
|
421
|
-
And step "implement.prototype.health.check" changes nothing
|
|
422
|
-
And step "implement.prototype.testing", when prompted with "This is a re-test", writes ".gtd/PROTOTYPE.md" with:
|
|
423
|
-
"""
|
|
424
|
-
## No testing instructions
|
|
425
|
-
|
|
426
|
-
The CLI is gone.
|
|
427
|
-
"""
|
|
428
|
-
Then driving the process is refused with "needs exactly one chunk"
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { reportOf } from "./prototype.js"
|
|
3
|
-
|
|
4
|
-
describe("reportOf", () => {
|
|
5
|
-
it("reads each criterion and problem chunk with its verdict, any case", () => {
|
|
6
|
-
expect(
|
|
7
|
-
reportOf(
|
|
8
|
-
"# Prototype test\n\n## AC-1: pass\n\nok\n\n## AC-2 : FAIL\n\n## Problem double replies: blocked\n",
|
|
9
|
-
),
|
|
10
|
-
).toEqual({
|
|
11
|
-
unguided: false,
|
|
12
|
-
items: [
|
|
13
|
-
{ subject: "AC-1", verdict: "pass" },
|
|
14
|
-
{ subject: "AC-2", verdict: "fail" },
|
|
15
|
-
{ subject: "Problem double replies", verdict: "blocked" },
|
|
16
|
-
],
|
|
17
|
-
malformed: [],
|
|
18
|
-
})
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it("skips fenced lines, deeper headings and prose headings", () => {
|
|
22
|
-
expect(reportOf("```md\n## AC-1: fail\n```\n### AC-2: fail\n## Notes\n")).toEqual({
|
|
23
|
-
unguided: false,
|
|
24
|
-
items: [],
|
|
25
|
-
malformed: [],
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it("lists an item-like heading that misses the shape", () => {
|
|
30
|
-
expect(
|
|
31
|
-
reportOf("## Problem crash — fail\n## AC-2: failed\n## Problems: fail\n## AC-3: maybe\n")
|
|
32
|
-
.malformed,
|
|
33
|
-
).toEqual(["Problem crash — fail", "AC-2: failed", "Problems: fail", "AC-3: maybe"])
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it("upper-cases a criterion ID written in lower case", () => {
|
|
37
|
-
expect(reportOf("## ac-1: pass\n").items).toEqual([{ subject: "AC-1", verdict: "pass" }])
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it("reads the no-instructions chunk", () => {
|
|
41
|
-
expect(reportOf("## No testing instructions\n\nNo skill found.\n").unguided).toBe(true)
|
|
42
|
-
})
|
|
43
|
-
})
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { answered, changes, human, judge, read, refuse, scope, vars } from "../../flows/index.js"
|
|
2
|
-
import { HEADING, markFenced } from "../shared/fences.js"
|
|
3
|
-
import { coder, PROTOTYPE, REQUIREMENTS } from "../shared/steps.js"
|
|
4
|
-
import * as t from "../shared/text.js"
|
|
5
|
-
import { defaults } from "../shared/vars.js"
|
|
6
|
-
import { green } from "./green.js"
|
|
7
|
-
import { criteriaOf, criterionIds, handsOnIds, requireCriteriaFrozen } from "./criteria.js"
|
|
8
|
-
|
|
9
|
-
/** Only a `no` at this or above skips a criterion: no verdict, or an unsure one, sends it to the hands-on test. */
|
|
10
|
-
const UNOBSERVABLE_MIN_P = 0.5
|
|
11
|
-
|
|
12
|
-
/** The `prototypeRounds` setting. Anything but a non-negative integer fails rather than guess a cap. */
|
|
13
|
-
export const prototypeRounds = (): number => {
|
|
14
|
-
const value = vars.prototypeRounds ?? defaults.prototypeRounds!
|
|
15
|
-
if (!/^(0|[1-9]\d*)$/.test(value))
|
|
16
|
-
refuse(`gtd: prototypeRounds must be a non-negative integer, got ${JSON.stringify(value)}`)
|
|
17
|
-
return Number(value)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type Verdict = "pass" | "fail" | "blocked"
|
|
21
|
-
|
|
22
|
-
export interface Item {
|
|
23
|
-
/** A criterion ID, or `Problem <title>`. */
|
|
24
|
-
readonly subject: string
|
|
25
|
-
readonly verdict: Verdict
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface Report {
|
|
29
|
-
readonly unguided: boolean
|
|
30
|
-
readonly items: readonly Item[]
|
|
31
|
-
/** Headings that read as an item but miss the shape: a failure there would go unseen. */
|
|
32
|
-
readonly malformed: readonly string[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const CHUNK = /^(AC-\d+|Problem\b.*?)\s*:\s*(pass|fail|blocked)$/i
|
|
36
|
-
const UNGUIDED = /^no testing instructions$/i
|
|
37
|
-
|
|
38
|
-
/** The `## ` chunks of a prototype report; a heading in neither shape is prose, not an item. */
|
|
39
|
-
export const reportOf = (text: string): Report => {
|
|
40
|
-
let unguided = false
|
|
41
|
-
const items: Item[] = []
|
|
42
|
-
const malformed: string[] = []
|
|
43
|
-
for (const { line, fenced } of markFenced(text)) {
|
|
44
|
-
const heading = fenced ? null : HEADING.exec(line)
|
|
45
|
-
if (heading?.[1] !== "##") continue
|
|
46
|
-
if (UNGUIDED.test(heading[2]!)) unguided = true
|
|
47
|
-
const chunk = CHUNK.exec(heading[2]!)
|
|
48
|
-
if (chunk === null && /^(ac-\d|problem)/i.test(heading[2]!)) malformed.push(heading[2]!)
|
|
49
|
-
if (chunk !== null)
|
|
50
|
-
items.push({
|
|
51
|
-
subject: /^ac-/i.test(chunk[1]!) ? chunk[1]!.toUpperCase() : chunk[1]!,
|
|
52
|
-
verdict: chunk[2]!.toLowerCase() as Verdict,
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
return { unguided, items, malformed }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* The report the testing turn left, refused unless it is the only change and
|
|
60
|
-
* names each of `ids` once, with its open items: any other criterion is not
|
|
61
|
-
* this test's to fix.
|
|
62
|
-
*/
|
|
63
|
-
const requireReport = (
|
|
64
|
-
ids: readonly string[],
|
|
65
|
-
retest: boolean,
|
|
66
|
-
): { readonly unguided: boolean; readonly open: readonly Item[] } => {
|
|
67
|
-
const others = changes()
|
|
68
|
-
.map((c) => c.path)
|
|
69
|
-
.filter((path) => path !== PROTOTYPE)
|
|
70
|
-
if (others.length > 0)
|
|
71
|
-
refuse(
|
|
72
|
-
`gtd land: prototype-report: test in a disposable place and change nothing but ${PROTOTYPE} — revert these:\n${others.map((path) => ` - ${path}`).join("\n")}`,
|
|
73
|
-
)
|
|
74
|
-
const text = read(PROTOTYPE)
|
|
75
|
-
if (text === undefined) return refuse(`gtd land: prototype-report: write ${PROTOTYPE}`)
|
|
76
|
-
const report = reportOf(text)
|
|
77
|
-
if (report.malformed.length > 0)
|
|
78
|
-
refuse(
|
|
79
|
-
`gtd land: prototype-report: head each chunk \`## <ID>: pass|fail|blocked\` or \`## Problem <title>: pass|fail|blocked\` — these miss it:\n${report.malformed.map((h) => ` - ${h}`).join("\n")}`,
|
|
80
|
-
)
|
|
81
|
-
// Only a first test with nothing else in it: a chunk beside it was tested after all, and a
|
|
82
|
-
// re-test's open items must reach a person, not slip past as untested.
|
|
83
|
-
if (report.unguided && report.items.length === 0 && !retest) return { unguided: true, open: [] }
|
|
84
|
-
const elsewhere = criterionIds(read(REQUIREMENTS) ?? "").filter((id) => !ids.includes(id))
|
|
85
|
-
const count = (id: string): number => report.items.filter((i) => i.subject === id).length
|
|
86
|
-
const wrong = ids.filter((id) => count(id) !== 1)
|
|
87
|
-
if (wrong.length > 0)
|
|
88
|
-
refuse(
|
|
89
|
-
`gtd land: prototype-report: ${PROTOTYPE} needs exactly one chunk headed \`## <ID>: pass|fail|blocked\` for ${wrong.join(", ")}`,
|
|
90
|
-
)
|
|
91
|
-
// A mistyped ID stays open: only a known criterion outside `ids` is not this test's to fix.
|
|
92
|
-
const open = report.items.filter((i) => i.verdict !== "pass" && !elsewhere.includes(i.subject))
|
|
93
|
-
return { unguided: false, open }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* The criteria a person can observe by using the change, by the judge: those
|
|
98
|
-
* tagged `(no hands-on)` are never asked about.
|
|
99
|
-
*/
|
|
100
|
-
const observable = async (): Promise<string[]> => {
|
|
101
|
-
const requirements = read(REQUIREMENTS) ?? ""
|
|
102
|
-
const own = handsOnIds(requirements)
|
|
103
|
-
const criteria = (criteriaOf(requirements) ?? []).filter((c) => own.includes(c.id))
|
|
104
|
-
if (criteria.length === 0) return []
|
|
105
|
-
const { answers } = await judge("observable", {
|
|
106
|
-
questions: criteria.map(t.observableQuestion),
|
|
107
|
-
evidence: { requirements },
|
|
108
|
-
message: t.observableMessage(),
|
|
109
|
-
label: "Judging which criteria to test by hand",
|
|
110
|
-
})
|
|
111
|
-
return own.filter((id) => !answered(answers[id], "no", UNOBSERVABLE_MIN_P))
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const frozen = (): void => requireCriteriaFrozen(REQUIREMENTS)
|
|
115
|
-
|
|
116
|
-
const testing = async (
|
|
117
|
-
ids: readonly string[],
|
|
118
|
-
retest: boolean,
|
|
119
|
-
): Promise<ReturnType<typeof requireReport>> => {
|
|
120
|
-
const skipped = criterionIds(read(REQUIREMENTS) ?? "").filter((id) => !ids.includes(id))
|
|
121
|
-
await t.agentWithSkills("testing", t.prototypeTestingPrompt(skipped, retest), {
|
|
122
|
-
label: retest ? "Re-testing the change by hand" : "Testing the change by hand",
|
|
123
|
-
file: PROTOTYPE,
|
|
124
|
-
model: coder(),
|
|
125
|
-
system: t.testerSystem(),
|
|
126
|
-
// A test that saw exactly what the last round saw leaves the report as it was.
|
|
127
|
-
allowEmpty: true,
|
|
128
|
-
})
|
|
129
|
-
return requireReport(ids, retest)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const fixing = async (): Promise<void> => {
|
|
133
|
-
await scope("fix", () =>
|
|
134
|
-
t.agentWithSkills("fixing", t.prototypeFixingPrompt(), {
|
|
135
|
-
label: "Fixing what the hands-on test found",
|
|
136
|
-
file: PROTOTYPE,
|
|
137
|
-
model: coder(),
|
|
138
|
-
system: t.builderSystem(),
|
|
139
|
-
// A `fail` the tester got wrong has nothing to fix: the re-test says so, or the cap ends it.
|
|
140
|
-
allowEmpty: true,
|
|
141
|
-
}),
|
|
142
|
-
)
|
|
143
|
-
// The re-test may land empty: a report the fixer edited would pass untested.
|
|
144
|
-
if (changes().some((c) => c.path === PROTOTYPE))
|
|
145
|
-
refuse(`gtd land: prototype-fix: leave ${PROTOTYPE} as it is — the next round re-tests from it`)
|
|
146
|
-
frozen()
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
|
|
150
|
-
const askPerson = async (name: string, message: string, label: string): Promise<boolean> => {
|
|
151
|
-
await human(name, { message, label, file: PROTOTYPE, acceptClean: true })
|
|
152
|
-
// A note in the report alone tests nothing new.
|
|
153
|
-
if (!changes().some((c) => c.path !== PROTOTYPE)) return false
|
|
154
|
-
frozen()
|
|
155
|
-
return true
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Use the change by hand: a testing turn reports on every criterion a person
|
|
160
|
-
* can observe, a fix turn takes each failure, and the change is tested again —
|
|
161
|
-
* until the report is clean, or `prototypeRounds` fixes are spent and a person
|
|
162
|
-
* decides. A change with no such criterion is not tested by hand.
|
|
163
|
-
*/
|
|
164
|
-
export const prototypeTest = async (): Promise<void> => {
|
|
165
|
-
const cap = prototypeRounds()
|
|
166
|
-
if (cap === 0) return
|
|
167
|
-
await scope("prototype", async () => {
|
|
168
|
-
const ids = await observable()
|
|
169
|
-
if (ids.length === 0) return
|
|
170
|
-
let fixes = 0
|
|
171
|
-
let retest = false
|
|
172
|
-
for (;;) {
|
|
173
|
-
const { unguided, open } = await testing(ids, retest)
|
|
174
|
-
retest = !unguided
|
|
175
|
-
if (unguided) {
|
|
176
|
-
const instructed = await askPerson(
|
|
177
|
-
"unguided",
|
|
178
|
-
t.prototypeUnguidedMessage(),
|
|
179
|
-
"No way to test by hand",
|
|
180
|
-
)
|
|
181
|
-
if (!instructed) return
|
|
182
|
-
} else if (open.length === 0) return
|
|
183
|
-
else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
|
|
184
|
-
fixes++
|
|
185
|
-
await fixing()
|
|
186
|
-
} else {
|
|
187
|
-
const changed = await askPerson(
|
|
188
|
-
"open",
|
|
189
|
-
t.prototypeOpenMessage(open, fixes),
|
|
190
|
-
"Hands-on test still failing",
|
|
191
|
-
)
|
|
192
|
-
if (!changed) return
|
|
193
|
-
fixes = 0
|
|
194
|
-
}
|
|
195
|
-
await green()
|
|
196
|
-
}
|
|
197
|
-
})
|
|
198
|
-
}
|