@pmelab/gtd 52.0.0 → 53.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 (108) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +96 -131
  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-D0MKVFFa.mjs → reserved-CQ7amcKG.mjs} +87 -133
  7. package/dist/chunks/{review-ZzUQH1KZ.mjs → review-CNgN_V8T.mjs} +2 -2
  8. package/dist/chunks/{tail-I1-vVq0P.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 +159 -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 +400 -675
  20. package/package.json +5 -4
  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 +13 -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/acceptance-criteria.feature +159 -3
  46. package/src/workflows/feature/criteria.test.ts +23 -15
  47. package/src/workflows/feature/criteria.ts +72 -39
  48. package/src/workflows/feature/fast-path.feature +4 -0
  49. package/src/workflows/feature/feature.test.ts +6 -5
  50. package/src/workflows/feature/feature.ts +34 -118
  51. package/src/workflows/feature/flag-mode.feature +4 -0
  52. package/src/workflows/feature/hands-on-skip.feature +37 -65
  53. package/src/workflows/feature/index.ts +0 -1
  54. package/src/workflows/feature/lens-models.feature +4 -0
  55. package/src/workflows/feature/lens-review.feature +4 -0
  56. package/src/workflows/feature/naive-acceptance.feature +4 -0
  57. package/src/workflows/feature/one-step-land.feature +4 -4
  58. package/src/workflows/feature/planning.ts +2 -1
  59. package/src/workflows/feature/prototype-rounds.feature +5 -1
  60. package/src/workflows/feature/prototype.feature +11 -3
  61. package/src/workflows/feature/prototype.ts +18 -29
  62. package/src/workflows/feature/refactor-absorbs.feature +7 -8
  63. package/src/workflows/feature/refactor-first.feature +4 -5
  64. package/src/workflows/feature/refactor-lens-models.feature +4 -0
  65. package/src/workflows/feature/refactor-rebuild.feature +4 -6
  66. package/src/workflows/feature/refactor-review.feature +31 -11
  67. package/src/workflows/feature/refactor.ts +30 -64
  68. package/src/workflows/feature/review-answers.feature +4 -0
  69. package/src/workflows/feature/review-gate.feature +13 -1
  70. package/src/workflows/feature/reviewer-walkthrough.feature +4 -0
  71. package/src/workflows/feature/risk-tier.feature +32 -0
  72. package/src/workflows/feature/seam-rejection.feature +8 -4
  73. package/src/workflows/feature/seam.feature +8 -0
  74. package/src/workflows/feature/seam.test.ts +11 -28
  75. package/src/workflows/feature/seam.ts +52 -165
  76. package/src/workflows/feature/severity-budget.feature +4 -0
  77. package/src/workflows/feature/severity-sorted-review.feature +4 -0
  78. package/src/workflows/feature/standards.feature +4 -0
  79. package/src/workflows/feature/tier-pin.feature +4 -0
  80. package/src/workflows/index.ts +0 -1
  81. package/src/workflows/shared/lenses.ts +0 -13
  82. package/src/workflows/shared/options.test.ts +2 -11
  83. package/src/workflows/shared/options.ts +4 -21
  84. package/src/workflows/shared/review.test.ts +3 -3
  85. package/src/workflows/shared/steps.ts +0 -2
  86. package/src/workflows/shared/text.test.ts +41 -71
  87. package/src/workflows/shared/text.ts +82 -212
  88. package/src/workflows/shared/vars.ts +2 -8
  89. package/src/workflows/shared/workflows.steps.ts +0 -13
  90. package/claude/hooks/context.ts +0 -66
  91. package/src/replay/Slices.test.ts +0 -73
  92. package/src/replay/Slices.ts +0 -45
  93. package/src/wire/ContextLimit.fixture.ts +0 -30
  94. package/src/wire/ContextLimit.test.ts +0 -96
  95. package/src/wire/ContextLimit.ts +0 -169
  96. package/src/wire/Slices.ts +0 -25
  97. package/src/wire/golden/1/slices.json +0 -11
  98. package/src/workflows/feature/context-limit.feature +0 -37
  99. package/src/workflows/feature/continuation-cap.feature +0 -65
  100. package/src/workflows/feature/continuation-rebuild.feature +0 -88
  101. package/src/workflows/feature/continuation-slices.feature +0 -64
  102. package/src/workflows/feature/continuation.feature +0 -205
  103. package/src/workflows/feature/slice-findings.feature +0 -252
  104. package/src/workflows/feature/slicing-cap.feature +0 -23
  105. package/src/workflows/feature/slicing.feature +0 -265
  106. package/src/workflows/feature/slicing.test.ts +0 -49
  107. package/src/workflows/feature/slicing.ts +0 -105
  108. package/src/workflows/shared/continuation.ts +0 -92
@@ -109,7 +109,8 @@ describe("naivePass", () => {
109
109
  })
110
110
 
111
111
  describe("naiveAcceptance and requireAcceptance", () => {
112
- const requirements = "# R\n\n## Acceptance Criteria\n\n- AC-1: adds.\n- AC-2: subtracts.\n"
112
+ const requirements =
113
+ "# R\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: adds.\n- AC-2: subtracts.\n"
113
114
  const file = (path: string, before: string | undefined, after: string): Change => ({
114
115
  path,
115
116
  status: before === undefined ? "added" : "modified",
@@ -170,7 +171,8 @@ describe("naiveAcceptance and requireAcceptance", () => {
170
171
  })
171
172
 
172
173
  describe("acceptanceFault", () => {
173
- const requirements = "# R\n\n## Acceptance Criteria\n\n- AC-1: adds.\n- AC-2: subtracts.\n"
174
+ const requirements =
175
+ "# R\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: adds.\n- AC-2: subtracts.\n"
174
176
  const file = (path: string, status: Change["status"], after: string): Change => ({
175
177
  path,
176
178
  status,
@@ -309,9 +311,6 @@ const reviewDoc = [
309
311
 
310
312
  interface Seamed {
311
313
  readonly names: string[]
312
- readonly slices: string[]
313
- /** Slice names entered, with the steps seen while inside each. */
314
- readonly inside: Record<string, string[]>
315
314
  readonly outcome: SeamOutcome | Error
316
315
  }
317
316
 
@@ -325,10 +324,7 @@ const driveSeam = async (
325
324
  let red = false
326
325
  let redsLeft = reds
327
326
  const names: string[] = []
328
- const slices: string[] = []
329
- const inside: Record<string, string[]> = {}
330
327
  const stack: string[] = []
331
- const open: string[] = []
332
328
  const files = new Map<string, string>([["src/a.ts", "a"]])
333
329
  let current = "c0"
334
330
  const effects: Record<string, () => void> = {
@@ -349,12 +345,6 @@ const driveSeam = async (
349
345
  },
350
346
  pushScope: (scope: { name?: string }) => void stack.push(scope.name ?? ""),
351
347
  popScope: () => void stack.pop(),
352
- enterSlice: (name: string) => {
353
- slices.push(name)
354
- open.push(name)
355
- inside[name] = []
356
- },
357
- leaveSlice: () => void open.pop(),
358
348
  read: (path: string) => files.get(path),
359
349
  head: () => current,
360
350
  kept: () => tip(stray),
@@ -370,7 +360,6 @@ const driveSeam = async (
370
360
  }
371
361
  if (full === "seam.clear") files.delete(".gtd/FEEDBACK.md")
372
362
  names.push(full)
373
- for (const name of open) inside[name]!.push(full)
374
363
  effects[request.name]?.()
375
364
  if (request.kind === "judge")
376
365
  return Promise.resolve({ answers: { shape: { answer, p: 0.9 } }, truncated: [] })
@@ -383,14 +372,13 @@ const driveSeam = async (
383
372
  } catch (error) {
384
373
  outcome = error as Error
385
374
  }
386
- return { names, slices, inside, outcome }
375
+ return { names, outcome }
387
376
  }
388
377
 
389
378
  describe("seamBuild", () => {
390
- 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 () => {
391
380
  const h = await driveSeam("land")
392
381
  expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
393
- expect(h.slices).toEqual([])
394
382
  expect(h.names).toEqual(["seam.naive", "seam.check", "seam.judge"])
395
383
  })
396
384
 
@@ -430,7 +418,7 @@ describe("seamBuild", () => {
430
418
  expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
431
419
  })
432
420
 
433
- 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 () => {
434
422
  const h = await driveSeam("refactor-first")
435
423
  expect(h.outcome).toEqual({
436
424
  reference: "abc",
@@ -438,16 +426,14 @@ describe("seamBuild", () => {
438
426
  refactored: true,
439
427
  rebuild: { note: "Seam: a shared op", acceptance: { hash: "abc", tests: [], ids: [] } },
440
428
  })
441
- expect(h.slices).toEqual(["refactor"])
442
429
  expect(h.names.indexOf("seam.note")).toBe(h.names.indexOf("seam.judge") + 1)
443
- expect(h.inside.refactor).not.toContain("seam.note")
444
- expect(h.inside.refactor![0]).toBe("refactor.deriving")
445
- 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")
446
432
  expect(h.names.some((n) => n.includes("approve"))).toBe(false)
447
433
  expect(h.names.some((n) => n.startsWith("implement"))).toBe(false)
448
434
  })
449
435
 
450
- 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 () => {
451
437
  const h = await driveSeam("refactor-first", change("src/a.test.ts", "modified"), {
452
438
  seamAllow: ".gtd/**",
453
439
  })
@@ -456,7 +442,6 @@ describe("seamBuild", () => {
456
442
  base: "c-base",
457
443
  rebuild: { acceptance: { hash: "abc", tests: [], ids: [] } },
458
444
  })
459
- expect(h.slices).toEqual([])
460
445
  expect(h.names).not.toContain("seam.by-hand")
461
446
  expect(h.names.some((n) => n.startsWith("refactor"))).toBe(false)
462
447
  })
@@ -467,18 +452,16 @@ describe("seamBuild", () => {
467
452
  { path: "src/a.test.ts", status: "modified", before: undefined, after: "b" },
468
453
  { seamAllow: ".gtd/**" },
469
454
  )
470
- expect(h.slices).toEqual([])
471
455
  expect(h.names).toContain("seam.by-hand")
472
456
  })
473
457
 
474
- 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 () => {
475
459
  const h = await driveSeam("refactor-first", {
476
460
  path: "src/a.ts",
477
461
  status: "modified",
478
462
  before: undefined,
479
463
  after: "b",
480
464
  })
481
- expect(h.slices).toEqual([])
482
465
  expect(h.names).toContain("seam.by-hand")
483
466
  expect(h.outcome).toEqual({
484
467
  reference: "abc",
@@ -17,36 +17,18 @@ import {
17
17
  removeScript,
18
18
  run,
19
19
  scope,
20
- slice as sliced,
21
- start,
22
20
  vars,
23
21
  wrote,
24
22
  type Change,
25
23
  type DiffShapeResult,
26
24
  type KeptTip,
27
25
  } from "../../flows/index.js"
28
- import { continuing, phase, type Handoff, type Phase } from "../shared/continuation.js"
29
- import {
30
- coder,
31
- FEEDBACK,
32
- HANDOFF,
33
- PROTOTYPE,
34
- REFACTOR_NOTE,
35
- REQUIREMENTS,
36
- SLICES,
37
- } from "../shared/steps.js"
26
+ import { coder, FEEDBACK, PROTOTYPE, REFACTOR_NOTE, REQUIREMENTS } from "../shared/steps.js"
38
27
  import * as t from "../shared/text.js"
39
28
  import { defaults } from "../shared/vars.js"
40
29
  import { green } from "./green.js"
41
30
  import { prototypeTest } from "./prototype.js"
42
- import {
43
- criterionIds,
44
- requireCriteria,
45
- requireCriteriaFrozen,
46
- requireCriteriaIfEdited,
47
- sliceIds,
48
- type CriteriaSlice,
49
- } from "./criteria.js"
31
+ import { criterionIds, requireCriteria, requireCriteriaFrozen } from "./criteria.js"
50
32
  import {
51
33
  COMMENT,
52
34
  holdsCallSites,
@@ -72,47 +54,14 @@ export const judgeSeam = (tip: KeptTip): Promise<DiffShapeResult> =>
72
54
  exempt: tip.changes.filter((c) => c.status === "added").map((c) => c.path),
73
55
  })
74
56
 
75
- export const naivePass = (
76
- retry?: t.NaiveRetry,
77
- slice?: CriteriaSlice,
78
- from?: Handoff,
79
- ): Promise<void> =>
80
- t.agentWithSkills("naive", t.naivePassPrompt(retry, sliceIds(slice), from), {
81
- label:
82
- from !== undefined
83
- ? "Continuing the naive pass"
84
- : retry === undefined
85
- ? "Writing the naive pass"
86
- : "Rewriting the naive pass",
57
+ export const naivePass = (retry?: t.NaiveRetry): Promise<void> =>
58
+ t.agentWithSkills("naive", t.naivePassPrompt(retry), {
59
+ label: retry === undefined ? "Writing the naive pass" : "Rewriting the naive pass",
87
60
  keep: true,
88
61
  model: coder(),
89
62
  system: t.builderSystem(),
90
63
  })
91
64
 
92
- const handoffOf = (tip: KeptTip, slice: CriteriaSlice | undefined): Handoff | undefined => {
93
- const note = tip.changes.find((c) => c.path === HANDOFF && c.after !== undefined)
94
- if (note === undefined) return undefined
95
- return {
96
- snapshot: tip.hash,
97
- note: note.after!.trim(),
98
- open: unnamed(idsOf(REQUIREMENTS, slice), acceptanceTests(tip.changes)),
99
- }
100
- }
101
-
102
- /** A naive pass, continued until a pass ends without a handoff note: the snapshot is its kept tip. */
103
- const naiveContinued = (
104
- retry: t.NaiveRetry | undefined,
105
- slice: CriteriaSlice | undefined,
106
- naive: Phase,
107
- ) =>
108
- continuing(naive, async (from, fresh) => {
109
- const tip = await fresh(async () => {
110
- await naivePass(retry, slice, from)
111
- return kept("naive")!
112
- })
113
- return { result: tip, handoff: handoffOf(tip, slice) }
114
- })
115
-
116
65
  // Only a string an import, `require` or `mock` call takes: a URL route or path-like data is not one.
117
66
  const IMPORTED = /(?:\bfrom|\bimport|\brequire\s*\(|\bmock\s*\()\s*\(?\s*(["'`])([^"'`\n]+)\1/g
118
67
  const EXTENSION = /\.[\w-]+$/
@@ -158,8 +107,7 @@ const addedLines = (change: Change): string => {
158
107
  const acceptanceTests = (changes: readonly Change[]): readonly Change[] =>
159
108
  changes.filter((c) => c.after !== undefined && isCode(c.path) && isTestFile(c.path))
160
109
 
161
- const idsOf = (file: string, slice?: CriteriaSlice): string[] =>
162
- criterionIds(read(file) ?? "", slice)
110
+ const idsOf = (file: string): string[] => criterionIds(read(file) ?? "")
163
111
 
164
112
  // One rule for both gates, so a naive test carried over verbatim always satisfies the rebuild's.
165
113
  const unnamed = (ids: readonly string[], tests: readonly Change[]): string[] => {
@@ -172,13 +120,9 @@ const unnamed = (ids: readonly string[], tests: readonly Change[]): string[] =>
172
120
  * test line of the pass names, or a test importing a file the pass added —
173
121
  * naive structure a reset and refactor replaces, which the tests must survive.
174
122
  */
175
- export const acceptanceFault = (
176
- file: string,
177
- tip: KeptTip,
178
- slice?: CriteriaSlice,
179
- ): string | undefined => {
123
+ export const acceptanceFault = (file: string, tip: KeptTip): string | undefined => {
180
124
  const tests = acceptanceTests(tip.changes)
181
- const untested = unnamed(idsOf(file, slice), tests)
125
+ const untested = unnamed(idsOf(file), tests)
182
126
  if (untested.length > 0)
183
127
  return `acceptance-tests: no test of the naive pass names ${untested.join(", ")} — write a behaviour-level test for each criterion with its ID in the test or scenario name, e.g. "[${untested[0]}] …"`
184
128
  const added = tip.changes
@@ -198,8 +142,8 @@ export const acceptanceFault = (
198
142
  }
199
143
 
200
144
  /** What a rebuild carries over from the naive pass `tip`: the IDs its tests name, and those test files. */
201
- export const naiveAcceptance = (tip: KeptTip, slice?: CriteriaSlice): t.Acceptance => {
202
- const ids = idsOf(REQUIREMENTS, slice)
145
+ export const naiveAcceptance = (tip: KeptTip): t.Acceptance => {
146
+ const ids = idsOf(REQUIREMENTS)
203
147
  const perTest = acceptanceTests(tip.changes).map((c) => {
204
148
  const lines = addedLines(c)
205
149
  return { path: c.path, ids: ids.filter((id) => named(id, lines)) }
@@ -281,18 +225,14 @@ export interface ImplementOptions {
281
225
  readonly reference?: string | undefined
282
226
  readonly tier?: RiskTier | undefined
283
227
  readonly amend?: boolean | undefined
284
- /** After a refactor slice, which may already carry the feature: an empty turn completes the step. */
228
+ /** After a refactor, which may already carry the feature: an empty turn completes the step. */
285
229
  readonly afterRefactor?: boolean | undefined
286
230
  /** A kept naive tip to apply as the implementation; built fresh only when it no longer applies. */
287
231
  readonly naive?: string | undefined
288
232
  /** A rebuild after a refactor-first judgment: the note it plugs into, the acceptance tests it must carry. */
289
233
  readonly rebuild?: t.Rebuild | undefined
290
- /** The criteria this slice builds, when the change has more than one slice. */
291
- readonly slice?: CriteriaSlice | undefined
292
234
  /** @deprecated Ignored since the coverage judge is gone. */
293
235
  readonly base?: string | undefined
294
- /** Not the last slice: `.gtd/REQUIREMENTS.md` and `.gtd/SLICES.md` stay for the next one. */
295
- readonly more?: boolean | (() => boolean) | undefined
296
236
  }
297
237
 
298
238
  /** Resolves `false` when the tip no longer applies (the failure is in `.gtd/FEEDBACK.md`) or has nothing outside `.gtd/` to apply. */
@@ -304,54 +244,31 @@ const applied = async (tip: string): Promise<boolean> => {
304
244
  }
305
245
 
306
246
  /** `carried` limited to the criteria `.gtd/REQUIREMENTS.md` lists now; a file listing none narrows nothing. */
307
- const settled = (
308
- carried: readonly string[] | undefined,
309
- slice: CriteriaSlice | undefined,
310
- ): readonly string[] => {
311
- const listed = idsOf(REQUIREMENTS, slice)
312
- if (carried === undefined) return listed
247
+ const settled = (carried: readonly string[]): readonly string[] => {
248
+ const listed = idsOf(REQUIREMENTS)
313
249
  return listed.length === 0 ? carried : carried.filter((id) => listed.includes(id))
314
250
  }
315
251
 
316
- // A snapshot lands on the branch as it is; the health loop greens the whole build after.
317
- // Open IDs count every test line since the process start, so an amend lap's earlier tests close theirs.
318
- const build = (options: ImplementOptions): Promise<void> =>
319
- continuing(phase("build", requireCriteriaIfEdited), async (from, fresh) => {
320
- await fresh(() =>
321
- t.agentWithSkills(
322
- "building",
323
- t.implementBuildingPrompt(
324
- options.reference,
325
- options.tier,
326
- options.amend,
327
- options.afterRefactor,
328
- flagModeOn(),
329
- options.rebuild,
330
- sliceIds(options.slice),
331
- from,
332
- ),
333
- {
334
- label: from === undefined ? "Implementing" : "Continuing the implementation",
335
- model: coder(),
336
- system: t.builderSystem(),
337
- ...(options.afterRefactor === true ? { allowEmpty: true } : {}),
338
- },
339
- ),
340
- )
341
- requireCriteriaFrozen(REQUIREMENTS)
342
- // Committed by the snapshot, so a turn stopped again may leave it as it was: present means stopped.
343
- const note = read(HANDOFF)
344
- if (note === undefined) return { result: undefined }
345
- const ids = settled(options.rebuild?.acceptance?.ids, options.slice)
346
- return {
347
- result: undefined,
348
- handoff: {
349
- snapshot: head(),
350
- note: note.trim(),
351
- open: unnamed(ids, acceptanceTests(changesSince(start()))),
352
- },
353
- }
354
- })
252
+ const build = async (options: ImplementOptions): Promise<void> => {
253
+ await t.agentWithSkills(
254
+ "building",
255
+ t.implementBuildingPrompt(
256
+ options.reference,
257
+ options.tier,
258
+ options.amend,
259
+ options.afterRefactor,
260
+ flagModeOn(),
261
+ options.rebuild,
262
+ ),
263
+ {
264
+ label: "Implementing",
265
+ model: coder(),
266
+ system: t.builderSystem(),
267
+ ...(options.afterRefactor === true ? { allowEmpty: true } : {}),
268
+ },
269
+ )
270
+ requireCriteriaFrozen(REQUIREMENTS)
271
+ }
355
272
 
356
273
  export function implement(options?: ImplementOptions): Promise<void>
357
274
  /** @deprecated Pass `implement({ reference, tier, amend })`. */
@@ -378,38 +295,21 @@ export const implementing = (options: ImplementOptions): Promise<readonly string
378
295
  const built = options.naive === undefined || !(await applied(options.naive))
379
296
  if (built) await build(options)
380
297
  else requireCriteriaFrozen(REQUIREMENTS)
381
- // Only the criteria still listed: a person may narrow them at `implement.oversized`.
298
+ // Only the criteria the requirements still list.
382
299
  if (built && acceptance !== undefined)
383
- requireAcceptance({ ...acceptance, ids: settled(acceptance.ids, options.slice) }, since)
300
+ requireAcceptance({ ...acceptance, ids: settled(acceptance.ids) }, since)
384
301
  // Fix turns are held to the carried tests too: deleting a red one must not buy a green suite.
385
302
  const keepsTests =
386
303
  built && acceptance !== undefined ? () => requireAcceptanceKept(acceptance) : undefined
387
304
  await green(keepsTests)
388
- await prototypeTest(options.slice, keepsTests)
305
+ await prototypeTest(keepsTests)
389
306
  const ids = criterionIds(read(REQUIREMENTS) ?? "")
390
- await closing(moreToBuild(options.more))
307
+ await run("closing", removeScript([REQUIREMENTS, REFACTOR_NOTE, PROTOTYPE]), {
308
+ label: "Closing out the implementation",
309
+ })
391
310
  return ids
392
311
  })
393
312
 
394
- // Read at closing: a narrowing at `implement.oversized` may have emptied every later slice.
395
- const moreToBuild = (more: ImplementOptions["more"]): boolean =>
396
- typeof more === "function" ? more() : more === true
397
-
398
- /** The last slice's closing, for a last slice a steer left with no criterion to build. */
399
- export const closeOut = (): Promise<void> => scope("implement", () => closing(false))
400
-
401
- /** A slice with `more` after it keeps the criteria and the slicing for the next one. */
402
- const closing = (more: boolean): Promise<void> =>
403
- run(
404
- "closing",
405
- removeScript(
406
- more
407
- ? [REFACTOR_NOTE, HANDOFF, PROTOTYPE]
408
- : [REQUIREMENTS, REFACTOR_NOTE, SLICES, HANDOFF, PROTOTYPE],
409
- ),
410
- { label: "Closing out the implementation" },
411
- )
412
-
413
313
  interface Decision {
414
314
  readonly reference: string
415
315
  readonly tip: KeptTip
@@ -459,12 +359,8 @@ interface Vetting {
459
359
  }
460
360
 
461
361
  /** Sends a pass whose tests fall short or run red back (`undefined`: it goes on, an overruled one as it is). */
462
- const vetted = async (
463
- tip: KeptTip,
464
- vetting: Vetting,
465
- slice: CriteriaSlice | undefined,
466
- ): Promise<Verdict | undefined> => {
467
- const fault = vetting.waived ? undefined : acceptanceFault(REQUIREMENTS, tip, slice)
362
+ const vetted = async (tip: KeptTip, vetting: Vetting): Promise<Verdict | undefined> => {
363
+ const fault = vetting.waived ? undefined : acceptanceFault(REQUIREMENTS, tip)
468
364
  if (fault !== undefined) {
469
365
  const verdict = await vetting.again({ fault, hash: tip.hash })
470
366
  if (verdict.kind !== "overruled") return verdict
@@ -484,15 +380,15 @@ const ruled = async (tip: KeptTip, again: Vetting["again"]): Promise<Decision |
484
380
  return next.kind === "overruled" ? { reference: tip.hash, tip } : next
485
381
  }
486
382
 
487
- const decide = (slice: CriteriaSlice | undefined): Promise<Decision> =>
383
+ const decide = (): Promise<Decision> =>
488
384
  scope("seam", async () => {
489
385
  const vetting: Vetting = { again: budget(), waived: false }
490
- const naive = phase("naive pass", requireCriteriaIfEdited)
491
386
  let retry: t.NaiveRetry | undefined
492
387
  for (;;) {
493
- const tip = await naiveContinued(retry, slice, naive)
388
+ await naivePass(retry)
389
+ const tip = kept("naive")!
494
390
  const outcome =
495
- (await vetted(tip, vetting, slice)) ??
391
+ (await vetted(tip, vetting)) ??
496
392
  (flagModeOn() ? await ruled(tip, vetting.again) : { reference: tip.hash, tip })
497
393
  if (!("kind" in outcome)) return outcome
498
394
  retry = outcome.kind === "retry" ? outcome.retry : undefined
@@ -504,7 +400,7 @@ export interface SeamOutcome {
504
400
  readonly reference: string
505
401
  /** The naive pass lands as it is: no build turn. */
506
402
  readonly land?: boolean
507
- /** The feature slice's base: HEAD after the refactor on refactor-first, else the caller's base. */
403
+ /** The feature's base: HEAD after the refactor on refactor-first, else the caller's base. */
508
404
  readonly base: string
509
405
  readonly refactored?: boolean
510
406
  /** Set on refactor-first: what the rebuild carries over from the naive pass (the note only after a refactor). */
@@ -533,14 +429,8 @@ export const refactorNote = async (
533
429
  return note
534
430
  }
535
431
 
536
- export interface SeamSlice {
537
- /** Absent for a change built in one slice. */
538
- readonly criteria?: CriteriaSlice | undefined
539
- readonly refactor?: string | undefined
540
- }
541
-
542
- export const seamBuild = async (base: string, slice: SeamSlice = {}): Promise<SeamOutcome> => {
543
- const { reference, tip, shape } = await decide(slice.criteria)
432
+ export const seamBuild = async (base: string): Promise<SeamOutcome> => {
433
+ const { reference, tip, shape } = await decide()
544
434
  if (shape === undefined) return { reference, base, land: true }
545
435
  if (shape.unreadable.length > 0) {
546
436
  await scope("seam", () =>
@@ -550,20 +440,17 @@ export const seamBuild = async (base: string, slice: SeamSlice = {}): Promise<Se
550
440
  acceptClean: true,
551
441
  }),
552
442
  )
553
- return { reference, base, rebuild: { acceptance: naiveAcceptance(tip, slice.criteria) } }
443
+ return { reference, base, rebuild: { acceptance: naiveAcceptance(tip) } }
554
444
  }
555
445
  const strays = refactorStrays(shape)
556
- if (strays.length === 0)
557
- return { reference, base, rebuild: { acceptance: naiveAcceptance(tip, slice.criteria) } }
446
+ if (strays.length === 0) return { reference, base, rebuild: { acceptance: naiveAcceptance(tip) } }
558
447
  const note = await scope("seam", () => refactorNote(reference, strays))
559
- const refactored = await sliced(slice.refactor ?? "refactor", () =>
560
- reviewedRefactor(shape, { reference, note }),
561
- )
448
+ const refactored = await reviewedRefactor(shape, { reference, note })
562
449
  return {
563
450
  reference,
564
451
  base: head(),
565
452
  refactored,
566
453
  // Fixed at `seam.note`, like the package's scope: a later edit of the file reaches neither turn.
567
- rebuild: { note, acceptance: naiveAcceptance(tip, slice.criteria) },
454
+ rebuild: { note, acceptance: naiveAcceptance(tip) },
568
455
  }
569
456
  }
@@ -28,6 +28,10 @@ Feature: the review's ranking is batched to the judge's budget
28
28
  """
29
29
  # Requirements
30
30
 
31
+ ## Impact
32
+
33
+ - the greeting module
34
+
31
35
  ## Acceptance Criteria
32
36
 
33
37
  - AC-1: subtract(a, b) returns a - b.
@@ -31,6 +31,10 @@ Feature: the final review lists the findings that matter most first
31
31
  """
32
32
  # Requirements
33
33
 
34
+ ## Impact
35
+
36
+ - the greeting module
37
+
34
38
  ## Acceptance Criteria
35
39
 
36
40
  - AC-1: subtract(a, b) returns a - b.
@@ -37,6 +37,10 @@ Feature: the review learns standards from the human's answers
37
37
 
38
38
  - `subtract(a, b)` returns `a - b`.
39
39
 
40
+ ## Impact
41
+
42
+ - the greeting module
43
+
40
44
  ## Acceptance Criteria
41
45
 
42
46
  - AC-1: subtract(a, b) returns a - b.
@@ -34,6 +34,10 @@ Feature: a process keeps the risk tier setting it started with when GTD_FLAG_RIS
34
34
 
35
35
  low — a new pure function
36
36
 
37
+ ## Impact
38
+
39
+ - the greeting module
40
+
37
41
  ## Acceptance Criteria
38
42
 
39
43
  - AC-1: the change behaves as described.
@@ -25,7 +25,6 @@ export * from "./feature/criteria.js"
25
25
  export * from "./feature/planning.js"
26
26
  export * from "./feature/refactor.js"
27
27
  export * from "./feature/seam.js"
28
- export * from "./feature/slicing.js"
29
28
  export {
30
29
  buildTail,
31
30
  type BuildTailOptions,
@@ -41,19 +41,6 @@ export interface LensReport {
41
41
 
42
42
  export const NO_FINDINGS: LensReport = { notes: [], fixed: [], noise: [] }
43
43
 
44
- /** `reports` as one, each finding labelled with the slice it came from, in slice order. */
45
- export const joinReports = (
46
- reports: readonly { readonly slice: string; readonly report: LensReport }[],
47
- ): LensReport => {
48
- const tag = <F extends Origin>(slice: string, fs: readonly F[]): F[] =>
49
- fs.map((f) => ({ ...f, slice }))
50
- return {
51
- notes: reports.flatMap((r) => tag(r.slice, r.report.notes)),
52
- fixed: reports.flatMap((r) => tag(r.slice, r.report.fixed)),
53
- noise: reports.flatMap((r) => tag(r.slice, r.report.noise)),
54
- }
55
- }
56
-
57
44
  const UNTITLED = "Quality findings"
58
45
 
59
46
  const below = (section: string): string => section.split("\n").slice(1).join("\n").trim()
@@ -8,9 +8,8 @@ const { defaults, envDefaults, feature, fix, review, ShippedOptions } = workflow
8
8
  const { flagShipped } = options
9
9
 
10
10
  describe("the shipped workflow factories", () => {
11
- it("take exactly the settings the shipped workflows declare as options, and contextLimit", () => {
12
- const settings = Object.keys(ShippedOptions.fields).filter((name) => name !== "contextLimit")
13
- expect(settings.sort()).toEqual(
11
+ it("take exactly the settings the shipped workflows declare as options", () => {
12
+ expect(Object.keys(ShippedOptions.fields).sort()).toEqual(
14
13
  [...Object.keys(defaults), ...Object.keys(envDefaults)]
15
14
  .filter((name) => !name.startsWith("flag_"))
16
15
  .sort(),
@@ -24,14 +23,6 @@ describe("the shipped workflow factories", () => {
24
23
  expect(defaults).toHaveProperty(`flag_${name}`, defaultOn ? "1" : "0")
25
24
  })
26
25
 
27
- it("keep contextLimit out of the settings", () => {
28
- const built = configure(feature({ contextLimit: { hard: 0.8 } }))
29
- expect(built).toMatchObject({ ok: true })
30
- const build = built?.ok === true ? built.build : undefined
31
- expect(build?.defaults).not.toHaveProperty("contextLimit")
32
- expect(build?.envDefaults).not.toHaveProperty("contextLimit")
33
- })
34
-
35
26
  it("declare a seamAllow default a factory option overrides", () => {
36
27
  expect(configure(feature({ seamAllow: "docs/**" }))).toMatchObject({
37
28
  ok: true,
@@ -6,7 +6,6 @@ import {
6
6
  type WorkflowDoors,
7
7
  type WorkflowFactory,
8
8
  refuse,
9
- scope,
10
9
  vars,
11
10
  } from "../../flows/index.js"
12
11
  import { defaultFlagRegistry, defaults, envDefaults } from "./vars.js"
@@ -23,23 +22,13 @@ export const ShippedOptions = Schema.Struct({
23
22
  standardMinP: setting,
24
23
  refactorFlagPattern: setting,
25
24
  seamAllow: setting,
26
- sliceMaxCriteria: setting,
27
25
  qualityReviews: setting,
28
26
  refactorQualityReviews: setting,
29
- continuations: setting,
30
27
  prototypeRounds: setting,
31
- continuationStall: setting,
32
28
  testCommand: setting,
33
29
  plannerModel: setting,
34
30
  coderModel: setting,
35
31
  flagMode: Schema.optionalWith(Schema.Boolean, { exact: true }),
36
- contextLimit: Schema.optionalWith(
37
- Schema.Struct({
38
- soft: Schema.optionalWith(Schema.Number, { exact: true }),
39
- hard: Schema.optionalWith(Schema.Number, { exact: true }),
40
- }),
41
- { exact: true },
42
- ),
43
32
  })
44
33
 
45
34
  export type ShippedOptions = Schema.Schema.Encoded<typeof ShippedOptions>
@@ -64,12 +53,6 @@ export const flagModeOn = (): boolean => {
64
53
  return value === "true"
65
54
  }
66
55
 
67
- // Not a setting: a factory option is known at load, so the scope wraps the
68
- // whole flow without reading a setting before its first step. The engine
69
- // refuses limits it cannot use when it reaches an agent step.
70
- const limited = (flow: Flow, context: ShippedOptions["contextLimit"]): Flow =>
71
- context === undefined ? flow : () => scope({ context }, flow)
72
-
73
56
  /** A shipped flow as a factory: its options override the settings it declares, each in its own kind. */
74
57
  export const shipped = (
75
58
  flow: Flow,
@@ -78,8 +61,8 @@ export const shipped = (
78
61
  ): WorkflowFactory<ShippedOptions> =>
79
62
  defineWorkflow(
80
63
  Schema.standardSchemaV1(ShippedOptions, { onExcessProperty: "error", errors: "all" }),
81
- ({ contextLimit, ...options }) => ({
82
- flow: limited(flow, contextLimit),
64
+ (options) => ({
65
+ flow,
83
66
  defaults: over(defaults, options),
84
67
  envDefaults: over(envDefaults, options),
85
68
  doors,
@@ -97,10 +80,10 @@ export const flagShipped = (
97
80
  ): WorkflowFactory<FlagOptions> =>
98
81
  defineWorkflow(
99
82
  Schema.standardSchemaV1(FlagOptions, { onExcessProperty: "error", errors: "all" }),
100
- ({ contextLimit, ...options }) => {
83
+ (options) => {
101
84
  const flagDefaults = { ...defaults, flag: "", flagRegistry: defaultFlagRegistry }
102
85
  return {
103
- flow: limited(flow, contextLimit),
86
+ flow,
104
87
  defaults: over(flagDefaults, options),
105
88
  envDefaults: over(envDefaults, options),
106
89
  doors: { [name]: { args: [{ name: "flag" }], vars: ({ flag }) => ({ flag: flag ?? "" }) } },