@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,7 +5,6 @@ import type {
5
5
  RenderedDemandSource,
6
6
  StateMode,
7
7
  StateName,
8
- StepContextLimit,
9
8
  } from "./types.js"
10
9
 
11
10
  /** One pending change — `gtd next --json`'s `changes` entries. */
@@ -33,7 +32,6 @@ export interface BeatStatus {
33
32
  readonly initial: boolean
34
33
  readonly model: string | undefined
35
34
  readonly system: string | undefined
36
- readonly context: StepContextLimit | undefined
37
35
  readonly log: string
38
36
  readonly state: StateName
39
37
  readonly actor: Actor
@@ -76,7 +74,6 @@ export const statusOf = (input: {
76
74
  initial,
77
75
  model: rendered.model,
78
76
  system: rendered.system !== undefined && rendered.system !== "" ? rendered.system : undefined,
79
- context: rendered.context,
80
77
  log,
81
78
  state: rendered.state,
82
79
  actor: rendered.actor,
@@ -9,8 +9,6 @@ import {
9
9
  landFields,
10
10
  renderBeatJson,
11
11
  renderLandJson,
12
- renderSlicesJson,
13
- slicesDocument,
14
12
  statusOf,
15
13
  WIRE_SCHEMA,
16
14
  type BeatKind,
@@ -118,9 +116,6 @@ const documents: Readonly<Record<string, string>> = {
118
116
  }),
119
117
  ),
120
118
  "judge.json": JUDGE,
121
- "slices.json": renderSlicesJson(
122
- slicesDocument([{ name: "build", base: "a".repeat(40), tip: "b".repeat(40), closed: true }]),
123
- ),
124
119
  }
125
120
 
126
121
  describe(`wire schema ${WIRE_SCHEMA}`, () => {
@@ -144,19 +139,6 @@ describe(`wire schema ${WIRE_SCHEMA}`, () => {
144
139
  }
145
140
  })
146
141
 
147
- describe("slicesDocument", () => {
148
- it("stamps WIRE_SCHEMA and drops any field beyond the four", () => {
149
- const doc = slicesDocument([
150
- { name: "n", base: "b", tip: "t", closed: false, extra: 1 } as never,
151
- ])
152
- expect(doc).toEqual({
153
- schema: WIRE_SCHEMA,
154
- slices: [{ name: "n", base: "b", tip: "t", closed: false }],
155
- })
156
- expect(Object.keys(doc.slices[0]!)).toEqual(["name", "base", "tip", "closed"])
157
- })
158
- })
159
-
160
142
  describe("the shape check", () => {
161
143
  const land = JSON.parse(documents["land.json"]!) as Record<string, unknown>
162
144
  const { subject, ...rest } = land
package/src/wire/index.ts CHANGED
@@ -10,5 +10,3 @@ export * from "./Judge.js"
10
10
  export { selectPath } from "./Select.js"
11
11
  export { resolveSession } from "./Sessions.js"
12
12
  export { globMatches } from "./Glob.js"
13
- export * from "./Slices.js"
14
- export * from "./ContextLimit.js"
package/src/wire/types.ts CHANGED
@@ -38,12 +38,6 @@ export interface ApprovalEntry {
38
38
  readonly commit: string
39
39
  }
40
40
 
41
- /** The context-limit shares a step sets; a driver fills an absent one from its defaults. */
42
- export interface StepContextLimit {
43
- readonly soft?: number
44
- readonly hard?: number
45
- }
46
-
47
41
  export interface RenderedDemandSource {
48
42
  readonly state: StateName
49
43
  readonly actor: Actor
@@ -57,5 +51,4 @@ export interface RenderedDemandSource {
57
51
  readonly judge?: string
58
52
  readonly skills?: readonly string[]
59
53
  readonly access?: StepAccess
60
- readonly context?: StepContextLimit
61
54
  }
@@ -1,12 +1,5 @@
1
1
  import type { WorkflowBase, Flow, ScopeAccess, Summary } from "../flows/index.js"
2
- import type {
3
- Actor,
4
- ContentKind,
5
- StateMode,
6
- StateName,
7
- StepAccess,
8
- StepContextLimit,
9
- } from "../wire/index.js"
2
+ import type { Actor, ContentKind, StateMode, StateName, StepAccess } from "../wire/index.js"
10
3
 
11
4
  export type { Actor, ContentKind, StateMode, StateName }
12
5
 
@@ -51,7 +44,6 @@ export interface StepDef {
51
44
  readonly approve?: boolean
52
45
  readonly skills?: readonly string[]
53
46
  readonly access?: StepAccess
54
- readonly context?: StepContextLimit
55
47
  }
56
48
 
57
49
  /**
@@ -139,17 +139,9 @@ describe("statedCriteria", () => {
139
139
  describe("criterionIds", () => {
140
140
  const requirements = "## Acceptance Criteria\n\n- AC-1: a\n- AC-2: b\n- AC-4: added\n- unnamed\n"
141
141
 
142
- it("lists every ID'd criterion of an unsliced change", () => {
142
+ it("lists every ID'd criterion", () => {
143
143
  expect(criterionIds(requirements)).toEqual(["AC-1", "AC-2", "AC-4"])
144
144
  })
145
-
146
- it("keeps `only` to the IDs the file still lists", () => {
147
- expect(criterionIds(requirements, { only: ["AC-1", "AC-3"] })).toEqual(["AC-1"])
148
- })
149
-
150
- it("gives `except` every other ID, one added since included", () => {
151
- expect(criterionIds(requirements, { except: ["AC-1", "AC-3"] })).toEqual(["AC-2", "AC-4"])
152
- })
153
145
  })
154
146
 
155
147
  describe("handsOnIds", () => {
@@ -159,11 +151,6 @@ describe("handsOnIds", () => {
159
151
  it("leaves out every criterion tagged (no hands-on)", () => {
160
152
  expect(handsOnIds(requirements)).toEqual(["AC-1", "AC-3"])
161
153
  })
162
-
163
- it("keeps to the slice", () => {
164
- expect(handsOnIds(requirements, { only: ["AC-2", "AC-3"] })).toEqual(["AC-3"])
165
- expect(handsOnIds(requirements, { only: ["AC-2", "AC-4"] })).toEqual([])
166
- })
167
154
  })
168
155
 
169
156
  describe("unstableIds", () => {
@@ -79,37 +79,16 @@ export const criteriaOf = (text: string): readonly Criterion[] | undefined =>
79
79
  return id === null ? { id: "", text: item } : criterion(id)
80
80
  })
81
81
 
82
- /**
83
- * Which criteria one slice builds, resolved against the file whenever read:
84
- * `only` drops an ID a steer deleted, `except` (the last slice) takes one a
85
- * steer added.
86
- */
87
- export interface CriteriaSlice {
88
- readonly only?: readonly string[]
89
- readonly except?: readonly string[]
90
- }
91
-
92
- /** The ID'd criteria of `text`, only `slice`'s when given. */
93
- export const criterionIds = (text: string, slice?: CriteriaSlice): string[] =>
94
- (criteriaOf(text) ?? [])
95
- .map((c) => c.id)
96
- .filter(
97
- (id) =>
98
- id !== "" &&
99
- (slice?.only === undefined || slice.only.includes(id)) &&
100
- !(slice?.except ?? []).includes(id),
101
- )
82
+ /** The ID'd criteria of `text`. */
83
+ export const criterionIds = (text: string): string[] =>
84
+ (criteriaOf(text) ?? []).map((c) => c.id).filter((id) => id !== "")
102
85
 
103
- /** The ID'd criteria of `text` (`slice`'s when given) a person can try by hand: none tagged `(no hands-on)`. */
104
- export const handsOnIds = (text: string, slice?: CriteriaSlice): string[] => {
86
+ /** The ID'd criteria of `text` a person can try by hand: none tagged `(no hands-on)`. */
87
+ export const handsOnIds = (text: string): string[] => {
105
88
  const tagged = (criteriaOf(text) ?? []).filter((c) => c.noHandsOn).map((c) => c.id)
106
- return criterionIds(text, slice).filter((id) => !tagged.includes(id))
89
+ return criterionIds(text).filter((id) => !tagged.includes(id))
107
90
  }
108
91
 
109
- /** `slice`'s IDs in `.gtd/REQUIREMENTS.md` as it is now; `undefined` for an unsliced change. */
110
- export const sliceIds = (slice: CriteriaSlice | undefined): string[] | undefined =>
111
- slice === undefined ? undefined : criterionIds(read(REQUIREMENTS) ?? "", slice)
112
-
113
92
  /** Items numbered `AC-1`…, unless every one already carries its own distinct ID. */
114
93
  const numbered = (items: readonly string[]): Criterion[] => {
115
94
  const ids = items.map((item) => ID.exec(item))
@@ -292,14 +271,3 @@ export const skipDesign = (stated: StatedCriteria | undefined): Promise<boolean>
292
271
  await impact()
293
272
  return true
294
273
  })
295
-
296
- /** After a gate that invites narrowing the requirements: an edit must leave valid criteria. */
297
- export const requireCriteriaIfEdited = (): void => {
298
- const change = changes().get(REQUIREMENTS)
299
- if (change === undefined) return
300
- if (change.status === "deleted")
301
- refuse(
302
- `gtd land: criteria-missing: ${REQUIREMENTS} must stay — narrow its criteria, never delete it`,
303
- )
304
- requireCriteria(REQUIREMENTS)
305
- }
@@ -6,44 +6,36 @@ import {
6
6
  read,
7
7
  revertScript,
8
8
  run,
9
- slice,
10
9
  start,
11
10
  } from "../../flows/index.js"
12
11
  import { gate } from "../shared/health.js"
13
- import { joinReports, type LensReport } from "../shared/lenses.js"
14
12
  import { flagModeOn, shipped } from "../shared/options.js"
15
13
  import { FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
16
14
  import { refusePendingHandOff, tail } from "../shared/tail.js"
17
15
  import * as t from "../shared/text.js"
18
16
  import {
19
17
  criterionIds,
20
- requireCriteriaFrozen,
21
18
  requireStableIds,
22
19
  type StableIds,
23
20
  skipDesign,
24
- sliceIds,
25
21
  statedCriteria,
26
- type CriteriaSlice,
27
22
  type StatedCriteria,
28
23
  } from "./criteria.js"
29
24
  import { design } from "./planning.js"
30
25
  import { buildTail, humanReview, isCode, reUnwind, type ReviewOutcome } from "../shared/review.js"
31
26
  import { prototypeRounds } from "./prototype.js"
32
27
  import { implement, implementing, seamBuild } from "./seam.js"
33
- import { sliceCap, sliceCriteria, slicing } from "./slicing.js"
34
- import { recordRisk, riskTierOn, type RiskTier } from "./tier.js"
28
+ import { recordRisk, riskTierOn } from "./tier.js"
35
29
 
36
30
  // The ordinary start. Any change to the tree starts a process: `idle` →
37
31
  // `unwind` reverts the sketch (its intent survives in history) → a
38
- // green-baseline gate → design → per slice of the criteria, a naive pass,
39
- // judged with `flagMode` on: it lands as the implementation, or (refactoring
40
- // first when the judge asks) is built fresh from → the health loop → a
41
- // hands-on test of every criterion, fixed and re-tested → the
42
- // triaged quality lap, whose leftovers are carried on → after the last slice,
43
- // a reviewer's walkthrough of the whole change, every slice's findings
44
- // among its chunks, for the human, which
45
- // signs off (the episode ends back at `idle`) or sends a design → implement
46
- // lap.
32
+ // green-baseline gate → design → a naive pass, judged with `flagMode` on: it
33
+ // lands as the implementation, or (refactoring first when the judge asks) is
34
+ // built fresh from → the health loop → a hands-on test of every criterion,
35
+ // fixed and re-tested → the triaged quality lap, whose leftovers are carried
36
+ // on → a reviewer's walkthrough of the whole change, its findings among its
37
+ // chunks, for the human, which signs off (the episode ends back at `idle`) or
38
+ // sends a design → implement lap.
47
39
 
48
40
  /** Revert the sketch that started the process out of the working tree; its intent survives in history. */
49
41
  export const unwind = (): Promise<void> => {
@@ -56,36 +48,32 @@ export const unwind = (): Promise<void> => {
56
48
  export { reUnwind }
57
49
 
58
50
  /**
59
- * The slices `design.slicing` grouped the criteria into, each built in turn
60
- * from where the previous one landed; design must have run since `firstBase`.
51
+ * A naive pass (judged only with `flagMode` on), then implement and the
52
+ * quality lap, then the review; design must have run since `firstBase`.
61
53
  */
62
54
  export const buildFeature = async (firstBase: string): Promise<void> => {
63
55
  const tier = riskTierOn() ? recordRisk() : undefined
64
- const slices = await slicing()
65
- // Every ID handed out, gathered before a later narrowing can drop one: none is ever reused.
56
+ // Every ID handed out, gathered before a later edit can drop one: none is ever reused.
66
57
  const handedOut = new Set(listedIds())
67
- // A single slice keeps the unnumbered names and builds against every criterion.
68
- const one = slices.length === 1
69
- // A steer may delete every criterion of a later slice: there is nothing to build there.
70
- const built = (i: number): boolean => sliceIds(sliceCriteria(slices, i))?.length !== 0
71
- const reports: { slice: string; report: LensReport }[] = []
72
- for (const i of slices.keys()) {
73
- if (!built(i)) continue
74
- const n = one ? "" : `-${i + 1}`
75
- await buildSlice(i === 0 ? firstBase : head(), {
76
- number: `${i + 1}`,
77
- tier,
78
- criteria: sliceCriteria(slices, i),
79
- refactor: `refactor${n}`,
80
- feature: `feature${n}`,
81
- // A steer or a narrowing may empty every later slice.
82
- more: () => slices.some((_, j) => j > i && built(j)),
83
- // Every slice but the one is built unreviewed, so the review covers them all.
84
- reviewBase: one ? undefined : firstBase,
85
- handedOut,
86
- laps: reports,
87
- })
88
- }
58
+ const seam = await seamBuild(firstBase)
59
+ const used = gather(handedOut)
60
+ const closed = await implementing({
61
+ reference: seam.reference,
62
+ tier,
63
+ afterRefactor: seam.refactored,
64
+ naive: seam.land === true ? seam.reference : undefined,
65
+ rebuild: seam.rebuild,
66
+ })
67
+ // A feature with no code beyond the carried-over tests: the refactor carried
68
+ // it, so the lap covers the whole change.
69
+ const carried = seam.rebuild?.acceptance?.tests ?? []
70
+ const base =
71
+ seam.refactored === true &&
72
+ !changesSince(seam.base).some((c) => isCode(c.path) && !carried.includes(c.path))
73
+ ? firstBase
74
+ : seam.base
75
+ const outcome = await reviewedTail(base, base)
76
+ await featureFeedback(outcome, base, { closed, used })
89
77
  }
90
78
 
91
79
  const listedIds = (): readonly string[] => criterionIds(read(REQUIREMENTS) ?? "")
@@ -95,85 +83,14 @@ const gather = (handedOut: Set<string>): readonly string[] => {
95
83
  return [...handedOut]
96
84
  }
97
85
 
98
- interface SliceBuild {
99
- /** The slice's number, from 1. */
100
- readonly number: string
101
- readonly tier: RiskTier | undefined
102
- readonly criteria: CriteriaSlice | undefined
103
- readonly refactor: string
104
- readonly feature: string
105
- readonly more: () => boolean
106
- /** Absent: the slice's own base. */
107
- readonly reviewBase?: string | undefined
108
- /** Every criterion ID handed out so far; the slice adds those a steer adds. */
109
- readonly handedOut: Set<string>
110
- /** Each earlier slice's lap report, by slice number, for the review after the last one. */
111
- readonly laps: { slice: string; report: LensReport }[]
112
- }
113
-
114
- /** A naive pass (judged only with `flagMode` on), then implement and the quality lap; after the last slice, the review. */
115
- const buildSlice = async (sliceBase: string, build: SliceBuild): Promise<void> => {
116
- const seam = await seamBuild(sliceBase, { criteria: build.criteria, refactor: build.refactor })
117
- const used = gather(build.handedOut)
118
- await slice(build.feature, async () => {
119
- const closed = await implementing({
120
- reference: seam.reference,
121
- tier: build.tier,
122
- afterRefactor: seam.refactored,
123
- naive: seam.land === true ? seam.reference : undefined,
124
- rebuild: seam.rebuild,
125
- slice: build.criteria,
126
- more: build.more,
127
- })
128
- // Closing kept the criteria exactly when there is more to build, so this reads as it did.
129
- const more = build.more()
130
- // A feature slice with no code beyond the carried-over tests: the refactor carried the
131
- // feature, so the lap covers the whole slice.
132
- const carried = seam.rebuild?.acceptance?.tests ?? []
133
- const base =
134
- seam.refactored === true &&
135
- !changesSince(seam.base).some((c) => isCode(c.path) && !carried.includes(c.path))
136
- ? sliceBase
137
- : seam.base
138
- if (more) {
139
- // The criteria stay in the tree for the next slice; no quality fix may move them.
140
- const guard = () => requireCriteriaFrozen(REQUIREMENTS)
141
- // No gate between slices: what the lap left waits for the review after the last one.
142
- const report = await tail(async (_, report) => report, {
143
- green: true,
144
- lensBase: base,
145
- guard,
146
- guardRests: false,
147
- })
148
- build.laps.push({ slice: build.number, report })
149
- return
150
- }
151
- const reviewBase = build.reviewBase ?? base
152
- const outcome = await reviewedTail(reviewBase, base, { slice: build.number, laps: build.laps })
153
- await featureFeedback(outcome, base, { closed, used })
154
- })
155
- }
156
-
157
- /**
158
- * The quality lap of slice `last.slice`, then a reviewer's walkthrough since
159
- * `base` for the human, carrying what earlier slices' `laps` left too.
160
- */
86
+ /** The quality lap, then a reviewer's walkthrough since `base` for the human. */
161
87
  const reviewedTail = (
162
88
  base: string,
163
89
  lensBase: string,
164
- last: { readonly slice: string; readonly laps: SliceBuild["laps"] } = { slice: "", laps: [] },
165
90
  lap: "first" | "feedback" = "first",
166
91
  ): Promise<ReviewOutcome> =>
167
92
  tail(
168
- (escalations, report) =>
169
- humanReview(base, escalations, {
170
- report:
171
- last.laps.length === 0
172
- ? report
173
- : joinReports([...last.laps, { slice: last.slice, report }]),
174
- walkthrough: true,
175
- lap,
176
- }),
93
+ (escalations, report) => humanReview(base, escalations, { report, walkthrough: true, lap }),
177
94
  { green: true, lensBase },
178
95
  )
179
96
 
@@ -197,7 +114,7 @@ const featureFeedback = async (
197
114
  })
198
115
  const tier = riskTierOn() ? recordRisk() : undefined
199
116
  closed = await implementing({ tier, amend: true })
200
- outcome = await reviewedTail(outcome.base, lensBase, undefined, "feedback")
117
+ outcome = await reviewedTail(outcome.base, lensBase, "feedback")
201
118
  }
202
119
  }
203
120
 
@@ -209,7 +126,7 @@ export const planAndBuild = async (firstBase: string, stated?: StatedCriteria):
209
126
  }
210
127
 
211
128
  // seam.naive / seam.judge never run here: a re-plan's seam is built unjudged and unrefactored.
212
- /** After a tail that skipped planning: stop on sign-off, re-plan on feedback. Opens no slice: slices cannot nest. */
129
+ /** After a tail that skipped planning: stop on sign-off, re-plan on feedback. */
213
130
  export const afterTail = async (first: ReviewOutcome): Promise<void> => {
214
131
  let outcome = first
215
132
  while (outcome.verdict === "feedback") {
@@ -227,7 +144,6 @@ export const ordinaryStart = async (): Promise<void> => {
227
144
  refusePendingHandOff()
228
145
  // Read right after idle, so a bad value refuses the landing that would pin it.
229
146
  flagModeOn()
230
- sliceCap()
231
147
  prototypeRounds()
232
148
  const stated = statedCriteria(changes(), head())
233
149
  await unwind()
@@ -7,14 +7,13 @@ Feature: the hands-on test tries only the criteria a person can observe
7
7
  per untagged criterion, whether a person can observe it by using the
8
8
  software; a tagged one is never asked about. Every ID it does not answer
9
9
  `no` reaches implement.prototype.testing, which must report each once and
10
- needs no chunk for the rest. A slice with no such criterion is not tested by hand, and
11
- nothing rests to say so.
10
+ needs no chunk for the rest. A change with no such criterion is not tested by
11
+ hand, and nothing rests to say so.
12
12
 
13
13
  Background:
14
14
  Given the shipped workflows, as gtd install scaffolds them
15
15
  And an environment variable "GTD_QUALITYREVIEWS" set to ""
16
16
  And an environment variable "GTD_FLAGMODE" set to "false"
17
- And an environment variable "GTD_SLICEMAXCRITERIA" set to "2"
18
17
  And a file "src/calc.ts" with:
19
18
  """
20
19
  export const add = (a: number, b: number) => a + b
@@ -275,73 +274,6 @@ Feature: the hands-on test tries only the criteria a person can observe
275
274
  gtd(check): implement.closing → build.review.reviewing
276
275
  """
277
276
 
278
- Scenario: each slice's hands-on test judges its own criteria afresh
279
- Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
280
- """
281
- # Requirements
282
-
283
- ## Impact
284
-
285
- - the greeting module
286
-
287
- ## Acceptance Criteria
288
-
289
- - AC-1: subtract(a, b) returns a - b.
290
- - AC-2 (no hands-on): the build prompt names subtract.
291
- - AC-3: the review prompt names subtract.
292
- """
293
- And step "design.slicing" writes ".gtd/SLICES.md" with:
294
- """
295
- # Slices
296
-
297
- ## Prompts
298
-
299
- - AC-2
300
- - AC-3
301
-
302
- ## Subtract
303
-
304
- - AC-1
305
- """
306
- And step "seam.naive", when prompted with "This slice builds AC-2, AC-3 only", writes "src/prompts.test.ts" with:
307
- """
308
- it("[AC-2] names subtract in the build", () => {})
309
- it("[AC-3] names subtract in the review", () => {})
310
- """
311
- And step "seam.naive", when prompted with "This slice builds AC-1 only", writes "src/calc.test.ts" with:
312
- """
313
- it("[AC-1] subtracts", () => {})
314
- """
315
- And judge step "implement.prototype.observable", when its questions contain "criterion AC-3 describes", answers:
316
- """
317
- [{"id":"AC-3","answer":"no","p":0.9}]
318
- """
319
- And judge step "implement.prototype.observable", when its questions contain "criterion AC-1 describes", answers:
320
- """
321
- [{"id":"AC-1","answer":"yes","p":0.9}]
322
- """
323
- And step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
324
- """
325
- ## AC-1: pass
326
-
327
- Printed 2.
328
- """
329
- When the process is driven
330
- Then the process rests at "build.review.reviewing"
331
- And the commit subjects end with:
332
- """
333
- gtd(check): implement.health.check → implement.prototype.observable
334
- gtd(judge): implement.prototype.observable → implement.closing
335
- gtd(check): implement.closing → seam.naive
336
- gtd(agent): seam.naive → seam.check
337
- gtd(check): seam.check → implement.applying
338
- gtd(check): implement.applying → implement.health.check
339
- gtd(check): implement.health.check → implement.prototype.observable
340
- gtd(judge): implement.prototype.observable → implement.prototype.testing
341
- gtd(agent): implement.prototype.testing → implement.closing
342
- gtd(check): implement.closing → build.review.reviewing
343
- """
344
-
345
277
  Scenario: the tag is frozen once design is settled
346
278
  Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
347
279
  """
@@ -5,7 +5,6 @@ export * from "./criteria.js"
5
5
  export * from "./planning.js"
6
6
  export * from "./refactor.js"
7
7
  export * from "./seam.js"
8
- export * from "./slicing.js"
9
8
  export {
10
9
  buildTail,
11
10
  type BuildTailOptions,
@@ -161,6 +161,56 @@ Feature: the quality lap's findings are triaged before the review
161
161
  gtd(check): build.review.carrying → build.review.await-review
162
162
  """
163
163
 
164
+ Scenario: a risk is fixed before the human sees the review, re-reviewed in the reviewer's own draft
165
+ Given judge step "build.quality.triage" answers:
166
+ """
167
+ [{"id":"Q1","answer":"mechanical","p":0.9},{"id":"Q2","answer":"noise","p":0.85},{"id":"Q3","answer":"design","p":0.8}]
168
+ """
169
+ And step "build.fix.quality.fixing" writes "src/calc.test.ts" with:
170
+ """
171
+ it("[AC-1] subtracts", () => {})
172
+ it("subtracts a larger number", () => {})
173
+ """
174
+ And step "build.fix.quality.fixing" deletes ".gtd/QUALITY.md"
175
+ And step "build.health.check" changes nothing
176
+ And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
177
+ """
178
+ # Review: abc1234
179
+
180
+ <!-- base: 0000000 -->
181
+
182
+ ## Subtract
183
+
184
+ - [ ] ./src/calc.ts#2-2 — Risk: overflows on large numbers
185
+
186
+ ## AC-1 ignores floating point
187
+
188
+ - [ ] ./src/calc.ts#2-2 — where it applies
189
+ """
190
+ When the process is driven
191
+ Then the process rests at "build.review.fix.risks.fixing"
192
+ Given step "build.review.reviewing" is no longer scripted
193
+ And step "build.review.reviewing" replaces the text "Risk: overflows on large numbers" in ".gtd/REVIEW.md" with "subtract(a, b), now guarded"
194
+ And step "build.review.fix.risks.fixing" changes nothing
195
+ When the process is driven
196
+ Then the process rests at "build.review.await-review"
197
+ And ".gtd/REVIEW.md" contains "subtract(a, b), now guarded"
198
+ And ".gtd/REVIEW.md" contains "## Fixed automatically" exactly once
199
+ And ".gtd/REVIEW.md" contains "## Dropped as noise" exactly once
200
+ And ".gtd/REVIEW.md" contains "## Walkthrough" exactly once
201
+ And ".gtd/REVIEW.md" contains "Should subtract round? The criterion does not say." exactly once
202
+ And ".gtd/REVIEW.md" contains "Tick exactly one:" exactly once
203
+ And the commit subjects end with:
204
+ """
205
+ gtd(check): build.health.check → build.review.reviewing
206
+ gtd(agent): build.review.reviewing → build.review.fix.risks.fixing
207
+ gtd(agent): build.review.fix.risks.fixing → build.health.check
208
+ gtd(check): build.health.check → build.review.reviewing
209
+ gtd(agent): build.review.reviewing → build.review.ranking
210
+ gtd(judge): build.review.ranking → build.review.carrying
211
+ gtd(check): build.review.carrying → build.review.await-review
212
+ """
213
+
164
214
  Scenario: an unsure verdict makes a finding a note, never a fix or a drop
165
215
  Given judge step "build.quality.triage" answers:
166
216
  """
@@ -87,8 +87,4 @@ Feature: a naive pass the judge rules land is itself the implementation
87
87
  And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
88
88
  And the commit "gtd(judge): seam.judge → implement.applying" has a body containing "\"answer\":\"land\""
89
89
  And no commit body contains "Gtd-Approved:"
90
- And the output lines of gtd "slices" start with:
91
- """
92
- feature
93
- """
94
90
  And no commit body contains "Gtd-Workflow:"
@@ -1,7 +1,7 @@
1
1
  @inmem
2
2
  Feature: past its round cap the hands-on test rests at a person's gate
3
3
 
4
- `prototypeRounds` caps the fix turns a slice's hands-on test gets. Once they
4
+ `prototypeRounds` caps the fix turns the hands-on test gets. Once they
5
5
  are spent and the report still has open items, the process rests at
6
6
  implement.prototype.open naming them; a person's fix there is tested again
7
7
  with a fresh budget.
@@ -3,10 +3,10 @@ Feature: every observable criterion is tested by hand before the quality lap
3
3
 
4
4
  Once the suite is green, implement.prototype.observable judges which
5
5
  criteria a person can observe, and implement.prototype.testing uses the
6
- slice the way a person would and writes .gtd/PROTOTYPE.md, one chunk per
6
+ change the way a person would and writes .gtd/PROTOTYPE.md, one chunk per
7
7
  such criterion headed `## AC-<n>: pass|fail|blocked`, plus one per problem
8
8
  found beside them. A failure goes to implement.prototype.fix.fixing and is tested again; only a clean
9
- report closes the slice. Past `prototypeRounds` fix rounds, or with nothing a
9
+ report closes the test. Past `prototypeRounds` fix rounds, or with nothing a
10
10
  fixer can take, a person decides at implement.prototype.open.
11
11
 
12
12
  Background:
@@ -67,7 +67,7 @@ Feature: every observable criterion is tested by hand before the quality lap
67
67
  And the resting step's content contains "against the user's real data or this working tree"
68
68
  And the resting step's content contains "`## AC-<n>: pass`"
69
69
 
70
- Scenario: a clean report closes the slice and moves on to the quality lap
70
+ Scenario: a clean report closes the test and moves on to the quality lap
71
71
  Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
72
72
  """
73
73
  # Prototype test