@pmelab/gtd 32.1.0 → 34.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 (36) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +66 -53
  3. package/dist/chunks/{reserved-2fV0mj14.mjs → reserved-BcJgt5_R.mjs} +79 -12
  4. package/dist/chunks/{tail-BynhCfuv.mjs → tail-CM_fa93v.mjs} +1 -1
  5. package/dist/testkit.mjs +2 -2
  6. package/dist/workflows/cleanup.mjs +2 -2
  7. package/dist/workflows/feature.mjs +174 -40
  8. package/dist/workflows/fix.mjs +2 -2
  9. package/dist/workflows/review.mjs +2 -2
  10. package/dist/workflows/rollout.mjs +1 -1
  11. package/dist/workflows.mjs +249 -48
  12. package/package.json +1 -1
  13. package/skills/authoring/SKILL.md +2 -2
  14. package/src/testkit/steps.ts +4 -0
  15. package/src/workflows/feature/acceptance-criteria.feature +8 -0
  16. package/src/workflows/feature/fast-path.feature +11 -35
  17. package/src/workflows/feature/feature.test.ts +9 -100
  18. package/src/workflows/feature/feature.ts +29 -16
  19. package/src/workflows/feature/flag-mode.feature +2 -91
  20. package/src/workflows/feature/naive-acceptance.feature +140 -0
  21. package/src/workflows/feature/one-step-land.feature +11 -34
  22. package/src/workflows/feature/quality-unresolved.feature +96 -0
  23. package/src/workflows/feature/refactor-absorbs.feature +17 -25
  24. package/src/workflows/feature/refactor-first.feature +11 -35
  25. package/src/workflows/feature/refactor-lens-models.feature +6 -1
  26. package/src/workflows/feature/refactor-review.feature +67 -360
  27. package/src/workflows/feature/refactor.ts +4 -3
  28. package/src/workflows/feature/risk-tier.feature +10 -0
  29. package/src/workflows/feature/seam-rejection.feature +20 -37
  30. package/src/workflows/feature/seam.feature +66 -113
  31. package/src/workflows/feature/seam.test.ts +173 -3
  32. package/src/workflows/feature/seam.ts +196 -31
  33. package/src/workflows/review/review-door.feature +108 -0
  34. package/src/workflows/shared/tail.ts +2 -2
  35. package/src/workflows/shared/text.test.ts +40 -0
  36. package/src/workflows/shared/text.ts +110 -16
@@ -47,6 +47,8 @@ const review = (text?: string): string =>
47
47
 
48
48
  const CALC = "export const add = (a: number, b: number) => a + b\n"
49
49
 
50
+ const ACCEPTANCE = 'it("[AC-1] done", () => {})\n'
51
+
50
52
  const naivePlanned = () => {
51
53
  const repo = testRepo({
52
54
  workflow: CONFIG_SCAFFOLD,
@@ -62,99 +64,6 @@ const naivePlanned = () => {
62
64
  return repo
63
65
  }
64
66
 
65
- describe("planAndBuild", () => {
66
- it("a refactor that carried the whole feature: the empty implement turn passes and the review covers the whole change", async () => {
67
- const repo = naivePlanned()
68
- const SUBTRACT = `${CALC}export const subtract = (a: number, b: number) => a - b\n`
69
- expect(
70
- (
71
- await repo.drive({
72
- unwind: () => undefined,
73
- "start-gate.check": () => undefined,
74
- "design.triage": (_, r) =>
75
- r.write(
76
- ".gtd/REQUIREMENTS.md",
77
- "# Requirements\n\n## Concerns\n\n### S\n\n- `s`\n\n## Acceptance Criteria\n\n- AC-1: done\n",
78
- ),
79
- "design.gate.answer": () => undefined,
80
- "seam.naive": (_, r) => r.write("src/calc.ts", SUBTRACT),
81
- "seam.judge": () => '[{"id":"shape","answer":"refactor-first","p":0.9}]',
82
- "refactor.deriving": runsItsScript,
83
- "refactor.building": (_, r) => r.write("src/calc.ts", SUBTRACT),
84
- "refactor.health.check": () => undefined,
85
- "refactor.closing": runsItsScript,
86
- "refactor.build.review.reviewing": (_, r) => r.write(".gtd/REVIEW.md", review()),
87
- })
88
- ).state,
89
- ).toBe("refactor.build.review.await-review")
90
- await repo.land()
91
- const rest = await repo.drive({
92
- "refactor.build.review.closing": runsItsScript,
93
- "implement.building": () => undefined,
94
- "implement.health.check": () => undefined,
95
- "implement.closing": runsItsScript,
96
- "build.review.reviewing": (_, r) => r.write(".gtd/REVIEW.md", review()),
97
- })
98
- expect(rest.state).toBe("build.review.await-review")
99
- const base = (await repo.gtd("base")).stdout.trim()
100
- expect(repo.show(base, "src/calc.ts")).toBe(CALC)
101
- })
102
-
103
- it("after land, feedback rests next at design.triage, then implement.building, never seam.naive", async () => {
104
- const repo = naivePlanned()
105
- const requirements = (_: unknown, r: typeof repo) =>
106
- r.write(
107
- ".gtd/REQUIREMENTS.md",
108
- "# Requirements\n\n## Concerns\n\n### Subtract\n\n- `subtract`\n\n## Acceptance Criteria\n\n- AC-1: done\n",
109
- )
110
- const lap = {
111
- unwind: () => undefined,
112
- "start-gate.check": () => undefined,
113
- "design.triage": requirements,
114
- "design.gate.answer": () => undefined,
115
- "seam.naive": (_: unknown, r: typeof repo) => r.write("src/calc.ts", `${CALC}// naive\n`),
116
- "seam.judge": () => '[{"id":"shape","answer":"land","p":0.9}]',
117
- "implement.building": (_: unknown, r: typeof repo) =>
118
- r.write("src/calc.ts", `${CALC}// built\n`),
119
- "implement.health.check": () => undefined,
120
- "implement.closing": runsItsScript,
121
- "build.review.reviewing": (_: unknown, r: typeof repo) => r.write(".gtd/REVIEW.md", review()),
122
- }
123
- expect((await repo.drive(lap)).state).toBe("build.review.await-review")
124
-
125
- repo.write(".gtd/REVIEW.md", review("also export multiply"))
126
- await repo.land()
127
- const collected = await repo.drive({
128
- "build.review.collecting": (_, r) => {
129
- requirements(_, r)
130
- r.write(".gtd/REVIEW.md", `${review("also export multiply")} - A: noted.\n`)
131
- },
132
- })
133
- expect(collected.state).toBe("build.review.await-review")
134
-
135
- repo.write(".gtd/REVIEW.md", review())
136
- await repo.land()
137
- const rest = await repo.drive({
138
- "build.review.closing": runsItsScript,
139
- "re-unwind": runsItsScript,
140
- "design.triage": (_, r) =>
141
- r.write(
142
- ".gtd/REQUIREMENTS.md",
143
- "# Requirements\n\n- multiply\n\n## Acceptance Criteria\n\n- AC-1: done\n",
144
- ),
145
- "design.gate.answer": () => undefined,
146
- })
147
- expect(rest.state).toBe("implement.building")
148
- expect(rest.content).toContain("already exists")
149
- expect(rest.content).not.toContain("write the change fresh")
150
- const subjects = repo.subjects()
151
- const reUnwind = subjects.findIndex((subject) => subject.endsWith("→ re-unwind"))
152
- expect(reUnwind).toBeGreaterThan(-1)
153
- expect(subjects.slice(reUnwind + 1).some((subject) => subject.includes("seam."))).toBe(false)
154
- expect(subjects.filter((subject) => subject.includes("→ seam.naive"))).toHaveLength(1)
155
- })
156
- })
157
-
158
67
  describe("afterTail feedback lap", () => {
159
68
  const TAIL = [
160
69
  `import { start } from "@pmelab/gtd/flows"`,
@@ -341,7 +250,10 @@ describe("an in-flight 28.0.0 naive-pass process", () => {
341
250
  "# Requirements\n\n## Concerns\n\n### S\n\n- `s`\n\n## Acceptance Criteria\n\n- AC-1: done\n",
342
251
  ),
343
252
  "design.gate.answer": () => undefined,
344
- "seam.naive": (_, r) => r.write("src/calc.ts", `${CALC}// naive\n`),
253
+ "seam.naive": (_, r) => {
254
+ r.write("src/calc.ts", `${CALC}// naive\n`)
255
+ r.write("src/calc.test.ts", ACCEPTANCE)
256
+ },
345
257
  })
346
258
  const kept = /Gtd-Kept: (\w+)/.exec(repo.messages().at(-1)!)![1]!
347
259
  const seen: Record<string, number> = {
@@ -366,16 +278,13 @@ describe("an in-flight 28.0.0 naive-pass process", () => {
366
278
  return repo
367
279
  }
368
280
 
281
+ // seam.check now follows every naive pass, so no rest past the first naive pass replays.
369
282
  for (const [name, trail] of Object.entries(trails)) {
370
- const replays = name !== "refactor-first"
371
- it(`${name}: ${replays ? "every rest replays to itself" : "every rest from the verdict on fails replay"}`, async () => {
283
+ it(`${name}: every rest past the naive pass fails replay`, async () => {
372
284
  for (let upTo = 1; upTo <= trail.length; upTo++) {
373
285
  const rest = trail[upTo - 1]![2]
374
286
  const repo = await forged(trail, upTo)
375
- if (replays) expect((await repo.next()).state, rest).toBe(rest)
376
- else {
377
- await expect(repo.next(), rest).rejects.toThrow("the workflow changed under this process")
378
- }
287
+ await expect(repo.next(), rest).rejects.toThrow("the workflow changed under this process")
379
288
  }
380
289
  })
381
290
  }
@@ -3,15 +3,16 @@ import {
3
3
  changesSince,
4
4
  head,
5
5
  human,
6
+ read,
6
7
  revertScript,
7
8
  run,
8
9
  slice,
9
10
  start,
10
11
  } from "../../flows/index.js"
11
- import { gate } from "../shared/health.js"
12
+ import { gate, healthy } from "../shared/health.js"
12
13
  import { flagModeOn, shipped } from "../shared/options.js"
13
- import { FEEDBACK } from "../shared/steps.js"
14
- import { refusePendingHandOff } from "../shared/tail.js"
14
+ import { FEEDBACK, fixCheck, QUALITY } from "../shared/steps.js"
15
+ import { refusePendingHandOff, tail } from "../shared/tail.js"
15
16
  import * as t from "../shared/text.js"
16
17
  import { skipDesign, statedCriteria, type StatedCriteria } from "./criteria.js"
17
18
  import { design } from "./planning.js"
@@ -22,9 +23,8 @@ import { recordRisk, riskTierOn } from "./tier.js"
22
23
  // The ordinary start. Any change to the tree starts a process: `idle` →
23
24
  // `unwind` reverts the sketch (its intent survives in history) → a
24
25
  // green-baseline gate → design → a judged naive pass (refactoring first when
25
- // the judge asks; with `flagMode` off, neither) → implement → the quality lap
26
- // → human review, which signs off (the episode ends back at `idle`) or sends a
27
- // design → implement lap.
26
+ // the judge asks; with `flagMode` off, neither) → implement → the quality lap,
27
+ // and the episode ends back at `idle`. Human review is the `review` door's.
28
28
 
29
29
  /** Revert the sketch that started the process out of the working tree; its intent survives in history. */
30
30
  export const unwind = (): Promise<void> => {
@@ -36,9 +36,7 @@ export const unwind = (): Promise<void> => {
36
36
 
37
37
  export { reUnwind }
38
38
 
39
- // seam.naive / seam.judge run once: a feedback lap is design → implement on every verdict, so a
40
- // seam a re-plan turns up is built unjudged and unrefactored.
41
- /** A judged naive pass (none with `flagMode` off), then implement and review until a review round signs off; design must have run since `firstBase`. */
39
+ /** A judged naive pass (none with `flagMode` off), then implement and the quality lap; design must have run since `firstBase`. */
42
40
  export const buildFeature = async (firstBase: string): Promise<void> => {
43
41
  const tier = riskTierOn() ? recordRisk() : undefined
44
42
  const seam: Omit<SeamOutcome, "reference"> & { reference?: string } = flagModeOn()
@@ -46,15 +44,31 @@ export const buildFeature = async (firstBase: string): Promise<void> => {
46
44
  : { base: firstBase }
47
45
  await slice("feature", async () => {
48
46
  await implement({ reference: seam.reference, tier, afterRefactor: seam.refactored })
49
- // An empty feature slice: the refactor carried the feature, so review the whole change.
47
+ // An empty feature slice: the refactor carried the feature, so the lap covers the whole change.
50
48
  const base =
51
49
  seam.refactored === true && !changesSince(seam.base).some((c) => isCode(c.path))
52
50
  ? firstBase
53
51
  : seam.base
54
- await featureFeedback(await buildTail(base, { green: true, lensBase: base }), base)
52
+ await tail(settleQuality, { green: true, lensBase: base })
55
53
  })
56
54
  }
57
55
 
56
+ // The lap is feature's last look: findings the fix cap left must not end the process, nor reach
57
+ // the next one's lap, which appends to the file. Empty counts as resolved, as the lap reads it.
58
+ const settleQuality = async (): Promise<void> => {
59
+ let rested = false
60
+ while ((read(QUALITY) ?? "").length > 0) {
61
+ rested = true
62
+ await human("quality-unresolved", {
63
+ message: t.qualityUnresolvedMessage(),
64
+ label: "Quality findings left unresolved",
65
+ file: QUALITY,
66
+ })
67
+ }
68
+ // The person may have fixed code by hand there; nothing else checks it before idle.
69
+ if (rested) await healthy(() => fixCheck())
70
+ }
71
+
58
72
  /** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
59
73
  export const planAndBuild = async (firstBase: string, stated?: StatedCriteria): Promise<void> => {
60
74
  // GTD_FLAG_RISK_TIER: the tier is proposed at triage and set at the design gate, so no skip.
@@ -62,20 +76,19 @@ export const planAndBuild = async (firstBase: string, stated?: StatedCriteria):
62
76
  await buildFeature(firstBase)
63
77
  }
64
78
 
65
- const featureFeedback = async (first: ReviewOutcome, lensBase?: string): Promise<void> => {
79
+ // seam.naive / seam.judge never run here: a re-plan's seam is built unjudged and unrefactored.
80
+ /** After a tail that skipped planning: stop on sign-off, re-plan on feedback. Opens no slice: slices cannot nest. */
81
+ export const afterTail = async (first: ReviewOutcome): Promise<void> => {
66
82
  let outcome = first
67
83
  while (outcome.verdict === "feedback") {
68
84
  await reUnwind(outcome)
69
85
  await design(outcome.base)
70
86
  const tier = riskTierOn() ? recordRisk() : undefined
71
87
  await implement({ tier, amend: true })
72
- outcome = await buildTail(outcome.base, { green: true, lap: "feedback", lensBase })
88
+ outcome = await buildTail(outcome.base, { green: true, lap: "feedback" })
73
89
  }
74
90
  }
75
91
 
76
- /** After a tail that skipped planning: stop on sign-off, re-plan on feedback. Opens no slice: slices cannot nest. */
77
- export const afterTail = (outcome: ReviewOutcome): Promise<void> => featureFeedback(outcome)
78
-
79
92
  /** An ordinary start: wait at `idle`, unwind the sketch, check the baseline, then plan and build. */
80
93
  export const ordinaryStart = async (): Promise<void> => {
81
94
  await human("idle", { message: t.idleMessage(), label: "Idle", file: ".gtd/TODO.md" })
@@ -58,7 +58,7 @@ Feature: flag mode off builds a feature with no release flag, seam judge or refa
58
58
  And step "implement.closing" runs its script
59
59
  And step "build.quality.correctness.reviewing" changes nothing
60
60
  When the process is driven
61
- Then the process rests at "build.review.reviewing"
61
+ Then the process rests at "idle"
62
62
  And "src/calc.ts" contains "subtract"
63
63
  And the commit subjects end with:
64
64
  """
@@ -72,94 +72,5 @@ Feature: flag mode off builds a feature with no release flag, seam judge or refa
72
72
  gtd(agent): implement.building → implement.health.check
73
73
  gtd(check): implement.health.check → implement.closing
74
74
  gtd(check): implement.closing → build.quality.correctness.reviewing
75
- gtd(agent): build.quality.correctness.reviewing → build.review.reviewing
75
+ gtd(agent): build.quality.correctness.reviewing → idle
76
76
  """
77
-
78
- Scenario: flag mode off asks for no flag on a feedback lap either
79
- Given step "implement.building" writes "src/calc.ts" with:
80
- """
81
- export const add = (a: number, b: number) => a + b
82
- export const subtract = (a: number, b: number) => a - b
83
- """
84
- And step "implement.health.check" changes nothing
85
- And step "implement.closing" runs its script
86
- And step "build.quality.correctness.reviewing" changes nothing
87
- And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
88
- """
89
- # Review: abc1234
90
-
91
- <!-- base: 0000000 -->
92
-
93
- ## calc
94
-
95
- - [ ] ./src/calc.ts#2-2
96
- new subtract function
97
- """
98
- When the process is driven
99
- Then the process rests at "build.review.await-review"
100
- Given a file ".gtd/REVIEW.md" with:
101
- """
102
- # Review: abc1234
103
-
104
- <!-- base: 0000000 -->
105
-
106
- ## calc
107
-
108
- - [ ] ./src/calc.ts#2-2
109
- new subtract function[^why]
110
-
111
- [^why]:
112
- - H: also export multiply
113
- """
114
- And step "build.review.collecting" writes ".gtd/REQUIREMENTS.md" with:
115
- """
116
- # Requirements
117
-
118
- ## Concerns
119
-
120
- ### Multiply
121
-
122
- - Export `multiply()`.
123
-
124
- ## Acceptance Criteria
125
-
126
- - AC-1: the change behaves as described.
127
- """
128
- And step "build.review.collecting" writes ".gtd/REVIEW.md" with:
129
- """
130
- # Review: abc1234
131
-
132
- <!-- base: 0000000 -->
133
-
134
- ## calc
135
-
136
- - [ ] ./src/calc.ts#2-2
137
- new subtract function[^why]
138
-
139
- [^why]:
140
- - H: also export multiply
141
- - A: noted in the requirements.
142
- """
143
- When gtd lands
144
- And the process is driven
145
- Then the process rests at "build.review.await-review"
146
- Given a file ".gtd/REVIEW.md" with:
147
- """
148
- # Review: abc1234
149
-
150
- <!-- base: 0000000 -->
151
-
152
- ## calc
153
-
154
- - [ ] ./src/calc.ts#2-2
155
- new subtract function
156
- """
157
- And step "build.review.closing" runs its script
158
- And step "re-unwind" runs its script
159
- And step "implement.building" is no longer scripted
160
- When gtd lands
161
- And the process is driven
162
- Then the process rests at "implement.building"
163
- And the resting step's content contains "Add no release flag — this project ships without release flags"
164
- And the resting step's content does not contain "release flag the earlier lap added"
165
- And no commit body contains "Gtd-Kept:"
@@ -0,0 +1,140 @@
1
+ @inmem
2
+ Feature: a naive pass ends only when a test names every criterion and the suite is green
3
+
4
+ `seam.naive` writes behaviour-level acceptance tests, each naming its
5
+ criterion's ID. A pass whose tests leave an ID out or import a file the
6
+ pass added is sent back; `seam.check` then runs the suite with the kept
7
+ pass applied, and a red run sends it back too, before the judge sees it.
8
+ Three failed attempts rest at `seam.steer`.
9
+
10
+ Background:
11
+ Given the shipped workflows, as gtd install scaffolds them
12
+ And an environment variable "GTD_QUALITYREVIEWS" set to ""
13
+ And an environment variable "GTD_FLAG_RISK_TIER" set to "0"
14
+ And a file "src/calc.ts" with:
15
+ """
16
+ export const add = (a: number, b: number) => a + b
17
+ """
18
+ And the working tree is committed as "feat: add calculator"
19
+ And a file "TODO.md" with:
20
+ """
21
+ # Add subtract
22
+
23
+ subtract(a, b) returns the difference.
24
+ """
25
+ And step "unwind" changes nothing
26
+ And step "start-gate.check" changes nothing
27
+ And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
28
+ """
29
+ # Requirements
30
+
31
+ ## Acceptance Criteria
32
+
33
+ - AC-1: `subtract(5, 3)` returns 2.
34
+ - AC-2: `add` still adds.
35
+ """
36
+ And step "design.gate.answer" changes nothing
37
+ And step "seam.naive" writes "src/calc.ts" with:
38
+ """
39
+ export const add = (a: number, b: number) => a + b
40
+ export const subtract = (a: number, b: number) => a - b
41
+ """
42
+
43
+ Scenario: a pass whose tests leave a criterion's ID out is sent back, and an overrule runs the check and sticks
44
+ Given step "seam.naive" writes "tests/calc.test.ts" with:
45
+ """
46
+ import { add, subtract } from "../src/calc.js"
47
+ it("[AC-1] subtracts", () => expect(subtract(5, 3)).toBe(2))
48
+ """
49
+ When the process is driven
50
+ Then the process rests at "seam.steer"
51
+ And the resting step's content contains "acceptance-tests: no test of the naive pass names AC-2"
52
+
53
+ Given step "seam.check" changes nothing
54
+ When gtd lands
55
+ And the process is driven
56
+ Then the process rests at "seam.judge"
57
+ And the commit subjects end with:
58
+ """
59
+ gtd(human): design.gate.answer → seam.naive
60
+ gtd(agent): seam.naive
61
+ gtd(agent): seam.naive
62
+ gtd(agent): seam.naive → seam.steer
63
+ gtd(human): seam.steer → seam.check
64
+ gtd(check): seam.check → seam.judge
65
+ """
66
+
67
+ Given judge step "seam.judge" answers:
68
+ """
69
+ [{"id":"shape","answer":"reject","p":0.9}]
70
+ """
71
+ When the process is driven
72
+ Then the process rests at "seam.steer"
73
+ And the resting step's content contains "rejected the last attempt"
74
+ And the commit subjects end with:
75
+ """
76
+ gtd(check): seam.check → seam.judge
77
+ gtd(judge): seam.judge → seam.naive
78
+ gtd(agent): seam.naive → seam.check
79
+ gtd(check): seam.check → seam.judge
80
+ gtd(judge): seam.judge → seam.naive
81
+ gtd(agent): seam.naive → seam.check
82
+ gtd(check): seam.check → seam.judge
83
+ gtd(judge): seam.judge → seam.steer
84
+ """
85
+
86
+ Scenario: a test importing a file the pass added is sent back
87
+ Given step "seam.naive" writes "src/subtract.ts" with:
88
+ """
89
+ export const subtract = (a: number, b: number) => a - b
90
+ """
91
+ And step "seam.naive" writes "src/subtract.test.ts" with:
92
+ """
93
+ import { subtract } from "./subtract.js"
94
+ it("[AC-1] subtracts", () => expect(subtract(5, 3)).toBe(2))
95
+ it("[AC-2] still adds", () => {})
96
+ """
97
+ When the process is driven
98
+ Then the process rests at "seam.steer"
99
+ And the resting step's content contains "src/subtract.test.ts → src/subtract.ts"
100
+
101
+ Scenario: a red suite sends the pass back, and it moves on only once green
102
+ Given step "seam.naive" writes "tests/calc.test.ts" with:
103
+ """
104
+ import { add, subtract } from "../src/calc.js"
105
+ it("[AC-1] subtracts", () => expect(subtract(5, 3)).toBe(2))
106
+ it("[AC-2] still adds", () => expect(add(1, 1)).toBe(2))
107
+ """
108
+ When the process is driven
109
+ Then the process rests at "seam.check"
110
+ And the resting step's content contains "git checkout -q HEAD --"
111
+ And the resting step's content does not contain "'^'"
112
+ And the resting step's content contains "npm test"
113
+
114
+ Given step "seam.naive" is no longer scripted
115
+ And step "seam.check" writes ".gtd/FEEDBACK.md" with:
116
+ """
117
+ FAIL tests/calc.test.ts > [AC-1] subtracts
118
+ """
119
+ When the process is driven
120
+ Then the process rests at "seam.naive"
121
+ And the resting step's content contains "left the suite red"
122
+ And ".gtd/FEEDBACK.md" contains "[AC-1] subtracts"
123
+
124
+ Given step "seam.naive" writes "tests/calc.test.ts" with:
125
+ """
126
+ import { add, subtract } from "../src/calc.js"
127
+ it("[AC-1] subtracts", () => expect(subtract(5, 3)).toBe(2))
128
+ it("[AC-2] still adds", () => expect(add(2, 2)).toBe(4))
129
+ """
130
+ And step "seam.check" from now on changes nothing
131
+ When the process is driven
132
+ Then the process rests at "implement.building"
133
+ And the commit subjects end with:
134
+ """
135
+ gtd(human): design.gate.answer → seam.naive
136
+ gtd(agent): seam.naive → seam.check
137
+ gtd(check): seam.check → seam.naive
138
+ gtd(agent): seam.naive → seam.check
139
+ gtd(check): seam.check → implement.building
140
+ """
@@ -4,8 +4,8 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
4
4
 
5
5
  With GTD_FLAG_RISK_TIER off, a naive pass that
6
6
  edits an existing source file is judged land: the process skips the approval
7
- gate and any refactor, and runs implement, quality and
8
- review back to idle. The flag is pinned here so the per-flag test runs never
7
+ gate and any refactor, and runs implement and quality
8
+ back to idle, with no review. The flag is pinned here so the per-flag test runs never
9
9
  move it off this path.
10
10
 
11
11
  Background:
@@ -40,6 +40,11 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
40
40
  - AC-1: the change behaves as described.
41
41
  """
42
42
  And step "design.gate.answer" changes nothing
43
+ And step "seam.check" changes nothing
44
+ And step "seam.naive" writes "src/acceptance.test.ts" with:
45
+ """
46
+ it("[AC-1] holds", () => {})
47
+ """
43
48
  And step "seam.naive" writes "src/greeting.ts" with:
44
49
  """
45
50
  export const greeting = "Hello, world"
@@ -55,35 +60,9 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
55
60
  And step "implement.health.check" changes nothing
56
61
  And step "implement.closing" runs its script
57
62
  And step "build.quality.correctness.reviewing" changes nothing
58
- And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
59
- """
60
- # Review: abc1234
61
-
62
- <!-- base: 0000000 -->
63
-
64
- ## greeting
65
-
66
- - [ ] ./src/greeting.ts#1-1
67
- typo fixed
68
- """
69
- And step "build.review.closing" runs its script
70
63
 
71
64
  Scenario: a land judgment runs idle to idle with no seam gate or refactor
72
65
  When the process is driven
73
- Then the process rests at "build.review.await-review"
74
- Given a file ".gtd/REVIEW.md" with:
75
- """
76
- # Review: abc1234
77
-
78
- <!-- base: 0000000 -->
79
-
80
- ## greeting
81
-
82
- - [x] ./src/greeting.ts#1-1
83
- typo fixed
84
- """
85
- When gtd lands
86
- And the process is driven
87
66
  Then the process rests at "idle"
88
67
  And "src/greeting.ts" contains "Hello, world!"
89
68
  And the commit subjects end with:
@@ -93,18 +72,16 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
93
72
  gtd(check): start-gate.check → design.triage
94
73
  gtd(agent): design.triage → design.gate.answer
95
74
  gtd(human): design.gate.answer → seam.naive
96
- gtd(agent): seam.naive → seam.judge
75
+ gtd(agent): seam.naive → seam.check
76
+ gtd(check): seam.check → seam.judge
97
77
  gtd(judge): seam.judge → implement.building
98
78
  gtd(agent): implement.building → implement.health.check
99
79
  gtd(check): implement.health.check → implement.closing
100
80
  gtd(check): implement.closing → build.quality.correctness.reviewing
101
- gtd(agent): build.quality.correctness.reviewing → build.review.reviewing
102
- gtd(agent): build.review.reviewing → build.review.await-review
103
- gtd(human): build.review.await-review → build.review.closing
104
- gtd(check): build.review.closing → idle
81
+ gtd(agent): build.quality.correctness.reviewing → idle
105
82
  """
106
83
  And the commit "gtd(human): idle → unwind" has a body containing "Gtd-Var: flag_risk_tier=0"
107
- And the commit "gtd(agent): seam.naive → seam.judge" has a body containing "Gtd-Kept:"
84
+ And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
108
85
  And the commit "gtd(judge): seam.judge → implement.building" has a body containing "\"answer\":\"land\""
109
86
  And no commit body contains "Gtd-Approved:"
110
87
  And the output lines of gtd "slices" start with:
@@ -0,0 +1,96 @@
1
+ @inmem
2
+ Feature: quality findings left unresolved hold a feature process short of idle
3
+
4
+ The quality lap is the last look `feature` takes. When the fix cap leaves
5
+ `.gtd/QUALITY.md` behind, the process rests for a person instead of ending
6
+ with findings nobody acted on.
7
+
8
+ Background:
9
+ Given the shipped workflows, as gtd install scaffolds them
10
+ And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
11
+ And an environment variable "GTD_FLAGMODE" set to "false"
12
+ And a file "src/calc.ts" with:
13
+ """
14
+ export const add = (a: number, b: number) => a + b
15
+ """
16
+ And the working tree is committed as "feat: add calculator"
17
+ And a file "TODO.md" with:
18
+ """
19
+ # Add subtract
20
+
21
+ subtract(a, b) returns the difference.
22
+ """
23
+ And step "unwind" changes nothing
24
+ And step "start-gate.check" changes nothing
25
+ And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
26
+ """
27
+ # Requirements
28
+
29
+ ## Concerns
30
+
31
+ ### Subtract
32
+
33
+ - `subtract(a, b)` returns `a - b`.
34
+
35
+ ## Acceptance Criteria
36
+
37
+ - AC-1: subtract(a, b) returns a - b.
38
+ """
39
+ And step "design.gate.answer" changes nothing
40
+ And step "implement.building" writes "src/calc.ts" with:
41
+ """
42
+ export const add = (a: number, b: number) => a + b
43
+ export const subtract = (a: number, b: number) => a - b
44
+ """
45
+ And step "implement.health.check" changes nothing
46
+ And step "implement.closing" runs its script
47
+
48
+ Scenario: findings the fix cap left unresolved rest at build.quality-unresolved
49
+ Given step "build.quality.correctness.reviewing" writes ".gtd/QUALITY.md" with:
50
+ """
51
+ # Quality
52
+
53
+ - subtract has no test
54
+ """
55
+ And step "build.fix.quality.fixing" from now on changes nothing
56
+ And step "build.health.describe" writes ".gtd/ESCALATION.md" with:
57
+ """
58
+ # Escalation
59
+
60
+ The fix turn changed nothing.
61
+ """
62
+ When the process is driven
63
+ Then the process rests at "build.health.stop"
64
+ Given step "build.fix" writes "src/calc.ts" with:
65
+ """
66
+ export const add = (a: number, b: number) => a + b
67
+ export const subtract = (a: number, b: number) => a - b
68
+ // checked
69
+ """
70
+ And step "build.health.check" changes nothing
71
+ When gtd lands
72
+ And the process is driven
73
+ Then the process rests at "build.quality-unresolved"
74
+ And ".gtd/QUALITY.md" contains "subtract has no test"
75
+
76
+ When gtd lands
77
+ And the process is driven
78
+ Then the process rests at "build.quality-unresolved"
79
+
80
+ Given a file "src/calc.ts" with:
81
+ """
82
+ export const add = (a: number, b: number) => a + b
83
+ export const subtract = (a: number, b: number) => a - b
84
+ // checked by hand
85
+ """
86
+ And the file ".gtd/QUALITY.md" is deleted
87
+ And step "build.health.check" changes nothing
88
+ When gtd lands
89
+ And the process is driven
90
+ Then the process rests at "idle"
91
+ And the commit subjects end with:
92
+ """
93
+ gtd(check): build.health.check → build.quality-unresolved
94
+ gtd(human): build.quality-unresolved → build.health.check
95
+ gtd(check): build.health.check → idle
96
+ """