@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.
Files changed (54) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +38 -28
  3. package/dist/chunks/{reserved-Ch4LLrEj.mjs → reserved-D7MNY9QJ.mjs} +58 -14
  4. package/dist/chunks/{steering-DVILanVf.mjs → steering-oCDdQlpP.mjs} +1 -1
  5. package/dist/chunks/tail-D2o2h5cc.mjs +246 -0
  6. package/dist/flows.mjs +11 -1
  7. package/dist/gtd.bundle.mjs +12 -2
  8. package/dist/steering.mjs +1 -1
  9. package/dist/testkit.mjs +15 -3
  10. package/dist/web/main.js +1 -1
  11. package/dist/workflow-kit.mjs +1 -1
  12. package/dist/workflows/cleanup.mjs +2 -2
  13. package/dist/workflows/feature.mjs +44 -24
  14. package/dist/workflows/fix.mjs +3 -3
  15. package/dist/workflows/review.mjs +2 -2
  16. package/dist/workflows/rollout.mjs +1 -1
  17. package/dist/workflows.mjs +245 -36
  18. package/package.json +1 -1
  19. package/src/flows/scripts.ts +15 -0
  20. package/src/steering/review.test.ts +2 -20
  21. package/src/steering/review.ts +2 -1
  22. package/src/testkit/FlowScripts.test.ts +17 -0
  23. package/src/testkit/FlowScripts.ts +15 -0
  24. package/src/testkit/steps.ts +27 -0
  25. package/src/workflows/feature/coverage.feature +3 -3
  26. package/src/workflows/feature/fast-path.feature +2 -2
  27. package/src/workflows/feature/feature.test.ts +2 -37
  28. package/src/workflows/feature/feature.ts +12 -14
  29. package/src/workflows/feature/flag-mode.feature +2 -2
  30. package/src/workflows/feature/lens-review.feature +234 -0
  31. package/src/workflows/feature/one-step-land.feature +2 -2
  32. package/src/workflows/feature/quality-unresolved.feature +47 -32
  33. package/src/workflows/feature/refactor-absorbs.feature +3 -3
  34. package/src/workflows/feature/refactor-first.feature +5 -5
  35. package/src/workflows/feature/refactor-rebuild.feature +7 -7
  36. package/src/workflows/feature/refactor-review.feature +19 -100
  37. package/src/workflows/feature/refactor.test.ts +12 -27
  38. package/src/workflows/feature/refactor.ts +0 -1
  39. package/src/workflows/feature/review-gate.feature +6 -6
  40. package/src/workflows/feature/review.test.ts +29 -25
  41. package/src/workflows/feature/review.ts +53 -10
  42. package/src/workflows/feature/seam-rejection.feature +2 -2
  43. package/src/workflows/feature/seam.feature +2 -2
  44. package/src/workflows/feature/seam.test.ts +1 -1
  45. package/src/workflows/feature/slicing.feature +6 -6
  46. package/src/workflows/index.ts +1 -0
  47. package/src/workflows/shared/lenses.test.ts +39 -0
  48. package/src/workflows/shared/lenses.ts +176 -0
  49. package/src/workflows/shared/options.ts +1 -0
  50. package/src/workflows/shared/tail.ts +19 -4
  51. package/src/workflows/shared/text.test.ts +68 -1
  52. package/src/workflows/shared/text.ts +126 -13
  53. package/src/workflows/shared/vars.ts +1 -0
  54. 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.reviewing" writes ".gtd/REVIEW.md" with:
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.reviewing" writes ".gtd/REVIEW.md" with:
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.reviewing
195
- gtd(agent): refactor.build.review.reviewing → refactor.build.review.await-review
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.reviewing" writes ".gtd/REVIEW.md" with:
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.reviewing
349
- gtd(agent): refactor.build.review.reviewing → refactor.build.review.await-review
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.reviewing
519
- gtd(agent): refactor.build.review.reviewing → refactor.build.review.await-review
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.reviewing"
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.reviewing"
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.reviewing"
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.reviewing"
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.reviewing
1188
- gtd(agent): refactor.build.review.reviewing → refactor.build.review.await-review
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.reviewing" writes ".gtd/REVIEW.md" with:
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.reviewing"
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.reviewing
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.reviewing" writes ".gtd/REVIEW.md" with:
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.reviewing": () => {
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.reviewing",
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 reviewing = h.steps.find((s) => s.kind === "agent" && s.name === "review.reviewing")
938
- expect(reviewing?.kind === "agent" ? reviewing.prompt : "").toContain("base: c0")
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.reviewing" writes ".gtd/REVIEW.md" with:
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.reviewing
82
- gtd(agent): build.review.reviewing → build.review.await-review
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(agent): build.review.reviewing → build.review.await-review
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.reviewing
218
- gtd(agent): build.review.reviewing → build.review.await-review
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.reviewing` turn writes, in order; also raises the review-turn stop to one past the last. */
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.reviewing": () => {
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.reviewing",
188
+ "review.composing",
188
189
  "review.fix.risks.fixing",
189
190
  "health.check",
190
- "review.reviewing",
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.reviewing",
203
+ "review.composing",
203
204
  "review.fix.risks.fixing",
204
205
  "health.check",
205
- "review.reviewing",
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.reviewing")
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.reviewing",
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.reviewing",
253
+ "review.composing",
253
254
  "review.fix.risks.fixing",
254
255
  "health.check",
255
- "review.reviewing",
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.reviewing",
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.reviewing",
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.reviewing",
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.reviewing",
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.reviewing",
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.reviewing",
519
+ "review.composing",
519
520
  ])
520
521
  expect(run.prompts.get("review.fix.nits.fixing")).not.toContain("why?")
521
- expect(run.prompts.get("review.reviewing")).toContain("Carry-over: commit `c4`")
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.reviewing")).not.toContain("Carry-over")
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.reviewing",
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.reviewing",
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.reviewing",
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.reviewing",
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.reviewing") throw new Stop()
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.reviewing" || request.name === "health.stop")
1053
+ if (request.name === "review.composing" || request.name === "health.stop")
1052
1054
  throw new Stop()
1053
1055
  return Promise.resolve(
1054
- request.kind === "judge" ? { answers: {}, truncated: [] } : undefined,
1056
+ request.kind === "judge"
1057
+ ? { answers: { Q1: { answer: "mechanical", p: 0.9 } }, truncated: [] }
1058
+ : undefined,
1055
1059
  )
1056
1060
  },
1057
1061
  pushScope: () => undefined,