@pmelab/gtd 41.0.0 → 42.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 +38 -28
- package/dist/chunks/{reserved-Ch4LLrEj.mjs → reserved-D7MNY9QJ.mjs} +58 -14
- package/dist/chunks/{steering-DVILanVf.mjs → steering-oCDdQlpP.mjs} +1 -1
- package/dist/chunks/tail-D2o2h5cc.mjs +246 -0
- package/dist/flows.mjs +11 -1
- package/dist/gtd.bundle.mjs +12 -2
- package/dist/steering.mjs +1 -1
- package/dist/testkit.mjs +15 -3
- package/dist/web/main.js +1 -1
- package/dist/workflow-kit.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +44 -24
- package/dist/workflows/fix.mjs +3 -3
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +245 -36
- package/package.json +1 -1
- package/src/flows/scripts.ts +15 -0
- package/src/steering/review.test.ts +2 -20
- package/src/steering/review.ts +2 -1
- package/src/testkit/FlowScripts.test.ts +17 -0
- package/src/testkit/FlowScripts.ts +15 -0
- package/src/testkit/steps.ts +27 -0
- package/src/workflows/feature/coverage.feature +3 -3
- package/src/workflows/feature/fast-path.feature +2 -2
- package/src/workflows/feature/feature.test.ts +2 -37
- package/src/workflows/feature/feature.ts +12 -14
- package/src/workflows/feature/flag-mode.feature +2 -2
- package/src/workflows/feature/lens-review.feature +234 -0
- package/src/workflows/feature/one-step-land.feature +2 -2
- package/src/workflows/feature/quality-unresolved.feature +47 -32
- 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 +7 -7
- package/src/workflows/feature/refactor-review.feature +19 -100
- package/src/workflows/feature/refactor.test.ts +12 -27
- package/src/workflows/feature/refactor.ts +0 -1
- package/src/workflows/feature/review-gate.feature +6 -6
- package/src/workflows/feature/review.test.ts +29 -25
- package/src/workflows/feature/review.ts +53 -10
- package/src/workflows/feature/seam-rejection.feature +2 -2
- package/src/workflows/feature/seam.feature +2 -2
- package/src/workflows/feature/seam.test.ts +1 -1
- package/src/workflows/feature/slicing.feature +6 -6
- package/src/workflows/index.ts +1 -0
- package/src/workflows/shared/lenses.test.ts +39 -0
- package/src/workflows/shared/lenses.ts +176 -0
- package/src/workflows/shared/options.ts +1 -0
- package/src/workflows/shared/tail.ts +19 -4
- package/src/workflows/shared/text.test.ts +68 -1
- package/src/workflows/shared/text.ts +126 -13
- package/src/workflows/shared/vars.ts +1 -0
- package/dist/chunks/tail-lY1UmJrU.mjs +0 -112
|
@@ -78,7 +78,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
78
78
|
"""
|
|
79
79
|
And step "refactor.health.check" changes nothing
|
|
80
80
|
And step "refactor.closing" runs its script
|
|
81
|
-
And step "refactor.build.review.
|
|
81
|
+
And step "refactor.build.review.composing" writes ".gtd/REVIEW.md" with:
|
|
82
82
|
"""
|
|
83
83
|
# Review: abc1234
|
|
84
84
|
|
|
@@ -171,7 +171,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
171
171
|
"""
|
|
172
172
|
And step "refactor.health.check" changes nothing
|
|
173
173
|
And step "refactor.closing" runs its script
|
|
174
|
-
And step "refactor.build.review.
|
|
174
|
+
And step "refactor.build.review.composing" writes ".gtd/REVIEW.md" with:
|
|
175
175
|
"""
|
|
176
176
|
# Review: abc1234
|
|
177
177
|
|
|
@@ -191,8 +191,8 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
191
191
|
And the commit subjects end with:
|
|
192
192
|
"""
|
|
193
193
|
gtd(check): refactor.closing → refactor.build.quality.correctness.reviewing
|
|
194
|
-
gtd(agent): refactor.build.quality.correctness.reviewing → refactor.build.review.
|
|
195
|
-
gtd(
|
|
194
|
+
gtd(agent): refactor.build.quality.correctness.reviewing → refactor.build.review.composing
|
|
195
|
+
gtd(check): refactor.build.review.composing → refactor.build.review.await-review
|
|
196
196
|
"""
|
|
197
197
|
|
|
198
198
|
|
|
@@ -263,7 +263,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
263
263
|
"""
|
|
264
264
|
And step "refactor.health.check" changes nothing
|
|
265
265
|
And step "refactor.closing" runs its script
|
|
266
|
-
And step "refactor.build.review.
|
|
266
|
+
And step "refactor.build.review.composing" writes ".gtd/REVIEW.md" with:
|
|
267
267
|
"""
|
|
268
268
|
# Review: abc1234
|
|
269
269
|
|
|
@@ -345,91 +345,10 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
345
345
|
gtd(check): refactor.deriving → refactor.building
|
|
346
346
|
gtd(agent): refactor.building → refactor.health.check
|
|
347
347
|
gtd(check): refactor.health.check → refactor.closing
|
|
348
|
-
gtd(check): refactor.closing → refactor.build.review.
|
|
349
|
-
gtd(
|
|
348
|
+
gtd(check): refactor.closing → refactor.build.review.composing
|
|
349
|
+
gtd(check): refactor.build.review.composing → refactor.build.review.await-review
|
|
350
350
|
"""
|
|
351
351
|
|
|
352
|
-
Scenario: the refactor review says the whole refactor on its second round
|
|
353
|
-
Given step "refactor.build.review.reviewing" is prompted with "it covers the whole refactor since it started, on every round"
|
|
354
|
-
And step "implement.building" writes "src/calc.ts" with:
|
|
355
|
-
"""
|
|
356
|
-
export const op = (a: number, b: number, sign: number) => a + sign * b
|
|
357
|
-
export const add = (a: number, b: number) => op(a, b, 1)
|
|
358
|
-
export const subtract = (a: number, b: number) => op(a, b, -1)
|
|
359
|
-
"""
|
|
360
|
-
And step "implement.building" writes "src/acceptance.test.ts" with:
|
|
361
|
-
"""
|
|
362
|
-
it("[AC-1] holds", () => {})
|
|
363
|
-
"""
|
|
364
|
-
And step "implement.health.check" changes nothing
|
|
365
|
-
And step "implement.closing" runs its script
|
|
366
|
-
And judge step "implement.coverage" answers:
|
|
367
|
-
"""
|
|
368
|
-
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
369
|
-
"""
|
|
370
|
-
When the process is driven
|
|
371
|
-
Then the process rests at "refactor.build.review.await-review"
|
|
372
|
-
Given a file ".gtd/REVIEW.md" with:
|
|
373
|
-
"""
|
|
374
|
-
# Review: abc1234
|
|
375
|
-
|
|
376
|
-
<!-- base: 0000000 -->
|
|
377
|
-
|
|
378
|
-
## calc
|
|
379
|
-
|
|
380
|
-
- [ ] ./src/calc.ts#1-2
|
|
381
|
-
add built on the shared op[^why]
|
|
382
|
-
|
|
383
|
-
[^why]:
|
|
384
|
-
- H: name the helper `combine`
|
|
385
|
-
"""
|
|
386
|
-
And step "refactor.build.review.collecting" writes ".gtd/REFACTOR.md" with:
|
|
387
|
-
"""
|
|
388
|
-
Name the shared helper `combine`.
|
|
389
|
-
"""
|
|
390
|
-
And step "refactor.build.review.collecting" writes ".gtd/REVIEW.md" with:
|
|
391
|
-
"""
|
|
392
|
-
# Review: abc1234
|
|
393
|
-
|
|
394
|
-
<!-- base: 0000000 -->
|
|
395
|
-
|
|
396
|
-
## calc
|
|
397
|
-
|
|
398
|
-
- [ ] ./src/calc.ts#1-2
|
|
399
|
-
add built on the shared op[^why]
|
|
400
|
-
|
|
401
|
-
[^why]:
|
|
402
|
-
- H: name the helper `combine`
|
|
403
|
-
- A: noted in the refactor package.
|
|
404
|
-
"""
|
|
405
|
-
When gtd lands
|
|
406
|
-
And the process is driven
|
|
407
|
-
Then the process rests at "refactor.build.review.await-review"
|
|
408
|
-
Given a file ".gtd/REVIEW.md" with:
|
|
409
|
-
"""
|
|
410
|
-
# Review: abc1234
|
|
411
|
-
|
|
412
|
-
<!-- base: 0000000 -->
|
|
413
|
-
|
|
414
|
-
## calc
|
|
415
|
-
|
|
416
|
-
- [ ] ./src/calc.ts#1-2
|
|
417
|
-
add built on the shared op
|
|
418
|
-
"""
|
|
419
|
-
And step "refactor.re-unwind" runs its script
|
|
420
|
-
And step "refactor.deriving" runs its script
|
|
421
|
-
And step "refactor.building" writes "src/calc.ts" with:
|
|
422
|
-
"""
|
|
423
|
-
export const combine = (a: number, b: number, sign: number) => a + sign * b
|
|
424
|
-
export const add = (a: number, b: number) => combine(a, b, 1)
|
|
425
|
-
"""
|
|
426
|
-
When gtd lands
|
|
427
|
-
And the process is driven
|
|
428
|
-
Then the process rests at "refactor.build.review.await-review"
|
|
429
|
-
When gtd lands
|
|
430
|
-
And the process is driven
|
|
431
|
-
Then the process rests at "build.review.reviewing"
|
|
432
|
-
|
|
433
352
|
Scenario: a question answered at the refactor review reaches the re-derived package as ### Answered Questions
|
|
434
353
|
When the process is driven
|
|
435
354
|
Then the process rests at "refactor.build.review.await-review"
|
|
@@ -515,8 +434,8 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
515
434
|
gtd(check): refactor.deriving → refactor.building
|
|
516
435
|
gtd(agent): refactor.building → refactor.health.check
|
|
517
436
|
gtd(check): refactor.health.check → refactor.closing
|
|
518
|
-
gtd(check): refactor.closing → refactor.build.review.
|
|
519
|
-
gtd(
|
|
437
|
+
gtd(check): refactor.closing → refactor.build.review.composing
|
|
438
|
+
gtd(check): refactor.build.review.composing → refactor.build.review.await-review
|
|
520
439
|
"""
|
|
521
440
|
|
|
522
441
|
Scenario: an edit note the collecting turn leaves out refuses the landing and never signs off
|
|
@@ -727,7 +646,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
727
646
|
"""
|
|
728
647
|
When gtd lands
|
|
729
648
|
And the process is driven
|
|
730
|
-
Then the process rests at "build.review.
|
|
649
|
+
Then the process rests at "build.review.composing"
|
|
731
650
|
And "src/calc.ts" contains "subtract"
|
|
732
651
|
|
|
733
652
|
Scenario: a praise-only one-shot footnote signs off and the feature slice follows
|
|
@@ -769,7 +688,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
769
688
|
"""
|
|
770
689
|
When gtd lands
|
|
771
690
|
And the process is driven
|
|
772
|
-
Then the process rests at "build.review.
|
|
691
|
+
Then the process rests at "build.review.composing"
|
|
773
692
|
And "src/calc.ts" contains "subtract"
|
|
774
693
|
|
|
775
694
|
Scenario: a one-shot footnote judged edit that collecting leaves out refuses naming its anchor
|
|
@@ -1084,7 +1003,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1084
1003
|
"""
|
|
1085
1004
|
When gtd lands
|
|
1086
1005
|
And the process is driven
|
|
1087
|
-
Then the process rests at "build.review.
|
|
1006
|
+
Then the process rests at "build.review.composing"
|
|
1088
1007
|
And "src/calc.ts" contains "subtract"
|
|
1089
1008
|
|
|
1090
1009
|
Scenario: a praise-only refactor review still signs off and the feature slice follows
|
|
@@ -1119,7 +1038,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1119
1038
|
"""
|
|
1120
1039
|
When gtd lands
|
|
1121
1040
|
And the process is driven
|
|
1122
|
-
Then the process rests at "build.review.
|
|
1041
|
+
Then the process rests at "build.review.composing"
|
|
1123
1042
|
And "src/calc.ts" contains "subtract"
|
|
1124
1043
|
|
|
1125
1044
|
Scenario: a nit fix in the refactor slice that changes a non-stray is refused, though a glob stray widens the write access
|
|
@@ -1184,8 +1103,8 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1184
1103
|
Then the process rests at "refactor.build.review.await-review"
|
|
1185
1104
|
And the commit subjects end with:
|
|
1186
1105
|
"""
|
|
1187
|
-
gtd(check): refactor.closing → refactor.build.review.
|
|
1188
|
-
gtd(
|
|
1106
|
+
gtd(check): refactor.closing → refactor.build.review.composing
|
|
1107
|
+
gtd(check): refactor.build.review.composing → refactor.build.review.await-review
|
|
1189
1108
|
"""
|
|
1190
1109
|
|
|
1191
1110
|
Rule: with a quality lens on both slices
|
|
@@ -1256,7 +1175,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1256
1175
|
"""
|
|
1257
1176
|
And step "refactor.health.check" changes nothing
|
|
1258
1177
|
And step "refactor.closing" runs its script
|
|
1259
|
-
And step "refactor.build.review.
|
|
1178
|
+
And step "refactor.build.review.composing" writes ".gtd/REVIEW.md" with:
|
|
1260
1179
|
"""
|
|
1261
1180
|
# Review: abc1234
|
|
1262
1181
|
|
|
@@ -1309,11 +1228,11 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1309
1228
|
And the resting step's content contains the hash of the commit "gtd(check): refactor.build.review.closing → implement.building"
|
|
1310
1229
|
Given step "build.quality.correctness.reviewing" changes nothing
|
|
1311
1230
|
When the process is driven
|
|
1312
|
-
Then the process rests at "build.review.
|
|
1231
|
+
Then the process rests at "build.review.composing"
|
|
1313
1232
|
And the commit subjects end with:
|
|
1314
1233
|
"""
|
|
1315
1234
|
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
1316
|
-
gtd(agent): build.quality.correctness.reviewing → build.review.
|
|
1235
|
+
gtd(agent): build.quality.correctness.reviewing → build.review.composing
|
|
1317
1236
|
"""
|
|
1318
1237
|
|
|
1319
1238
|
Rule: a refactor package never carries a test file, and demoted headings stop at ######
|
|
@@ -1463,7 +1382,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1463
1382
|
"""
|
|
1464
1383
|
And step "refactor.health.check" changes nothing
|
|
1465
1384
|
And step "refactor.closing" runs its script
|
|
1466
|
-
And step "refactor.build.review.
|
|
1385
|
+
And step "refactor.build.review.composing" writes ".gtd/REVIEW.md" with:
|
|
1467
1386
|
"""
|
|
1468
1387
|
# Review: abc1234
|
|
1469
1388
|
|
|
@@ -781,8 +781,6 @@ interface ReviewedScript {
|
|
|
781
781
|
/** The collecting turn writes no package. */
|
|
782
782
|
readonly silentCollect?: boolean
|
|
783
783
|
readonly collected?: string
|
|
784
|
-
/** The note the first `review.reviewing` turn writes; later ones write `add`. */
|
|
785
|
-
readonly firstReviewNote?: string
|
|
786
784
|
}
|
|
787
785
|
|
|
788
786
|
/** Runs `reviewedRefactor` against a scripted context; a third await-review stops the run. */
|
|
@@ -797,7 +795,6 @@ const driveReviewed = async (
|
|
|
797
795
|
const files = new Map<string, string>()
|
|
798
796
|
let last = ""
|
|
799
797
|
let awaits = 0
|
|
800
|
-
let reviews = 0
|
|
801
798
|
let reds = script.reds ?? 0
|
|
802
799
|
let checking = false
|
|
803
800
|
let pending: boolean | Error = false
|
|
@@ -823,9 +820,7 @@ const driveReviewed = async (
|
|
|
823
820
|
...Object.fromEntries(lensEffects),
|
|
824
821
|
deriving: () => void files.set(REFACTOR_PACKAGE, "package"),
|
|
825
822
|
closing: () => void files.delete(REFACTOR_PACKAGE),
|
|
826
|
-
"review.
|
|
827
|
-
files.set(REVIEW, reviewDoc(reviews++ === 0 ? (script.firstReviewNote ?? "add") : "add"))
|
|
828
|
-
},
|
|
823
|
+
"review.composing": () => void files.set(REVIEW, reviewDoc("add")),
|
|
829
824
|
"review.closing": () => void files.delete(REVIEW),
|
|
830
825
|
"review.collecting": () => {
|
|
831
826
|
if (!script.silentCollect)
|
|
@@ -846,6 +841,14 @@ const driveReviewed = async (
|
|
|
846
841
|
if (request.kind === "restart") return Promise.resolve()
|
|
847
842
|
record(request)
|
|
848
843
|
effects[request.name]?.()
|
|
844
|
+
// Every lens finding is mechanical, so the fix turn runs.
|
|
845
|
+
if (request.kind === "judge" && request.name === "quality.triage")
|
|
846
|
+
return Promise.resolve({
|
|
847
|
+
answers: Object.fromEntries(
|
|
848
|
+
request.questions.map((q) => [q.id, { answer: "mechanical", p: 0.9 }]),
|
|
849
|
+
),
|
|
850
|
+
truncated: [],
|
|
851
|
+
})
|
|
849
852
|
return Promise.resolve(
|
|
850
853
|
request.kind === "judge" ? { answers: script.answers ?? {}, truncated: [] } : undefined,
|
|
851
854
|
)
|
|
@@ -926,7 +929,7 @@ describe("reviewedRefactor", () => {
|
|
|
926
929
|
"refactor.build.quality.correctness.reviewing",
|
|
927
930
|
"refactor.build.quality.ponytail-review.reviewing",
|
|
928
931
|
"refactor.build.quality.conventions.reviewing",
|
|
929
|
-
"refactor.build.review.
|
|
932
|
+
"refactor.build.review.composing",
|
|
930
933
|
"refactor.build.review.await-review",
|
|
931
934
|
"refactor.build.review.closing",
|
|
932
935
|
])
|
|
@@ -934,8 +937,8 @@ describe("reviewedRefactor", () => {
|
|
|
934
937
|
|
|
935
938
|
it("reviews from HEAD at scope entry", async () => {
|
|
936
939
|
const h = await driveReviewed(shape())
|
|
937
|
-
const
|
|
938
|
-
expect(
|
|
940
|
+
const composing = h.steps.find((s) => s.kind === "run" && s.name === "review.composing")
|
|
941
|
+
expect(composing?.kind === "run" ? composing.body : "").toContain("<!-- base: c0 -->")
|
|
939
942
|
})
|
|
940
943
|
|
|
941
944
|
it("names the entry commit in every lens prompt, on the first lap and after a feedback lap", async () => {
|
|
@@ -1192,10 +1195,6 @@ describe("reviewedRefactor's tail prompts", () => {
|
|
|
1192
1195
|
it.each([
|
|
1193
1196
|
["refactor.build.fix", { findings: true, reds: 2 }],
|
|
1194
1197
|
["refactor.build.fix.quality.fixing", { findings: true }],
|
|
1195
|
-
[
|
|
1196
|
-
"refactor.build.review.fix.risks.fixing",
|
|
1197
|
-
{ firstReviewNote: "Risk: drops the carry", notes: [undefined] },
|
|
1198
|
-
],
|
|
1199
1198
|
[
|
|
1200
1199
|
"refactor.build.review.fix.nits.fixing",
|
|
1201
1200
|
{ notes: ["nit"], answers: { "note-1": { answer: "nit", p: 0.9 } } },
|
|
@@ -1211,17 +1210,3 @@ describe("reviewedRefactor's tail prompts", () => {
|
|
|
1211
1210
|
},
|
|
1212
1211
|
)
|
|
1213
1212
|
})
|
|
1214
|
-
|
|
1215
|
-
describe("reviewedRefactor's review lap wording", () => {
|
|
1216
|
-
it("says the whole refactor since it started on the first and the second round", async () => {
|
|
1217
|
-
const h = await driveReviewed(shape(), { notes: ["add — rename it", undefined] })
|
|
1218
|
-
const prompts = h.steps.flatMap((s) =>
|
|
1219
|
-
s.kind === "agent" && s.name === "review.reviewing" ? [s.prompt] : [],
|
|
1220
|
-
)
|
|
1221
|
-
expect(prompts).toHaveLength(2)
|
|
1222
|
-
for (const prompt of prompts) {
|
|
1223
|
-
expect(prompt).toContain("it covers the whole refactor since it started, on every round")
|
|
1224
|
-
expect(prompt).not.toContain("only what is new since the previous review")
|
|
1225
|
-
}
|
|
1226
|
-
})
|
|
1227
|
-
})
|
|
@@ -389,7 +389,6 @@ export const reviewedRefactor = async (
|
|
|
389
389
|
fixAccess: strayAccess(p.strays),
|
|
390
390
|
lensBase: entry,
|
|
391
391
|
rules: t.refactorRules(p.strays, p.pattern),
|
|
392
|
-
lap: "refactor",
|
|
393
392
|
})
|
|
394
393
|
if (outcome.verdict === "signoff") return true
|
|
395
394
|
await reUnwind(outcome)
|
|
@@ -57,7 +57,7 @@ Feature: a feature process ends at the human review
|
|
|
57
57
|
"""
|
|
58
58
|
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
59
59
|
"""
|
|
60
|
-
And step "build.review.
|
|
60
|
+
And step "build.review.composing" writes ".gtd/REVIEW.md" with:
|
|
61
61
|
"""
|
|
62
62
|
# Review: abc1234
|
|
63
63
|
|
|
@@ -78,8 +78,8 @@ Feature: a feature process ends at the human review
|
|
|
78
78
|
And the commit subjects end with:
|
|
79
79
|
"""
|
|
80
80
|
gtd(judge): implement.coverage → implement.closing
|
|
81
|
-
gtd(check): implement.closing → build.review.
|
|
82
|
-
gtd(
|
|
81
|
+
gtd(check): implement.closing → build.review.composing
|
|
82
|
+
gtd(check): build.review.composing → build.review.await-review
|
|
83
83
|
"""
|
|
84
84
|
|
|
85
85
|
Scenario: a sign-off ends the process at idle
|
|
@@ -102,7 +102,7 @@ Feature: a feature process ends at the human review
|
|
|
102
102
|
And ".gtd/REVIEW.md" does not exist
|
|
103
103
|
And the commit subjects end with:
|
|
104
104
|
"""
|
|
105
|
-
gtd(
|
|
105
|
+
gtd(check): build.review.composing → build.review.await-review
|
|
106
106
|
gtd(human): build.review.await-review → build.review.closing
|
|
107
107
|
gtd(check): build.review.closing → idle
|
|
108
108
|
"""
|
|
@@ -214,8 +214,8 @@ Feature: a feature process ends at the human review
|
|
|
214
214
|
gtd(agent): implement.building → implement.health.check
|
|
215
215
|
gtd(check): implement.health.check → implement.coverage
|
|
216
216
|
gtd(judge): implement.coverage → implement.closing
|
|
217
|
-
gtd(check): implement.closing → build.review.
|
|
218
|
-
gtd(
|
|
217
|
+
gtd(check): implement.closing → build.review.composing
|
|
218
|
+
gtd(check): build.review.composing → build.review.await-review
|
|
219
219
|
"""
|
|
220
220
|
|
|
221
221
|
Scenario: a design numbering new behaviour at or below an earlier ID is refused
|
|
@@ -48,7 +48,7 @@ interface Drive {
|
|
|
48
48
|
readonly vars?: Readonly<Record<string, string>>
|
|
49
49
|
/** The REVIEW.md the human leaves; defaults to `notes` laid over the baseline. */
|
|
50
50
|
readonly after?: string
|
|
51
|
-
/** The REVIEW.md each `review.
|
|
51
|
+
/** The REVIEW.md each `review.composing` turn writes, in order; also raises the review-turn stop to one past the last. */
|
|
52
52
|
readonly reviewDocs?: readonly (string | undefined)[]
|
|
53
53
|
/** Let the run pass the second gate, stopping at the first range read after it. */
|
|
54
54
|
readonly passSecondGate?: boolean
|
|
@@ -95,7 +95,7 @@ const drive = async (d: Drive): Promise<Run> => {
|
|
|
95
95
|
let awaits = 0
|
|
96
96
|
let reviews = 0
|
|
97
97
|
const effects: Record<string, () => void> = {
|
|
98
|
-
"review.
|
|
98
|
+
"review.composing": () => {
|
|
99
99
|
const docs = d.reviewDocs
|
|
100
100
|
if (++reviews > (docs?.length ?? 0) + (docs === undefined ? 1 : 0)) throw new Stop()
|
|
101
101
|
const written = docs?.[reviews - 1]
|
|
@@ -116,6 +116,7 @@ const drive = async (d: Drive): Promise<Run> => {
|
|
|
116
116
|
gateMessages.push(request.options.message ?? "")
|
|
117
117
|
}
|
|
118
118
|
if (request.kind === "agent") prompts.set(request.name, request.prompt)
|
|
119
|
+
if (request.kind === "run") prompts.set(request.name, request.body)
|
|
119
120
|
if (request.kind === "judge") asked.push(...request.questions)
|
|
120
121
|
}
|
|
121
122
|
const context = fixtureContext(
|
|
@@ -184,10 +185,10 @@ describe("the risk-fix pass", () => {
|
|
|
184
185
|
it("a marked risk is fixed, kept green, re-reviewed, then rests at the gate", async () => {
|
|
185
186
|
const run = await drive({ notes: [], reviewDocs: [risky, clean] })
|
|
186
187
|
expect(run.log.slice(0, 5)).toEqual([
|
|
187
|
-
"review.
|
|
188
|
+
"review.composing",
|
|
188
189
|
"review.fix.risks.fixing",
|
|
189
190
|
"health.check",
|
|
190
|
-
"review.
|
|
191
|
+
"review.composing",
|
|
191
192
|
"review.await-review",
|
|
192
193
|
])
|
|
193
194
|
expect(run.prompts.get("review.fix.risks.fixing")).toContain("risk-1")
|
|
@@ -199,10 +200,10 @@ describe("the risk-fix pass", () => {
|
|
|
199
200
|
const run = await drive({ notes: [], reviewDocs: [risky, risky] })
|
|
200
201
|
expect(run.log.filter((n) => n === "review.fix.risks.fixing")).toHaveLength(1)
|
|
201
202
|
expect(run.log.slice(0, 5)).toEqual([
|
|
202
|
-
"review.
|
|
203
|
+
"review.composing",
|
|
203
204
|
"review.fix.risks.fixing",
|
|
204
205
|
"health.check",
|
|
205
|
-
"review.
|
|
206
|
+
"review.composing",
|
|
206
207
|
"review.await-review",
|
|
207
208
|
])
|
|
208
209
|
})
|
|
@@ -232,7 +233,7 @@ describe("the risk-fix pass", () => {
|
|
|
232
233
|
it("no marker means no fix-risks step", async () => {
|
|
233
234
|
const run = await drive({ notes: [], reviewDocs: [clean] })
|
|
234
235
|
expect(run.log).not.toContain("review.fix.risks.fixing")
|
|
235
|
-
expect(run.log[0]).toBe("review.
|
|
236
|
+
expect(run.log[0]).toBe("review.composing")
|
|
236
237
|
expect(run.log[1]).toBe("review.await-review")
|
|
237
238
|
})
|
|
238
239
|
|
|
@@ -243,16 +244,16 @@ describe("the risk-fix pass", () => {
|
|
|
243
244
|
reviewDocs: [undefined, risky, clean],
|
|
244
245
|
})
|
|
245
246
|
expect(run.log).toEqual([
|
|
246
|
-
"review.
|
|
247
|
+
"review.composing",
|
|
247
248
|
"review.await-review",
|
|
248
249
|
"review.triage",
|
|
249
250
|
"review.fix.nits.fixing",
|
|
250
251
|
"health.check",
|
|
251
252
|
"review.closing",
|
|
252
|
-
"review.
|
|
253
|
+
"review.composing",
|
|
253
254
|
"review.fix.risks.fixing",
|
|
254
255
|
"health.check",
|
|
255
|
-
"review.
|
|
256
|
+
"review.composing",
|
|
256
257
|
"review.await-review",
|
|
257
258
|
])
|
|
258
259
|
})
|
|
@@ -265,7 +266,7 @@ describe("the guard", () => {
|
|
|
265
266
|
it("runs right after the risk-fix turn", async () => {
|
|
266
267
|
const run = await drive({ notes: [], reviewDocs: [risky, clean], guarded: true })
|
|
267
268
|
expect(run.log.slice(0, 4)).toEqual([
|
|
268
|
-
"review.
|
|
269
|
+
"review.composing",
|
|
269
270
|
"review.fix.risks.fixing",
|
|
270
271
|
"guard",
|
|
271
272
|
"health.check",
|
|
@@ -443,7 +444,7 @@ describe("review verdict routing", () => {
|
|
|
443
444
|
it("an unanswered note counts as edit", async () => {
|
|
444
445
|
const run = await drive({ notes: ["add — typo", "sub", "mul"], answers: {} })
|
|
445
446
|
expect(run.log).toEqual([
|
|
446
|
-
"review.
|
|
447
|
+
"review.composing",
|
|
447
448
|
"review.await-review",
|
|
448
449
|
"review.triage",
|
|
449
450
|
"review.closing",
|
|
@@ -478,7 +479,7 @@ describe("review verdict routing", () => {
|
|
|
478
479
|
it("only praise closes and signs off with no agent turn", async () => {
|
|
479
480
|
const run = await drive({ notes, answers: { "note-1": verdict("praise") } })
|
|
480
481
|
expect(run.log).toEqual([
|
|
481
|
-
"review.
|
|
482
|
+
"review.composing",
|
|
482
483
|
"review.await-review",
|
|
483
484
|
"review.triage",
|
|
484
485
|
"review.closing",
|
|
@@ -492,7 +493,7 @@ describe("review verdict routing", () => {
|
|
|
492
493
|
answers: { "note-1": verdict("question"), "note-2": verdict("question") },
|
|
493
494
|
})
|
|
494
495
|
expect(run.log).toEqual([
|
|
495
|
-
"review.
|
|
496
|
+
"review.composing",
|
|
496
497
|
"review.await-review",
|
|
497
498
|
"review.triage",
|
|
498
499
|
"review.answer-review-questions",
|
|
@@ -508,17 +509,17 @@ describe("review verdict routing", () => {
|
|
|
508
509
|
answers: { "note-1": verdict("question"), "note-2": verdict("nit") },
|
|
509
510
|
})
|
|
510
511
|
expect(run.log).toEqual([
|
|
511
|
-
"review.
|
|
512
|
+
"review.composing",
|
|
512
513
|
"review.await-review",
|
|
513
514
|
"review.triage",
|
|
514
515
|
"review.answer-review-questions",
|
|
515
516
|
"review.fix.nits.fixing",
|
|
516
517
|
"health.check",
|
|
517
518
|
"review.closing",
|
|
518
|
-
"review.
|
|
519
|
+
"review.composing",
|
|
519
520
|
])
|
|
520
521
|
expect(run.prompts.get("review.fix.nits.fixing")).not.toContain("why?")
|
|
521
|
-
expect(run.prompts.get("review.
|
|
522
|
+
expect(run.prompts.get("review.composing")).toContain("git show 'c4:.gtd/REVIEW.md'")
|
|
522
523
|
expect(run.result).toBe("stopped")
|
|
523
524
|
})
|
|
524
525
|
|
|
@@ -527,7 +528,8 @@ describe("review verdict routing", () => {
|
|
|
527
528
|
notes: ["add — typo", "sub", "mul"],
|
|
528
529
|
answers: { "note-1": verdict("nit") },
|
|
529
530
|
})
|
|
530
|
-
expect(run.prompts.get("review.
|
|
531
|
+
expect(run.prompts.get("review.composing")).not.toContain("git show")
|
|
532
|
+
expect(run.prompts.get("review.composing")).toContain("## Fixed from your notes")
|
|
531
533
|
})
|
|
532
534
|
|
|
533
535
|
it("edits run after questions and nits, and the capture names only the edits and the answering commit", async () => {
|
|
@@ -536,7 +538,7 @@ describe("review verdict routing", () => {
|
|
|
536
538
|
answers: { "note-2": verdict("question"), "note-3": verdict("nit") },
|
|
537
539
|
})
|
|
538
540
|
expect(run.log).toEqual([
|
|
539
|
-
"review.
|
|
541
|
+
"review.composing",
|
|
540
542
|
"review.await-review",
|
|
541
543
|
"review.triage",
|
|
542
544
|
"review.answer-review-questions",
|
|
@@ -556,7 +558,7 @@ describe("review verdict routing", () => {
|
|
|
556
558
|
it("a noted round with no extractable note collects as one edit, without judging", async () => {
|
|
557
559
|
const run = await drive({ notes: [], after: BEFORE + "\nSome stray prose.\n" })
|
|
558
560
|
expect(run.log).toEqual([
|
|
559
|
-
"review.
|
|
561
|
+
"review.composing",
|
|
560
562
|
"review.await-review",
|
|
561
563
|
"review.closing",
|
|
562
564
|
"review.collecting",
|
|
@@ -679,7 +681,7 @@ describe("severity routing", () => {
|
|
|
679
681
|
answers: { "note-1": verdict("edit"), ...sev("note-1", could) },
|
|
680
682
|
})
|
|
681
683
|
expect(run.log).toEqual([
|
|
682
|
-
"review.
|
|
684
|
+
"review.composing",
|
|
683
685
|
"review.await-review",
|
|
684
686
|
"review.triage",
|
|
685
687
|
"review.defer",
|
|
@@ -771,7 +773,7 @@ describe("severity routing", () => {
|
|
|
771
773
|
passSecondGate: true,
|
|
772
774
|
})
|
|
773
775
|
expect(run.log).toEqual([
|
|
774
|
-
"review.
|
|
776
|
+
"review.composing",
|
|
775
777
|
"review.await-review",
|
|
776
778
|
"review.triage",
|
|
777
779
|
"review.answer-review-questions",
|
|
@@ -807,7 +809,7 @@ describe("BuildTailOptions.lensBase", () => {
|
|
|
807
809
|
if (request.kind === "restart") return Promise.resolve()
|
|
808
810
|
if (request.kind === "agent" && request.name.startsWith("quality."))
|
|
809
811
|
prompts.push(request.prompt)
|
|
810
|
-
if (request.name === "review.
|
|
812
|
+
if (request.name === "review.composing") throw new Stop()
|
|
811
813
|
return Promise.resolve()
|
|
812
814
|
},
|
|
813
815
|
pushScope: () => undefined,
|
|
@@ -1048,10 +1050,12 @@ describe("buildTail without rules", () => {
|
|
|
1048
1050
|
if (request.kind === "agent") prompts.set(request.name, request.prompt)
|
|
1049
1051
|
if (request.name === "health.check") checks++
|
|
1050
1052
|
if (request.name === "fix.quality.fixing") files.delete(".gtd/QUALITY.md")
|
|
1051
|
-
if (request.name === "review.
|
|
1053
|
+
if (request.name === "review.composing" || request.name === "health.stop")
|
|
1052
1054
|
throw new Stop()
|
|
1053
1055
|
return Promise.resolve(
|
|
1054
|
-
request.kind === "judge"
|
|
1056
|
+
request.kind === "judge"
|
|
1057
|
+
? { answers: { Q1: { answer: "mechanical", p: 0.9 } }, truncated: [] }
|
|
1058
|
+
: undefined,
|
|
1055
1059
|
)
|
|
1056
1060
|
},
|
|
1057
1061
|
pushScope: () => undefined,
|