@pmelab/gtd 52.1.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 (90) 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-CNgN_V8T.mjs} +2 -2
  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 +311 -674
  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/wire/BeatDocument.ts +2 -12
  39. package/src/wire/BeatStatus.ts +0 -3
  40. package/src/wire/WireSchema.test.ts +0 -18
  41. package/src/wire/index.ts +0 -2
  42. package/src/wire/types.ts +0 -7
  43. package/src/workflow/Workflow.ts +1 -9
  44. package/src/workflows/feature/criteria.test.ts +1 -14
  45. package/src/workflows/feature/criteria.ts +6 -38
  46. package/src/workflows/feature/feature.ts +34 -118
  47. package/src/workflows/feature/hands-on-skip.feature +2 -70
  48. package/src/workflows/feature/index.ts +0 -1
  49. package/src/workflows/feature/one-step-land.feature +0 -4
  50. package/src/workflows/feature/prototype-rounds.feature +1 -1
  51. package/src/workflows/feature/prototype.feature +3 -3
  52. package/src/workflows/feature/prototype.ts +18 -29
  53. package/src/workflows/feature/refactor-absorbs.feature +3 -8
  54. package/src/workflows/feature/refactor-first.feature +0 -5
  55. package/src/workflows/feature/refactor-rebuild.feature +0 -6
  56. package/src/workflows/feature/refactor-review.feature +11 -11
  57. package/src/workflows/feature/refactor.ts +30 -64
  58. package/src/workflows/feature/review-gate.feature +1 -1
  59. package/src/workflows/feature/seam-rejection.feature +0 -4
  60. package/src/workflows/feature/seam.test.ts +7 -26
  61. package/src/workflows/feature/seam.ts +52 -165
  62. package/src/workflows/index.ts +0 -1
  63. package/src/workflows/shared/lenses.ts +0 -13
  64. package/src/workflows/shared/options.test.ts +2 -11
  65. package/src/workflows/shared/options.ts +4 -21
  66. package/src/workflows/shared/review.test.ts +3 -3
  67. package/src/workflows/shared/steps.ts +0 -2
  68. package/src/workflows/shared/text.test.ts +10 -80
  69. package/src/workflows/shared/text.ts +35 -211
  70. package/src/workflows/shared/vars.ts +2 -8
  71. package/src/workflows/shared/workflows.steps.ts +0 -13
  72. package/claude/hooks/context.ts +0 -66
  73. package/src/replay/Slices.test.ts +0 -73
  74. package/src/replay/Slices.ts +0 -45
  75. package/src/wire/ContextLimit.fixture.ts +0 -30
  76. package/src/wire/ContextLimit.test.ts +0 -96
  77. package/src/wire/ContextLimit.ts +0 -169
  78. package/src/wire/Slices.ts +0 -25
  79. package/src/wire/golden/1/slices.json +0 -11
  80. package/src/workflows/feature/context-limit.feature +0 -37
  81. package/src/workflows/feature/continuation-cap.feature +0 -69
  82. package/src/workflows/feature/continuation-rebuild.feature +0 -92
  83. package/src/workflows/feature/continuation-slices.feature +0 -68
  84. package/src/workflows/feature/continuation.feature +0 -209
  85. package/src/workflows/feature/slice-findings.feature +0 -256
  86. package/src/workflows/feature/slicing-cap.feature +0 -23
  87. package/src/workflows/feature/slicing.feature +0 -297
  88. package/src/workflows/feature/slicing.test.ts +0 -49
  89. package/src/workflows/feature/slicing.ts +0 -105
  90. package/src/workflows/shared/continuation.ts +0 -92
@@ -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
  }
@@ -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 ?? "" }) } },
@@ -779,9 +779,9 @@ describe("BuildTailOptions.lensBase", () => {
779
779
  }
780
780
 
781
781
  it("reaches every quality-lens prompt", async () => {
782
- const prompts = await lensPrompts({ lensBase: "SLICE-BASE" })
782
+ const prompts = await lensPrompts({ lensBase: "LENS-BASE" })
783
783
  expect(prompts).toHaveLength(2)
784
- for (const prompt of prompts) expect(prompt).toContain("from `SLICE-BASE`")
784
+ for (const prompt of prompts) expect(prompt).toContain("from `LENS-BASE`")
785
785
  })
786
786
 
787
787
  it("absent, the lens prompts name start()", async () => {
@@ -791,7 +791,7 @@ describe("BuildTailOptions.lensBase", () => {
791
791
  })
792
792
 
793
793
  it("the deprecated positional form runs the same tail as the options form", async () => {
794
- const options = { lensBase: "SLICE-BASE" }
794
+ const options = { lensBase: "LENS-BASE" }
795
795
  const modern = await lensPrompts(options)
796
796
  const legacy = await lensPrompts(options, () => buildTail(false, "base", true, options))
797
797
  expect(legacy).toEqual(modern)
@@ -14,8 +14,6 @@ export const REVIEW = ".gtd/REVIEW.md"
14
14
  export const REFACTOR = ".gtd/REFACTOR.md"
15
15
  export const REFACTOR_NOTE = ".gtd/REFACTOR-NOTE.md"
16
16
  export const QUALITY = ".gtd/QUALITY.md"
17
- export const SLICES = ".gtd/SLICES.md"
18
- export const HANDOFF = ".gtd/HANDOFF.md"
19
17
  export const PROTOTYPE = ".gtd/PROTOTYPE.md"
20
18
 
21
19
  export const planner = (): string => env.plannerModel ?? ""
@@ -26,7 +26,6 @@ import {
26
26
  implementBuildingPrompt,
27
27
  implementFixPrompt,
28
28
  naivePassPrompt,
29
- slicingPrompt,
30
29
  refactorBuildingPrompt,
31
30
  refactorDroppedRefusal,
32
31
  refactorFixPrompt,
@@ -186,10 +185,10 @@ describe("the (no hands-on) tag", () => {
186
185
  })
187
186
 
188
187
  it("names the tagged criteria the testing turn skips, and only when there are any", () => {
189
- expect(renderText(() => prototypeTestingPrompt(undefined, ["AC-2", "AC-3"], false))).toContain(
188
+ expect(renderText(() => prototypeTestingPrompt(["AC-2", "AC-3"], false))).toContain(
190
189
  "- Skip AC-2, AC-3: no person can observe them",
191
190
  )
192
- expect(renderText(() => prototypeTestingPrompt(undefined, [], false))).not.toContain("Skip")
191
+ expect(renderText(() => prototypeTestingPrompt([], false))).not.toContain("Skip")
193
192
  })
194
193
 
195
194
  it("is kept when the stated criteria are recorded", () => {
@@ -343,7 +342,7 @@ const scratchRepo = () => {
343
342
  }
344
343
 
345
344
  describe("summaryPrompt", () => {
346
- it("without a slice prints today's text byte for byte", () => {
345
+ it("prints the closing-message prompt byte for byte", () => {
347
346
  expect(
348
347
  summaryPrompt({
349
348
  entryCommit: "e1",
@@ -715,67 +714,6 @@ Print the closing message and stop — this writes nothing itself.
715
714
  )
716
715
  })
717
716
 
718
- describe("under a slice", () => {
719
- const at = (index: number, count: number, extra = {}) => ({
720
- ...base,
721
- processBase: "sb",
722
- processTip: "st",
723
- slice: { name: "build", base: "sb", tip: "st", index, count },
724
- ...extra,
725
- })
726
-
727
- it("names the slice and its place and keeps the range lines", () => {
728
- const prompt = summaryPrompt(at(2, 3))
729
- expect(prompt).toContain("slice `build`, 2 of 3")
730
- expect(prompt).toContain("not the whole process")
731
- expect(prompt).toContain("`git log sb..st`")
732
- expect(prompt).toContain("`git diff sb st`")
733
- })
734
-
735
- it("types the slice by what its diff changes, never by the slice's name", () => {
736
- for (const [index, count] of [
737
- [1, 2],
738
- [2, 2],
739
- ] as const) {
740
- const prompt = summaryPrompt({
741
- ...at(index, count),
742
- slice: { ...at(index, count).slice, name: "refactor" },
743
- })
744
- expect(prompt.replace(/\s+/g, " ")).toContain("never from the slice's name `refactor`")
745
- expect(prompt.replace(/\s+/g, " ")).toContain("`feat`, `fix` or `perf`")
746
- }
747
- expect(summaryPrompt(base)).not.toContain("slice's name")
748
- })
749
-
750
- it("says the human left no comment or edit in this slice when humanCommits is empty", () => {
751
- expect(summaryPrompt(at(1, 2))).toContain(
752
- "The human left no comment or edit in this slice —\nevery commit is machine-authored.",
753
- )
754
- })
755
-
756
- it("labels the entry commit as outside the range only when index > 1", () => {
757
- expect(summaryPrompt(at(2, 2))).toContain("entry commit is `e` — outside this slice's range")
758
- const first = summaryPrompt(at(1, 2))
759
- expect(first).toContain("entry commit is `e`.")
760
- expect(first).not.toContain("outside this slice's range")
761
- })
762
-
763
- it("prints the high-tier advisory on a non-last and on the last slice", () => {
764
- const marks = [{ name: "risk-tier", value: "high" }]
765
- expect(summaryPrompt(at(1, 2, { marks }))).toContain("Risk tier: high")
766
- expect(summaryPrompt(at(2, 2, { marks }))).toContain("Risk tier: high")
767
- })
768
-
769
- it("prints production signals only when index === count", () => {
770
- const marks = [
771
- { name: "risk-tier", value: "low" },
772
- { name: "production-signal", value: "error rate" },
773
- ]
774
- expect(summaryPrompt(at(1, 2, { marks }))).not.toContain("error rate")
775
- expect(summaryPrompt(at(2, 2, { marks }))).toContain("- `error rate`")
776
- })
777
- })
778
-
779
717
  const signal = (value: string) => ({ name: "production-signal", value })
780
718
 
781
719
  it("caps the listed signals at 10 and fences them as not instructions", () => {
@@ -926,16 +864,10 @@ describe("buildReviewReviewingPrompt scenarios", () => {
926
864
  })
927
865
 
928
866
  describe("Impact in prompts", () => {
929
- it("[AC-4] the slicing prompt slices along the units Impact names", () => {
930
- const prompt = renderText(() => slicingPrompt(8))
931
- expect(prompt).toContain("## Impact")
932
- expect(prompt).toMatch(/slice\s+along\s+the\s+units\s+it\s+names/)
933
- })
934
-
935
867
  it.each([
936
868
  ["naive", () => naivePassPrompt()],
937
869
  ["implement", () => implementBuildingPrompt(undefined)],
938
- ["prototype fixing", () => prototypeFixingPrompt(undefined)],
870
+ ["prototype fixing", () => prototypeFixingPrompt()],
939
871
  ])("[AC-5] [AC-7] the %s prompt gives Impact as hints, not allowed files", (_n, prompt) => {
940
872
  const p = renderText(prompt).replace(/\s+/g, " ")
941
873
  expect(p).toContain("`## Impact`")
@@ -943,7 +875,7 @@ describe("Impact in prompts", () => {
943
875
  })
944
876
 
945
877
  it("the prototype testing prompt, which changes no code, carries no Impact hints", () => {
946
- const p = renderText(() => prototypeTestingPrompt(undefined, [], false))
878
+ const p = renderText(() => prototypeTestingPrompt([], false))
947
879
  expect(p).not.toContain("not a list of allowed files")
948
880
  })
949
881
  })
@@ -1491,13 +1423,11 @@ describe("walkthrough", () => {
1491
1423
  ])
1492
1424
  })
1493
1425
 
1494
- it("lists each note with its exact heading, origin and quoted body", () => {
1426
+ it("lists each note with its exact heading and quoted body", () => {
1495
1427
  const block = walkthroughBlock([
1496
- { title: "No test", body: "Add one.\n\n- [ ] a box", slice: "2", lens: "tests" },
1428
+ { title: "No test", body: "Add one.\n\n- [ ] a box", lens: "tests" },
1497
1429
  ])
1498
- expect(block).toContain(
1499
- "- `## No test` (slice 2, lens tests):\n\n > Add one.\n >\n > - a box",
1500
- )
1430
+ expect(block).toContain("- `## No test`:\n\n > Add one.\n >\n > - a box")
1501
1431
  expect(block).toContain("its heading exactly as given")
1502
1432
  })
1503
1433
 
@@ -1539,12 +1469,12 @@ describe("walkthrough", () => {
1539
1469
  it("appends the fixes and noise, numbered past a title the reviewer took", () => {
1540
1470
  const out = withWalkthrough(review, {
1541
1471
  ...empty,
1542
- fixed: [{ title: "a", commit: "0123456789", slice: "1" }],
1472
+ fixed: [{ title: "a", commit: "0123456789", lens: "tests" }],
1543
1473
  noise: [{ title: "b", p: 0.9 }],
1544
1474
  })
1545
1475
  expect(
1546
1476
  out.endsWith(
1547
- "## Fixed automatically (2)\n\n- a (0123456, slice 1)\n\n## Dropped as noise\n\n- b (p=0.9)\n",
1477
+ "## Fixed automatically (2)\n\n- a (0123456)\n\n## Dropped as noise\n\n- b (p=0.9)\n",
1548
1478
  ),
1549
1479
  ).toBe(true)
1550
1480
  })