@pmelab/gtd 52.1.0 → 54.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 (94) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +91 -129
  3. package/claude/hooks/drive.ts +0 -3
  4. package/claude/hooks/register.tsx +3 -68
  5. package/claude/hooks/ship.ts +85 -1595
  6. package/dist/chunks/{reserved-DxB5CROj.mjs → reserved-CQ7amcKG.mjs} +45 -132
  7. package/dist/chunks/{review-CKMSdvgW.mjs → review-CXzUXJHL.mjs} +12 -5
  8. package/dist/chunks/{tail-QZc7Uz2U.mjs → tail-JZ9PAOwp.mjs} +2 -14
  9. package/dist/chunks/{wire-BJuZWR3C.mjs → wire-C12dahrH.mjs} +2 -130
  10. package/dist/flows.mjs +1 -16
  11. package/dist/gtd.bundle.mjs +476 -982
  12. package/dist/testkit.mjs +58 -417
  13. package/dist/wire.mjs +2 -2
  14. package/dist/workflows/cleanup.mjs +2 -2
  15. package/dist/workflows/feature.mjs +112 -363
  16. package/dist/workflows/fix.mjs +2 -2
  17. package/dist/workflows/review.mjs +3 -3
  18. package/dist/workflows/rollout.mjs +1 -1
  19. package/dist/workflows.mjs +321 -677
  20. package/package.json +1 -1
  21. package/src/cli/Cli.test.ts +1 -86
  22. package/src/cli/Cli.ts +13 -133
  23. package/src/cli/Metrics.test.ts +21 -31
  24. package/src/cli/Metrics.ts +25 -36
  25. package/src/cli/Program.test.ts +1 -35
  26. package/src/cli/Program.ts +5 -64
  27. package/src/cli/ToolCommands.ts +0 -3
  28. package/src/edge/Edge.test.ts +20 -322
  29. package/src/edge/Edge.ts +10 -181
  30. package/src/edge/index.ts +0 -1
  31. package/src/flows/runtime.test.ts +0 -27
  32. package/src/flows/runtime.ts +1 -49
  33. package/src/install/Install.ts +15 -24
  34. package/src/replay/Replay.test.ts +0 -169
  35. package/src/replay/Replay.ts +4 -93
  36. package/src/replay/index.ts +0 -1
  37. package/src/testkit/Layers.ts +0 -1
  38. package/src/testkit/steps.ts +23 -0
  39. package/src/wire/BeatDocument.ts +2 -12
  40. package/src/wire/BeatStatus.ts +0 -3
  41. package/src/wire/WireSchema.test.ts +0 -18
  42. package/src/wire/index.ts +0 -2
  43. package/src/wire/types.ts +0 -7
  44. package/src/workflow/Workflow.ts +1 -9
  45. package/src/workflows/feature/criteria.test.ts +1 -14
  46. package/src/workflows/feature/criteria.ts +6 -38
  47. package/src/workflows/feature/feature.ts +34 -118
  48. package/src/workflows/feature/hands-on-skip.feature +2 -70
  49. package/src/workflows/feature/index.ts +0 -1
  50. package/src/workflows/feature/lens-review.feature +50 -0
  51. package/src/workflows/feature/one-step-land.feature +0 -4
  52. package/src/workflows/feature/prototype-rounds.feature +1 -1
  53. package/src/workflows/feature/prototype.feature +3 -3
  54. package/src/workflows/feature/prototype.ts +18 -29
  55. package/src/workflows/feature/refactor-absorbs.feature +3 -8
  56. package/src/workflows/feature/refactor-first.feature +0 -5
  57. package/src/workflows/feature/refactor-rebuild.feature +0 -6
  58. package/src/workflows/feature/refactor-review.feature +49 -11
  59. package/src/workflows/feature/refactor.ts +30 -64
  60. package/src/workflows/feature/review-gate.feature +1 -1
  61. package/src/workflows/feature/reviewer-walkthrough.feature +0 -31
  62. package/src/workflows/feature/seam-rejection.feature +0 -4
  63. package/src/workflows/feature/seam.test.ts +7 -26
  64. package/src/workflows/feature/seam.ts +52 -165
  65. package/src/workflows/index.ts +0 -1
  66. package/src/workflows/shared/lenses.ts +0 -13
  67. package/src/workflows/shared/options.test.ts +2 -11
  68. package/src/workflows/shared/options.ts +4 -21
  69. package/src/workflows/shared/review.test.ts +3 -3
  70. package/src/workflows/shared/review.ts +12 -2
  71. package/src/workflows/shared/steps.ts +0 -2
  72. package/src/workflows/shared/text.test.ts +10 -80
  73. package/src/workflows/shared/text.ts +35 -211
  74. package/src/workflows/shared/vars.ts +2 -8
  75. package/src/workflows/shared/workflows.steps.ts +0 -13
  76. package/claude/hooks/context.ts +0 -66
  77. package/src/replay/Slices.test.ts +0 -73
  78. package/src/replay/Slices.ts +0 -45
  79. package/src/wire/ContextLimit.fixture.ts +0 -30
  80. package/src/wire/ContextLimit.test.ts +0 -96
  81. package/src/wire/ContextLimit.ts +0 -169
  82. package/src/wire/Slices.ts +0 -25
  83. package/src/wire/golden/1/slices.json +0 -11
  84. package/src/workflows/feature/context-limit.feature +0 -37
  85. package/src/workflows/feature/continuation-cap.feature +0 -69
  86. package/src/workflows/feature/continuation-rebuild.feature +0 -92
  87. package/src/workflows/feature/continuation-slices.feature +0 -68
  88. package/src/workflows/feature/continuation.feature +0 -209
  89. package/src/workflows/feature/slice-findings.feature +0 -256
  90. package/src/workflows/feature/slicing-cap.feature +0 -23
  91. package/src/workflows/feature/slicing.feature +0 -297
  92. package/src/workflows/feature/slicing.test.ts +0 -49
  93. package/src/workflows/feature/slicing.ts +0 -105
  94. package/src/workflows/shared/continuation.ts +0 -92
@@ -5,14 +5,7 @@ import * as t from "../shared/text.js"
5
5
  import { defaults } from "../shared/vars.js"
6
6
  import type { Guard } from "../shared/health.js"
7
7
  import { green } from "./green.js"
8
- import {
9
- criteriaOf,
10
- criterionIds,
11
- handsOnIds,
12
- requireCriteriaFrozen,
13
- sliceIds,
14
- type CriteriaSlice,
15
- } from "./criteria.js"
8
+ import { criteriaOf, criterionIds, handsOnIds, requireCriteriaFrozen } from "./criteria.js"
16
9
 
17
10
  /** Only a `no` at this or above skips a criterion: no verdict, or an unsure one, sends it to the hands-on test. */
18
11
  const UNOBSERVABLE_MIN_P = 0.5
@@ -102,12 +95,12 @@ const requireReport = (
102
95
  }
103
96
 
104
97
  /**
105
- * The slice's criteria a person can observe by using the change, by the
106
- * judge: those tagged `(no hands-on)` are never asked about.
98
+ * The criteria a person can observe by using the change, by the judge: those
99
+ * tagged `(no hands-on)` are never asked about.
107
100
  */
108
- const observable = async (slice: CriteriaSlice | undefined): Promise<string[]> => {
101
+ const observable = async (): Promise<string[]> => {
109
102
  const requirements = read(REQUIREMENTS) ?? ""
110
- const own = handsOnIds(requirements, slice)
103
+ const own = handsOnIds(requirements)
111
104
  const criteria = (criteriaOf(requirements) ?? []).filter((c) => own.includes(c.id))
112
105
  if (criteria.length === 0) return []
113
106
  const { answers } = await judge("observable", {
@@ -122,13 +115,12 @@ const observable = async (slice: CriteriaSlice | undefined): Promise<string[]> =
122
115
  const frozen = (): void => requireCriteriaFrozen(REQUIREMENTS)
123
116
 
124
117
  const testing = async (
125
- slice: CriteriaSlice | undefined,
126
118
  ids: readonly string[],
127
119
  retest: boolean,
128
120
  ): Promise<ReturnType<typeof requireReport>> => {
129
- const skipped = criterionIds(read(REQUIREMENTS) ?? "", slice).filter((id) => !ids.includes(id))
130
- await t.agentWithSkills("testing", t.prototypeTestingPrompt(sliceIds(slice), skipped, retest), {
131
- label: retest ? "Re-testing the slice by hand" : "Testing the slice by hand",
121
+ const skipped = criterionIds(read(REQUIREMENTS) ?? "").filter((id) => !ids.includes(id))
122
+ await t.agentWithSkills("testing", t.prototypeTestingPrompt(skipped, retest), {
123
+ label: retest ? "Re-testing the change by hand" : "Testing the change by hand",
132
124
  file: PROTOTYPE,
133
125
  model: coder(),
134
126
  system: t.testerSystem(),
@@ -138,9 +130,9 @@ const testing = async (
138
130
  return requireReport(ids, retest)
139
131
  }
140
132
 
141
- const fixing = async (slice: CriteriaSlice | undefined, guard?: Guard): Promise<void> => {
133
+ const fixing = async (guard?: Guard): Promise<void> => {
142
134
  await scope("fix", () =>
143
- t.agentWithSkills("fixing", t.prototypeFixingPrompt(sliceIds(slice)), {
135
+ t.agentWithSkills("fixing", t.prototypeFixingPrompt(), {
144
136
  label: "Fixing what the hands-on test found",
145
137
  file: PROTOTYPE,
146
138
  model: coder(),
@@ -166,25 +158,22 @@ const askPerson = async (name: string, message: string, label: string): Promise<
166
158
  }
167
159
 
168
160
  /**
169
- * Use the slice by hand: a testing turn reports on every criterion a person
170
- * can observe, a fix turn takes each failure, and the slice is tested again —
161
+ * Use the change by hand: a testing turn reports on every criterion a person
162
+ * can observe, a fix turn takes each failure, and the change is tested again —
171
163
  * until the report is clean, or `prototypeRounds` fixes are spent and a person
172
- * decides. A slice with no such criterion is not tested by hand. `guard` holds
173
- * every fix turn, as it holds the implementation's.
164
+ * decides. A change with no such criterion is not tested by hand. `guard`
165
+ * holds every fix turn, as it holds the implementation's.
174
166
  */
175
- export const prototypeTest = async (
176
- slice: CriteriaSlice | undefined,
177
- guard?: Guard,
178
- ): Promise<void> => {
167
+ export const prototypeTest = async (guard?: Guard): Promise<void> => {
179
168
  const cap = prototypeRounds()
180
169
  if (cap === 0) return
181
170
  await scope("prototype", async () => {
182
- const ids = await observable(slice)
171
+ const ids = await observable()
183
172
  if (ids.length === 0) return
184
173
  let fixes = 0
185
174
  let retest = false
186
175
  for (;;) {
187
- const { unguided, open } = await testing(slice, ids, retest)
176
+ const { unguided, open } = await testing(ids, retest)
188
177
  retest = !unguided
189
178
  if (unguided) {
190
179
  const instructed = await askPerson(
@@ -196,7 +185,7 @@ export const prototypeTest = async (
196
185
  } else if (open.length === 0) return
197
186
  else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
198
187
  fixes++
199
- await fixing(slice, guard)
188
+ await fixing(guard)
200
189
  } else {
201
190
  const changed = await askPerson(
202
191
  "open",
@@ -3,10 +3,10 @@
3
3
  Feature: a refactor that carried the whole feature still gets a quality lap over all of it
4
4
 
5
5
  With GTD_FLAG_RISK_TIER off, a refactor-first
6
- refactor can land everything the requirements ask for. The feature slice's
6
+ refactor can land everything the requirements ask for. The feature's
7
7
  implement turn then has only the naive pass's acceptance tests to carry
8
- over, and with no code but tests changed since the refactor the feature
9
- slice's quality lap covers the whole change, refactor included.
8
+ over, and with no code but tests changed since the refactor the feature's
9
+ quality lap covers the whole change, refactor included.
10
10
 
11
11
  Background:
12
12
  Given the shipped workflows, as gtd install scaffolds them
@@ -132,8 +132,3 @@ Feature: a refactor that carried the whole feature still gets a quality lap over
132
132
  gtd(check): implement.closing → build.quality.correctness.reviewing
133
133
  gtd(agent): build.quality.correctness.reviewing → build.review.reviewing
134
134
  """
135
- And the output lines of gtd "slices" start with:
136
- """
137
- refactor
138
- feature
139
- """
@@ -160,9 +160,4 @@ Feature: a refactor-first judgment walks one fixed path to the review gate
160
160
  And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
161
161
  And the commit "gtd(judge): seam.judge → seam.note" has a body containing "\"answer\":\"refactor-first\""
162
162
  And no commit body contains "Gtd-Approved:"
163
- And the output lines of gtd "slices" start with:
164
- """
165
- refactor
166
- feature
167
- """
168
163
  And no commit body contains "Gtd-Workflow:"
@@ -197,7 +197,6 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
197
197
  Then the process rests at "implement.building"
198
198
  And "src/calc.ts" contains "op(a, b, 1)"
199
199
  And "src/calc.ts" does not contain "subtract"
200
- And the output of gtd "slices" contains "refactor "
201
200
 
202
201
  Scenario: the rebuild lands only once it carries the naive pass's acceptance tests, green
203
202
  Given step "seam.note" writes ".gtd/REFACTOR-NOTE.md" with:
@@ -285,8 +284,3 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
285
284
  gtd(check): implement.health.check → implement.closing
286
285
  gtd(check): implement.closing → build.review.reviewing
287
286
  """
288
- And the output lines of gtd "slices" start with:
289
- """
290
- refactor
291
- feature
292
- """
@@ -1,9 +1,9 @@
1
1
  @inmem
2
- Feature: the refactor slice is reviewed on its own before the feature is implemented
2
+ Feature: the refactor is reviewed on its own before the feature is implemented
3
3
 
4
4
  After a refactor-first judgment the refactor is built, quality-lapped and
5
- reviewed in its own slice. Review edits re-run the refactor, never the design;
6
- the feature slice that follows is quality-lapped from the refactor's
5
+ reviewed on its own. Review edits re-run the refactor, never the design;
6
+ the feature that follows is quality-lapped from the refactor's
7
7
  sign-off, then reviewed. The flags are pinned so the per-flag test runs never move it off
8
8
  its path.
9
9
 
@@ -614,7 +614,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
614
614
  And ".gtd/REFACTOR.md" does not exist
615
615
  And ".gtd/REVIEW.md" contains "A: add and subtract differ only by sign."
616
616
 
617
- Scenario: a no-change landing after the in-gate reply signs off and the feature slice follows
617
+ Scenario: a no-change landing after the in-gate reply signs off and the feature follows
618
618
  Given step "implement.building" writes "src/calc.ts" with:
619
619
  """
620
620
  export const op = (a: number, b: number, sign: number) => a + sign * b
@@ -697,7 +697,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
697
697
  Then the process rests at "build.review.reviewing"
698
698
  And "src/calc.ts" contains "subtract"
699
699
 
700
- Scenario: a praise-only one-shot footnote signs off and the feature slice follows
700
+ Scenario: a praise-only one-shot footnote signs off and the feature follows
701
701
  Given step "implement.building" writes "src/calc.ts" with:
702
702
  """
703
703
  export const op = (a: number, b: number, sign: number) => a + sign * b
@@ -979,7 +979,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
979
979
  Then driving the process is refused with "refactor-feedback"
980
980
  And driving the process is refused with "hand-edited src/calc.ts"
981
981
 
982
- Scenario: a praise note and a concluded thread on a settled round sign off and the feature slice follows
982
+ Scenario: a praise note and a concluded thread on a settled round sign off and the feature follows
983
983
  Given step "implement.building" writes "src/calc.ts" with:
984
984
  """
985
985
  export const op = (a: number, b: number, sign: number) => a + sign * b
@@ -1066,7 +1066,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
1066
1066
  Then the process rests at "build.review.reviewing"
1067
1067
  And "src/calc.ts" contains "subtract"
1068
1068
 
1069
- Scenario: a praise-only refactor review still signs off and the feature slice follows
1069
+ Scenario: a praise-only refactor review still signs off and the feature follows
1070
1070
  Given step "implement.building" writes "src/calc.ts" with:
1071
1071
  """
1072
1072
  export const op = (a: number, b: number, sign: number) => a + sign * b
@@ -1099,7 +1099,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
1099
1099
  Then the process rests at "build.review.reviewing"
1100
1100
  And "src/calc.ts" contains "subtract"
1101
1101
 
1102
- Scenario: a nit fix in the refactor slice that changes a non-stray is refused, though a glob stray widens the write access
1102
+ Scenario: a nit fix in the refactor that changes a non-stray is refused, though a glob stray widens the write access
1103
1103
  Given step "seam.naive" writes "src/*.ts" with:
1104
1104
  """
1105
1105
  export const literalGlobName = 2
@@ -1130,7 +1130,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
1130
1130
  When gtd lands
1131
1131
  Then driving the process is refused with "refactor-scope"
1132
1132
 
1133
- Scenario: the nit-fix turn in the refactor slice may write the strays and .gtd/** only
1133
+ Scenario: the nit-fix turn in the refactor may write the strays and .gtd/** only
1134
1134
  Given step "seam.naive" writes "src/*.ts" with:
1135
1135
  """
1136
1136
  export const literalGlobName = 2
@@ -1185,7 +1185,45 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
1185
1185
  """
1186
1186
  And no commit body contains "refactor.build.review.composing"
1187
1187
 
1188
- Rule: with a quality lens on both slices
1188
+ Scenario: a risk the refactor's re-review marks again reaches the human unfixed
1189
+ Given step "refactor.build.review.reviewing" writes ".gtd/REVIEW.md" with:
1190
+ """
1191
+ # Review: abc1234
1192
+
1193
+ <!-- base: 0000000 -->
1194
+
1195
+ ## calc
1196
+
1197
+ - [ ] ./src/calc.ts#1-2 — Risk: drops the sign
1198
+ """
1199
+ When the process is driven
1200
+ Then the process rests at "refactor.build.review.fix.risks.fixing"
1201
+ Given step "refactor.build.review.reviewing" is no longer scripted
1202
+ And step "refactor.build.review.reviewing" replaces the text "Risk: drops the sign" in ".gtd/REVIEW.md" with "Risk: still drops the sign"
1203
+ And step "refactor.build.review.fix.risks.fixing" changes nothing
1204
+ And step "refactor.build.health.check" changes nothing
1205
+ When the process is driven
1206
+ Then the process rests at "refactor.build.review.await-review"
1207
+ And ".gtd/REVIEW.md" contains:
1208
+ """
1209
+ ## Walkthrough
1210
+
1211
+ ### calc
1212
+
1213
+ - [ ] ./src/calc.ts#1-2 — Risk: still drops the sign
1214
+ """
1215
+ And ".gtd/REVIEW.md" contains "## Walkthrough" exactly once
1216
+ And the commit subjects end with:
1217
+ """
1218
+ gtd(agent): refactor.build.review.reviewing → refactor.build.review.fix.risks.fixing
1219
+ gtd(agent): refactor.build.review.fix.risks.fixing → refactor.build.health.check
1220
+ gtd(check): refactor.build.health.check → refactor.build.review.reviewing
1221
+ gtd(agent): refactor.build.review.reviewing → refactor.build.review.ranking
1222
+ gtd(judge): refactor.build.review.ranking → refactor.build.review.carrying
1223
+ gtd(check): refactor.build.review.carrying → refactor.build.review.await-review
1224
+ """
1225
+
1226
+ Rule: with a quality lens on both the refactor and the feature
1189
1227
 
1190
1228
  Background:
1191
1229
  Given an environment variable "GTD_REFACTORQUALITYREVIEWS" set to "correctness"
@@ -1275,7 +1313,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
1275
1313
  """
1276
1314
  And step "refactor.build.review.closing" runs its script
1277
1315
 
1278
- Scenario: each slice's quality lap reviews from that slice's base
1316
+ Scenario: the refactor's and the feature's quality laps each review from their own base
1279
1317
  When the process is driven
1280
1318
  Then the process rests at "refactor.build.quality.correctness.reviewing"
1281
1319
  And the resting step's content contains the hash of the commit "gtd(agent): seam.note → refactor.deriving"
@@ -15,9 +15,8 @@ import {
15
15
  } from "../../flows/index.js"
16
16
  import { lensEntriesOf } from "../../workflow-kit/index.js"
17
17
  import { markFenced } from "../shared/fences.js"
18
- import { continuing, phase, type Phase } from "../shared/continuation.js"
19
18
  import { healthy, type Guard } from "../shared/health.js"
20
- import { coder, FEEDBACK, HANDOFF, REFACTOR } from "../shared/steps.js"
19
+ import { coder, FEEDBACK, REFACTOR } from "../shared/steps.js"
21
20
  import * as t from "../shared/text.js"
22
21
  import { defaults, testFileGlobs } from "../shared/vars.js"
23
22
  import { buildTail, reUnwind, type FeedbackTarget } from "../shared/review.js"
@@ -264,12 +263,7 @@ const guardOf =
264
263
  }
265
264
 
266
265
  /** Derive, build, check and close one refactor lap; `feedback` is the collected review text of the lap before. */
267
- const refactorLap = async (
268
- p: Plan,
269
- guard: Guard,
270
- refactor: Phase,
271
- feedback?: string,
272
- ): Promise<void> => {
266
+ const refactorLap = async (p: Plan, guard: Guard, feedback?: string): Promise<void> => {
273
267
  const { pattern, strays, reference, note } = p
274
268
  await run(
275
269
  "deriving",
@@ -282,59 +276,32 @@ const refactorLap = async (
282
276
  ]),
283
277
  { label: "Deriving the refactor package", file: REFACTOR_PACKAGE },
284
278
  )
285
- const reached = new Set<string>()
286
- // Each snapshot is greened before its continuation: every sub-step keeps the old suite green.
287
- await continuing(refactor, async (from, fresh) => {
288
- await fresh(() =>
289
- t.agentWithSkills(
290
- "building",
291
- t.refactorBuildingPrompt({
292
- pkg: REFACTOR_PACKAGE,
293
- strays,
294
- pattern,
295
- reference,
296
- feedback: feedback !== undefined,
297
- note: note !== undefined,
298
- from,
299
- }),
300
- {
301
- label: from === undefined ? "Refactoring" : "Continuing the refactor",
302
- model: coder(),
303
- system: t.builderSystem(),
304
- },
305
- ),
306
- )
307
- // Committed by the snapshot, so a turn stopped again may leave it as it was: present means stopped.
308
- const left = read(HANDOFF)
309
- guard()
310
- const snapshot = head()
311
- // The building turns' own changes only: a fix turn reaching a path is not the refactor reaching it.
312
- for (const c of changes()) reached.add(c.path)
313
- await healthy(
314
- async () => {
315
- await t.agentWithSkills("fix.suite.fixing", t.refactorFixPrompt(strays, pattern), {
316
- label: "Fixing the check",
317
- file: FEEDBACK,
318
- model: coder(),
319
- system: t.builderSystem(),
320
- })
321
- guard()
322
- },
323
- { describeGuard: guard, rules: t.refactorRules(strays, pattern) },
324
- )
325
- if (left === undefined) return { result: undefined }
326
- return {
327
- result: undefined,
328
- handoff: {
329
- snapshot,
330
- note: left.trim(),
331
- open: strays.filter((path) => !reached.has(path)),
332
- },
333
- }
334
- })
335
- await run("closing", removeScript([REFACTOR_PACKAGE, HANDOFF]), {
336
- label: "Closing out the refactor",
337
- })
279
+ await t.agentWithSkills(
280
+ "building",
281
+ t.refactorBuildingPrompt({
282
+ pkg: REFACTOR_PACKAGE,
283
+ strays,
284
+ pattern,
285
+ reference,
286
+ feedback: feedback !== undefined,
287
+ note: note !== undefined,
288
+ }),
289
+ { label: "Refactoring", model: coder(), system: t.builderSystem() },
290
+ )
291
+ guard()
292
+ await healthy(
293
+ async () => {
294
+ await t.agentWithSkills("fix.suite.fixing", t.refactorFixPrompt(strays, pattern), {
295
+ label: "Fixing the check",
296
+ file: FEEDBACK,
297
+ model: coder(),
298
+ system: t.builderSystem(),
299
+ })
300
+ guard()
301
+ },
302
+ { describeGuard: guard, rules: t.refactorRules(strays, pattern) },
303
+ )
304
+ await run("closing", removeScript([REFACTOR_PACKAGE]), { label: "Closing out the refactor" })
338
305
  }
339
306
 
340
307
  const strayAccess = (strays: readonly string[]) => ({
@@ -348,7 +315,7 @@ export const refactorFirst = async (
348
315
  const p = plan(shape, options)
349
316
  if (p === undefined) return false
350
317
  return scope({ name: "refactor", access: strayAccess(p.strays) }, async () => {
351
- await refactorLap(p, guardOf(p, head()), phase("refactor"))
318
+ await refactorLap(p, guardOf(p, head()))
352
319
  return true
353
320
  })
354
321
  }
@@ -375,10 +342,9 @@ export const reviewedRefactor = async (
375
342
  "gtd land: refactor-feedback: the review feedback changed files but wrote no .gtd/REFACTOR.md — a refactor cannot act on it; revert the change, or `gtd abandon` and restart",
376
343
  dropped: t.refactorDroppedRefusal,
377
344
  }
378
- const refactor = phase("refactor")
379
345
  let collected: string | undefined
380
346
  for (;;) {
381
- await refactorLap(p, guard, refactor, collected)
347
+ await refactorLap(p, guard, collected)
382
348
  // A person's edit at an escalation rest is never refused, as in the lap.
383
349
  const outcome = await buildTail(entry, {
384
350
  green: true,
@@ -1,7 +1,7 @@
1
1
  @inmem
2
2
  Feature: a feature process ends at the human review
3
3
 
4
- After the last slice's quality lap the process rests at the review gate,
4
+ After the quality lap the process rests at the review gate,
5
5
  `.gtd/REVIEW.md`. A sign-off ends the process. A note asking for a change in
6
6
  behaviour starts a feedback lap — design, then implement with no new naive
7
7
  pass or seam judge, the hands-on test, the quality lap — and the review
@@ -179,37 +179,6 @@ Feature: a reviewer walks the human through the feature's code
179
179
  """
180
180
  Then driving the process is refused with "AC-1 ignores floating point — no chunk of its own"
181
181
 
182
- Scenario: a risk the reviewer marks is fixed before the human sees the review
183
- Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
184
- """
185
- # Review: abc1234
186
-
187
- <!-- base: 0000000 -->
188
-
189
- ## Subtract
190
-
191
- - [ ] ./src/calc.ts#2-2 — Risk: overflows on large numbers
192
-
193
- ## AC-1 ignores floating point
194
-
195
- - [ ] ./src/calc.ts#2-2 — returns the raw difference
196
- """
197
- And step "build.review.fix.risks.fixing" changes nothing
198
- And step "build.health.check" changes nothing
199
- When the process is driven
200
- Then the process rests at "build.review.await-review"
201
- And the commit subjects end with:
202
- """
203
- gtd(agent): build.review.reviewing → build.review.ranking
204
- gtd(judge): build.review.ranking → build.review.carrying
205
- gtd(check): build.review.carrying → build.review.fix.risks.fixing
206
- gtd(agent): build.review.fix.risks.fixing → build.health.check
207
- gtd(check): build.health.check → build.review.reviewing
208
- gtd(agent): build.review.reviewing → build.review.ranking
209
- gtd(judge): build.review.ranking → build.review.carrying
210
- gtd(check): build.review.carrying → build.review.await-review
211
- """
212
-
213
182
  Scenario: a standard conflict the reviewer writes itself is refused
214
183
  Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
215
184
  """
@@ -136,8 +136,4 @@ Feature: rejected naive passes walk one fixed path through the steer gate to the
136
136
  And the commit "gtd(judge): seam.judge → seam.steer" has a body containing "\"answer\":\"reject\""
137
137
  And the commit "gtd(judge): seam.judge → implement.applying" has a body containing "\"answer\":\"land\""
138
138
  And no commit body contains "Gtd-Approved:"
139
- And the output lines of gtd "slices" start with:
140
- """
141
- feature
142
- """
143
139
  And no commit body contains "Gtd-Workflow:"
@@ -311,9 +311,6 @@ const reviewDoc = [
311
311
 
312
312
  interface Seamed {
313
313
  readonly names: string[]
314
- readonly slices: string[]
315
- /** Slice names entered, with the steps seen while inside each. */
316
- readonly inside: Record<string, string[]>
317
314
  readonly outcome: SeamOutcome | Error
318
315
  }
319
316
 
@@ -327,10 +324,7 @@ const driveSeam = async (
327
324
  let red = false
328
325
  let redsLeft = reds
329
326
  const names: string[] = []
330
- const slices: string[] = []
331
- const inside: Record<string, string[]> = {}
332
327
  const stack: string[] = []
333
- const open: string[] = []
334
328
  const files = new Map<string, string>([["src/a.ts", "a"]])
335
329
  let current = "c0"
336
330
  const effects: Record<string, () => void> = {
@@ -351,12 +345,6 @@ const driveSeam = async (
351
345
  },
352
346
  pushScope: (scope: { name?: string }) => void stack.push(scope.name ?? ""),
353
347
  popScope: () => void stack.pop(),
354
- enterSlice: (name: string) => {
355
- slices.push(name)
356
- open.push(name)
357
- inside[name] = []
358
- },
359
- leaveSlice: () => void open.pop(),
360
348
  read: (path: string) => files.get(path),
361
349
  head: () => current,
362
350
  kept: () => tip(stray),
@@ -372,7 +360,6 @@ const driveSeam = async (
372
360
  }
373
361
  if (full === "seam.clear") files.delete(".gtd/FEEDBACK.md")
374
362
  names.push(full)
375
- for (const name of open) inside[name]!.push(full)
376
363
  effects[request.name]?.()
377
364
  if (request.kind === "judge")
378
365
  return Promise.resolve({ answers: { shape: { answer, p: 0.9 } }, truncated: [] })
@@ -385,14 +372,13 @@ const driveSeam = async (
385
372
  } catch (error) {
386
373
  outcome = error as Error
387
374
  }
388
- return { names, slices, inside, outcome }
375
+ return { names, outcome }
389
376
  }
390
377
 
391
378
  describe("seamBuild", () => {
392
- it("land resolves the kept tip to land, with the caller's base, and enters no slice", async () => {
379
+ it("land resolves the kept tip to land, with the caller's base", async () => {
393
380
  const h = await driveSeam("land")
394
381
  expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
395
- expect(h.slices).toEqual([])
396
382
  expect(h.names).toEqual(["seam.naive", "seam.check", "seam.judge"])
397
383
  })
398
384
 
@@ -432,7 +418,7 @@ describe("seamBuild", () => {
432
418
  expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
433
419
  })
434
420
 
435
- it("refactor-first runs the reviewed refactor in slice refactor and resolves HEAD after it", async () => {
421
+ it("refactor-first runs the reviewed refactor right after the note and resolves HEAD after it", async () => {
436
422
  const h = await driveSeam("refactor-first")
437
423
  expect(h.outcome).toEqual({
438
424
  reference: "abc",
@@ -440,16 +426,14 @@ describe("seamBuild", () => {
440
426
  refactored: true,
441
427
  rebuild: { note: "Seam: a shared op", acceptance: { hash: "abc", tests: [], ids: [] } },
442
428
  })
443
- expect(h.slices).toEqual(["refactor"])
444
429
  expect(h.names.indexOf("seam.note")).toBe(h.names.indexOf("seam.judge") + 1)
445
- expect(h.inside.refactor).not.toContain("seam.note")
446
- expect(h.inside.refactor![0]).toBe("refactor.deriving")
447
- expect(h.inside.refactor).toContain("refactor.build.review.closing")
430
+ expect(h.names.indexOf("refactor.deriving")).toBe(h.names.indexOf("seam.note") + 1)
431
+ expect(h.names).toContain("refactor.build.review.closing")
448
432
  expect(h.names.some((n) => n.includes("approve"))).toBe(false)
449
433
  expect(h.names.some((n) => n.startsWith("implement"))).toBe(false)
450
434
  })
451
435
 
452
- it("refactor-first with only test-file strays resolves the caller's base with no slice and no by-hand", async () => {
436
+ it("refactor-first with only test-file strays resolves the caller's base with no refactor and no by-hand", async () => {
453
437
  const h = await driveSeam("refactor-first", change("src/a.test.ts", "modified"), {
454
438
  seamAllow: ".gtd/**",
455
439
  })
@@ -458,7 +442,6 @@ describe("seamBuild", () => {
458
442
  base: "c-base",
459
443
  rebuild: { acceptance: { hash: "abc", tests: [], ids: [] } },
460
444
  })
461
- expect(h.slices).toEqual([])
462
445
  expect(h.names).not.toContain("seam.by-hand")
463
446
  expect(h.names.some((n) => n.startsWith("refactor"))).toBe(false)
464
447
  })
@@ -469,18 +452,16 @@ describe("seamBuild", () => {
469
452
  { path: "src/a.test.ts", status: "modified", before: undefined, after: "b" },
470
453
  { seamAllow: ".gtd/**" },
471
454
  )
472
- expect(h.slices).toEqual([])
473
455
  expect(h.names).toContain("seam.by-hand")
474
456
  })
475
457
 
476
- it("rests unreadable strays at seam.by-hand and enters no refactor slice", async () => {
458
+ it("rests unreadable strays at seam.by-hand and runs no refactor", async () => {
477
459
  const h = await driveSeam("refactor-first", {
478
460
  path: "src/a.ts",
479
461
  status: "modified",
480
462
  before: undefined,
481
463
  after: "b",
482
464
  })
483
- expect(h.slices).toEqual([])
484
465
  expect(h.names).toContain("seam.by-hand")
485
466
  expect(h.outcome).toEqual({
486
467
  reference: "abc",