@pmelab/gtd 54.0.0 → 56.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 (72) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +109 -104
  3. package/dist/chunks/{reserved-CQ7amcKG.mjs → reserved-B8EC-WE8.mjs} +124 -230
  4. package/dist/chunks/{review-CXzUXJHL.mjs → review-HdrX0FJ7.mjs} +30 -125
  5. package/dist/chunks/{steering-CLhu8C7q.mjs → steering-CCNvngSH.mjs} +256 -65
  6. package/dist/chunks/{tail-JZ9PAOwp.mjs → tail-BSLah2mA.mjs} +155 -63
  7. package/dist/gtd.bundle.mjs +259 -22
  8. package/dist/steering.mjs +2 -2
  9. package/dist/testkit.mjs +6 -6
  10. package/dist/web/main.css +1 -1
  11. package/dist/web/main.js +551 -66
  12. package/dist/workflow-kit.mjs +1 -1
  13. package/dist/workflows/cleanup.mjs +2 -2
  14. package/dist/workflows/feature.mjs +47 -102
  15. package/dist/workflows/fix.mjs +3 -3
  16. package/dist/workflows/review.mjs +3 -3
  17. package/dist/workflows/rollout.mjs +1 -1
  18. package/dist/workflows.mjs +614 -574
  19. package/package.json +1 -1
  20. package/skills/authoring/SKILL.md +3 -3
  21. package/src/cli/Cli.ts +5 -5
  22. package/src/edge/Edge.test.ts +1 -1
  23. package/src/steering/FORMATS.md +5 -1
  24. package/src/steering/SteeringFormats.test.ts +2 -2
  25. package/src/steering/contract.test.ts +1 -0
  26. package/src/steering/findings.test.ts +47 -0
  27. package/src/steering/findings.ts +350 -0
  28. package/src/steering/fixtures/findings/gate.md +34 -0
  29. package/src/steering/fixtures/findings/gate.read.json +87 -0
  30. package/src/steering/index.test.ts +2 -2
  31. package/src/steering/index.ts +4 -3
  32. package/src/steering/review.test.ts +1 -75
  33. package/src/steering/review.ts +1 -74
  34. package/src/workflow/compile.test.ts +1 -0
  35. package/src/workflows/feature/criteria-coverage.feature +104 -0
  36. package/src/workflows/feature/feature.test.ts +1 -1
  37. package/src/workflows/feature/feature.ts +17 -20
  38. package/src/workflows/feature/green.ts +2 -5
  39. package/src/workflows/feature/lens-review.feature +323 -171
  40. package/src/workflows/feature/naive-acceptance.feature +31 -32
  41. package/src/workflows/feature/prototype.ts +5 -8
  42. package/src/workflows/feature/refactor-absorbs.feature +1 -7
  43. package/src/workflows/feature/refactor-first.feature +2 -10
  44. package/src/workflows/feature/refactor-rebuild.feature +20 -36
  45. package/src/workflows/feature/refactor-review.feature +33 -117
  46. package/src/workflows/feature/refactor.test.ts +0 -2
  47. package/src/workflows/feature/refactor.ts +0 -1
  48. package/src/workflows/feature/review-gate.feature +6 -22
  49. package/src/workflows/feature/reviewer-walkthrough.feature +16 -103
  50. package/src/workflows/feature/seam.test.ts +19 -101
  51. package/src/workflows/feature/seam.ts +13 -89
  52. package/src/workflows/feature/standards.feature +75 -34
  53. package/src/workflows/fix/fix-lens-origin.feature +20 -7
  54. package/src/workflows/fix/fix-quality.feature +104 -23
  55. package/src/workflows/review/review-door.feature +62 -59
  56. package/src/workflows/shared/access.test.ts +1 -0
  57. package/src/workflows/shared/learn.test.ts +0 -14
  58. package/src/workflows/shared/learn.ts +1 -6
  59. package/src/workflows/shared/lenses.ts +149 -43
  60. package/src/workflows/shared/review.test.ts +30 -27
  61. package/src/workflows/shared/review.ts +29 -196
  62. package/src/workflows/shared/skills.test.ts +3 -1
  63. package/src/workflows/shared/steps.test.ts +9 -6
  64. package/src/workflows/shared/steps.ts +12 -3
  65. package/src/workflows/shared/tail.test.ts +20 -2
  66. package/src/workflows/shared/tail.ts +38 -16
  67. package/src/workflows/shared/text.test.ts +122 -294
  68. package/src/workflows/shared/text.ts +181 -358
  69. package/src/workflows/shared/vars.ts +1 -1
  70. package/src/workflows/feature/review-answers.feature +0 -324
  71. package/src/workflows/feature/severity-budget.feature +0 -116
  72. package/src/workflows/feature/severity-sorted-review.feature +0 -198
@@ -14,8 +14,6 @@ import {
14
14
  naivePass,
15
15
  acceptanceFault,
16
16
  naiveAcceptance,
17
- requireAcceptance,
18
- requireAcceptanceKept,
19
17
  seamBuild,
20
18
  type SeamOutcome,
21
19
  } from "./seam.js"
@@ -108,71 +106,24 @@ describe("naivePass", () => {
108
106
  })
109
107
  })
110
108
 
111
- describe("naiveAcceptance and requireAcceptance", () => {
112
- const requirements =
113
- "# R\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: adds.\n- AC-2: subtracts.\n"
114
- const file = (path: string, before: string | undefined, after: string): Change => ({
115
- path,
116
- status: before === undefined ? "added" : "modified",
117
- before,
118
- after,
119
- })
120
- const install = (since: Change[] = []) =>
121
- installContext({
122
- ...fixtureContext({}),
123
- matches: globMatches,
124
- read: (path: string) => (path === ".gtd/REQUIREMENTS.md" ? requirements : undefined),
125
- changesSince: () => since,
126
- changes: () => since,
127
- refuse: (message: string) => {
128
- throw new Error(message)
129
- },
130
- } as unknown as FlowContext)
131
-
132
- it("takes the IDs the pass's tests name on an added line, and only those test files", () => {
133
- install()
109
+ describe("naiveAcceptance", () => {
110
+ it("carries every test file the pass added or changed, named or not", () => {
111
+ installContext({ ...fixtureContext({}), matches: globMatches } as unknown as FlowContext)
134
112
  expect(
135
113
  naiveAcceptance(
136
114
  tip(
137
- file("src/a.test.ts", 'it("[AC-2] old")', 'it("[AC-2] old")\nit("[AC-1] adds")'),
138
- file("src/b.test.ts", undefined, 'it("helper")'),
139
- file("src/calc.ts", "x", "AC-2"),
115
+ change("src/a.test.ts", "modified"),
116
+ change("tests/b.feature", "added"),
117
+ change("src/gone.test.ts", "deleted"),
118
+ change("src/calc.ts", "modified"),
119
+ change(".gtd/x.test.ts", "added"),
140
120
  ),
141
121
  ),
142
- ).toEqual({ hash: "abc", tests: ["src/a.test.ts"], ids: ["AC-1"] })
143
- })
144
-
145
- it("refuses a rebuild whose tests leave out an ID, naming the naive tests to carry over", () => {
146
- install([file("src/calc.test.ts", undefined, 'it("[AC-1] adds")')])
147
- expect(() =>
148
- requireAcceptance({ hash: "abc", tests: ["src/a.test.ts"], ids: ["AC-1", "AC-2"] }, "c0"),
149
- ).toThrow(
150
- /^gtd land: rebuild-acceptance: .*leave out AC-2 — .*git show abc -- 'src\/a\.test\.ts'/,
151
- )
152
- })
153
-
154
- it("refuses a fix turn that itself drops a carried ID, never one that leaves it alone", () => {
155
- const acceptance = { hash: "abc", tests: [], ids: ["AC-1"] }
156
- install([file("src/calc.test.ts", 'it("[AC-1] adds")', 'it("adds")')])
157
- expect(() => requireAcceptanceKept(acceptance)).toThrow(/drops AC-1/)
158
- install([file("src/calc.ts", "a", "b")])
159
- expect(() => requireAcceptanceKept(acceptance)).not.toThrow()
160
- })
161
-
162
- it("passes a rebuild whose tests name every ID, wherever they live", () => {
163
- install([
164
- file("tests/x.test.ts", undefined, 'it("[AC-1] adds")'),
165
- file("tests/y.feature", "", "Scenario: [AC-2] subtracts"),
166
- ])
167
- expect(() =>
168
- requireAcceptance({ hash: "abc", tests: ["src/a.test.ts"], ids: ["AC-1", "AC-2"] }, "c0"),
169
- ).not.toThrow()
122
+ ).toEqual({ hash: "abc", tests: ["src/a.test.ts", "tests/b.feature"] })
170
123
  })
171
124
  })
172
125
 
173
126
  describe("acceptanceFault", () => {
174
- const requirements =
175
- "# R\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: adds.\n- AC-2: subtracts.\n"
176
127
  const file = (path: string, status: Change["status"], after: string): Change => ({
177
128
  path,
178
129
  status,
@@ -180,30 +131,10 @@ describe("acceptanceFault", () => {
180
131
  after,
181
132
  })
182
133
  const refusal = (...changes: Change[]): string | undefined => {
183
- installContext({
184
- ...fixtureContext({}),
185
- matches: globMatches,
186
- read: (path: string) => (path === ".gtd/REQUIREMENTS.md" ? requirements : undefined),
187
- } as unknown as FlowContext)
188
- return acceptanceFault(".gtd/REQUIREMENTS.md", tip(...changes))
134
+ installContext({ ...fixtureContext({}), matches: globMatches } as unknown as FlowContext)
135
+ return acceptanceFault(tip(...changes))
189
136
  }
190
- const tagged = 'import { add } from "../src/calc.js"\nit("[AC-1] adds")\nit("[AC-2] subtracts")'
191
-
192
- it("passes when every ID is named in a test the pass wrote", () => {
193
- expect(
194
- refusal(file("src/calc.ts", "modified", "x"), file("tests/calc.test.ts", "added", tagged)),
195
- ).toBe(undefined)
196
- })
197
-
198
- it("names every ID no test of the pass mentions", () => {
199
- const message = refusal(file("tests/calc.test.ts", "added", 'it("[AC-12] x")'))
200
- expect(message).toMatch(/^acceptance-tests: /)
201
- expect(message).toContain("AC-1, AC-2")
202
- })
203
-
204
- it("does not count an ID in a non-test file", () => {
205
- expect(refusal(file("src/calc.ts", "modified", "AC-1 AC-2"))).toContain("AC-1, AC-2")
206
- })
137
+ const tagged = 'import { add } from "../src/calc.js"\nit("adds")'
207
138
 
208
139
  it("refuses a test importing a file the pass added, resolved from the test's directory", () => {
209
140
  const message = refusal(
@@ -224,17 +155,6 @@ describe("acceptanceFault", () => {
224
155
  ).toContain("acceptance-internals")
225
156
  })
226
157
 
227
- it("counts only the lines the pass added: an ID an earlier feature left does not", () => {
228
- const old = 'it("[AC-1] old")\nit("[AC-2] old")'
229
- const message = refusal({
230
- path: "tests/calc.test.ts",
231
- status: "modified",
232
- before: old,
233
- after: `${old}\nit("[AC-2] new")`,
234
- })
235
- expect(message).toContain("names AC-1 —")
236
- })
237
-
238
158
  it("refuses a bare . or .. import of an added index", () => {
239
159
  expect(
240
160
  refusal(
@@ -424,7 +344,7 @@ describe("seamBuild", () => {
424
344
  reference: "abc",
425
345
  base: "after-refactor",
426
346
  refactored: true,
427
- rebuild: { note: "Seam: a shared op", acceptance: { hash: "abc", tests: [], ids: [] } },
347
+ rebuild: { note: "Seam: a shared op", acceptance: { hash: "abc", tests: [] } },
428
348
  })
429
349
  expect(h.names.indexOf("seam.note")).toBe(h.names.indexOf("seam.judge") + 1)
430
350
  expect(h.names.indexOf("refactor.deriving")).toBe(h.names.indexOf("seam.note") + 1)
@@ -440,7 +360,7 @@ describe("seamBuild", () => {
440
360
  expect(h.outcome).toEqual({
441
361
  reference: "abc",
442
362
  base: "c-base",
443
- rebuild: { acceptance: { hash: "abc", tests: [], ids: [] } },
363
+ rebuild: { acceptance: { hash: "abc", tests: ["src/a.test.ts"] } },
444
364
  })
445
365
  expect(h.names).not.toContain("seam.by-hand")
446
366
  expect(h.names.some((n) => n.startsWith("refactor"))).toBe(false)
@@ -466,7 +386,7 @@ describe("seamBuild", () => {
466
386
  expect(h.outcome).toEqual({
467
387
  reference: "abc",
468
388
  base: "c-base",
469
- rebuild: { acceptance: { hash: "abc", tests: [], ids: [] } },
389
+ rebuild: { acceptance: { hash: "abc", tests: [] } },
470
390
  })
471
391
  })
472
392
  })
@@ -543,19 +463,17 @@ describe("implement", () => {
543
463
  reference: "abc",
544
464
  rebuild: {
545
465
  note: "# Note\n\nSeam: a shared op",
546
- acceptance: { hash: "abc", tests: ["src/a.test.ts"], ids: ["AC-1"] },
466
+ acceptance: { hash: "abc", tests: ["src/a.test.ts"] },
547
467
  },
548
- // The fixture's turn carries no test, so the acceptance guard refuses it after the prompt.
549
- }).catch(() => undefined),
468
+ }),
550
469
  )
551
470
  expect(prompt).toContain(" > Seam: a shared op")
552
- expect(prompt).toContain("acceptance tests for AC-1")
553
471
  expect(prompt).toContain("git show abc -- 'src/a.test.ts'")
554
472
  })
555
473
 
556
- it("a rebuild whose naive tests named no ID asks to carry nothing over", async () => {
474
+ it("a rebuild whose naive pass wrote no test asks to carry nothing over", async () => {
557
475
  const prompt = await building("abc", undefined, undefined, () =>
558
- implement({ reference: "abc", rebuild: { acceptance: { hash: "abc", tests: [], ids: [] } } }),
476
+ implement({ reference: "abc", rebuild: { acceptance: { hash: "abc", tests: [] } } }),
559
477
  )
560
478
  expect(prompt).not.toContain("Carry over")
561
479
  expect(prompt).not.toContain("refactor note")
@@ -3,7 +3,6 @@ import {
3
3
  agent,
4
4
  applyScript,
5
5
  changes,
6
- changesSince,
7
6
  check,
8
7
  codeChanges,
9
8
  env,
@@ -92,43 +91,19 @@ const reaches = (imports: ReturnType<typeof importsOf>, added: string): boolean
92
91
  imports.some(({ path, tail }) => name === path || (tail && name.endsWith(`/${path}`))),
93
92
  )
94
93
 
95
- const named = (id: string, text: string): boolean =>
96
- new RegExp(`(?<![\\w-])${id}(?!\\d)`).test(text)
97
-
98
- /** The lines `change` added: an ID an earlier change left in the file does not count. */
99
- const addedLines = (change: Change): string => {
100
- const before = new Set((change.before ?? "").split("\n"))
101
- return change
102
- .after!.split("\n")
103
- .filter((line) => !before.has(line))
104
- .join("\n")
105
- }
106
-
107
94
  const acceptanceTests = (changes: readonly Change[]): readonly Change[] =>
108
95
  changes.filter((c) => c.after !== undefined && isCode(c.path) && isTestFile(c.path))
109
96
 
110
- const idsOf = (file: string): string[] => criterionIds(read(file) ?? "")
111
-
112
- // One rule for both gates, so a naive test carried over verbatim always satisfies the rebuild's.
113
- const unnamed = (ids: readonly string[], tests: readonly Change[]): string[] => {
114
- const lines = tests.map(addedLines)
115
- return ids.filter((id) => !lines.some((text) => named(id, text)))
116
- }
117
-
118
97
  /**
119
- * Why `tip`'s tests fall short, or `undefined`: a criterion ID of `file` no
120
- * test line of the pass names, or a test importing a file the pass added —
121
- * naive structure a reset and refactor replaces, which the tests must survive.
98
+ * Why `tip`'s tests fall short, or `undefined`: a test importing a file the
99
+ * pass added — naive structure a reset and refactor replaces, which the tests
100
+ * must survive.
122
101
  */
123
- export const acceptanceFault = (file: string, tip: KeptTip): string | undefined => {
124
- const tests = acceptanceTests(tip.changes)
125
- const untested = unnamed(idsOf(file), tests)
126
- if (untested.length > 0)
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]}] …"`
102
+ export const acceptanceFault = (tip: KeptTip): string | undefined => {
128
103
  const added = tip.changes
129
104
  .filter((c) => c.status === "added" && isCode(c.path) && !isTestFile(c.path))
130
105
  .map((c) => c.path)
131
- const reaching = tests
106
+ const reaching = acceptanceTests(tip.changes)
132
107
  .filter((test) => holdsCallSites(test.path))
133
108
  .flatMap((test) => {
134
109
  const imports = importsOf(test)
@@ -141,48 +116,11 @@ export const acceptanceFault = (file: string, tip: KeptTip): string | undefined
141
116
  return undefined
142
117
  }
143
118
 
144
- /** What a rebuild carries over from the naive pass `tip`: the IDs its tests name, and those test files. */
145
- export const naiveAcceptance = (tip: KeptTip): t.Acceptance => {
146
- const ids = idsOf(REQUIREMENTS)
147
- const perTest = acceptanceTests(tip.changes).map((c) => {
148
- const lines = addedLines(c)
149
- return { path: c.path, ids: ids.filter((id) => named(id, lines)) }
150
- })
151
- return {
152
- hash: tip.hash,
153
- tests: perTest.filter((c) => c.ids.length > 0).map((c) => c.path),
154
- ids: ids.filter((id) => perTest.some((c) => c.ids.includes(id))),
155
- }
156
- }
157
-
158
- /**
159
- * Refuse a rebuild whose test changes since `since` leave out an ID the naive
160
- * pass's tests named. A skipped test still counts: the gate reads text, never runs it.
161
- */
162
- export const requireAcceptance = (acceptance: t.Acceptance, since: string): void => {
163
- const missing = unnamed(acceptance.ids, acceptanceTests(changesSince(since)))
164
- if (missing.length === 0) return
165
- refuse(
166
- `gtd land: rebuild-acceptance: the rebuild's tests leave out ${missing.join(", ")} — carry over the naive pass's acceptance tests (\`git show ${acceptance.hash} -- ${acceptance.tests.map(quote).join(" ")}\`), each with its ID in the test or scenario name`,
167
- )
168
- }
169
-
170
- /**
171
- * Refuse a fix turn that, on its own, drops an ID the naive pass's tests named
172
- * from a test file. Turn-local, so a person's edit at an escalation rest never
173
- * wedges later fix turns.
174
- */
175
- export const requireAcceptanceKept = (acceptance: t.Acceptance): void => {
176
- const dropped = acceptance.ids.filter((id) =>
177
- changes()
178
- .filter((c) => isCode(c.path) && isTestFile(c.path))
179
- .some((c) => named(id, c.before ?? "") && !named(id, c.after ?? "")),
180
- )
181
- if (dropped.length === 0) return
182
- refuse(
183
- `gtd land: rebuild-acceptance: this turn drops ${dropped.join(", ")} from the tests — the naive pass's acceptance tests stay; fix the code, not them`,
184
- )
185
- }
119
+ /** What a rebuild carries over from the naive pass `tip`: every test file it added or changed. */
120
+ export const naiveAcceptance = (tip: KeptTip): t.Acceptance => ({
121
+ hash: tip.hash,
122
+ tests: acceptanceTests(tip.changes).map((c) => c.path),
123
+ })
186
124
 
187
125
  const restore = (path: string): string =>
188
126
  `if git cat-file -e ${quote(`HEAD:${path}`)} 2>/dev/null; then git checkout -q HEAD -- ${quote(path)}; else rm -f -- ${quote(path)}; fi`
@@ -243,12 +181,6 @@ const applied = async (tip: string): Promise<boolean> => {
243
181
  return !wrote(FEEDBACK) && codeChanges().length > 0
244
182
  }
245
183
 
246
- /** `carried` limited to the criteria `.gtd/REQUIREMENTS.md` lists now; a file listing none narrows nothing. */
247
- const settled = (carried: readonly string[]): readonly string[] => {
248
- const listed = idsOf(REQUIREMENTS)
249
- return listed.length === 0 ? carried : carried.filter((id) => listed.includes(id))
250
- }
251
-
252
184
  const build = async (options: ImplementOptions): Promise<void> => {
253
185
  await t.agentWithSkills(
254
186
  "building",
@@ -290,19 +222,11 @@ export async function implement(
290
222
  /** `implement`, resolving with the criterion IDs the requirements listed when it closed. */
291
223
  export const implementing = (options: ImplementOptions): Promise<readonly string[]> =>
292
224
  scope("implement", async () => {
293
- const since = head()
294
- const acceptance = options.rebuild?.acceptance
295
225
  const built = options.naive === undefined || !(await applied(options.naive))
296
226
  if (built) await build(options)
297
227
  else requireCriteriaFrozen(REQUIREMENTS)
298
- // Only the criteria the requirements still list.
299
- if (built && acceptance !== undefined)
300
- requireAcceptance({ ...acceptance, ids: settled(acceptance.ids) }, since)
301
- // Fix turns are held to the carried tests too: deleting a red one must not buy a green suite.
302
- const keepsTests =
303
- built && acceptance !== undefined ? () => requireAcceptanceKept(acceptance) : undefined
304
- await green(keepsTests)
305
- await prototypeTest(keepsTests)
228
+ await green()
229
+ await prototypeTest()
306
230
  const ids = criterionIds(read(REQUIREMENTS) ?? "")
307
231
  await run("closing", removeScript([REQUIREMENTS, REFACTOR_NOTE, PROTOTYPE]), {
308
232
  label: "Closing out the implementation",
@@ -360,7 +284,7 @@ interface Vetting {
360
284
 
361
285
  /** Sends a pass whose tests fall short or run red back (`undefined`: it goes on, an overruled one as it is). */
362
286
  const vetted = async (tip: KeptTip, vetting: Vetting): Promise<Verdict | undefined> => {
363
- const fault = vetting.waived ? undefined : acceptanceFault(REQUIREMENTS, tip)
287
+ const fault = vetting.waived ? undefined : acceptanceFault(tip)
364
288
  if (fault !== undefined) {
365
289
  const verdict = await vetting.again({ fault, hash: tip.hash })
366
290
  if (verdict.kind !== "overruled") return verdict
@@ -1,8 +1,8 @@
1
1
  @inmem
2
2
  Feature: the review learns standards from the human's answers
3
3
 
4
- A won't-fix tick or an own answer at the review gate is a candidate
5
- standard. A judge asks whether it is reusable; above `standardMinP` it is
4
+ A won't-fix tick or an own answer at the review gate — on a note the human
5
+ added with its own answer boxes — is a candidate standard. A judge asks whether it is reusable; above `standardMinP` it is
6
6
  kept in `.gtd/STANDARDS.md` with no further question, and every later agent
7
7
  turn — lenses and coder alike — is told to follow it. One that contradicts
8
8
  a standard already kept becomes a note in `.gtd/REVIEW.md` the review cannot
@@ -12,7 +12,7 @@ Feature: the review learns standards from the human's answers
12
12
  Background:
13
13
  Given the shipped workflows, as gtd install scaffolds them
14
14
  And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
15
- And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
15
+ And an environment variable "GTD_QUALITYREVIEWS" set to ""
16
16
  And an environment variable "GTD_FLAGMODE" set to "false"
17
17
  And a file "src/calc.ts" with:
18
18
  """
@@ -59,15 +59,6 @@ Feature: the review learns standards from the human's answers
59
59
  And step "implement.applying" runs its script
60
60
  And step "implement.health.check" changes nothing
61
61
  And step "implement.closing" runs its script
62
- And step "build.quality.correctness.reviewing" writes ".gtd/QUALITY.md" with:
63
- """
64
- # Quality
65
-
66
- ## subtract has no class
67
-
68
- Calculators read better as classes.
69
- """
70
- And step "build.quality.sorting" runs its script
71
62
  And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
72
63
  """
73
64
  # Review: abc1234
@@ -78,29 +69,7 @@ Feature: the review learns standards from the human's answers
78
69
 
79
70
  - [ ] ./src/calc.ts#2-2 — subtract(a, b)
80
71
  """
81
- And step "build.review.reviewing", when prompted with "- `## subtract has no class`", writes ".gtd/REVIEW.md" with:
82
- """
83
- # Review: abc1234
84
-
85
- <!-- base: 0000000 -->
86
-
87
- ## Subtract
88
-
89
- - [ ] ./src/calc.ts#2-2 — subtract(a, b)
90
-
91
- ## subtract has no class
92
-
93
- - [ ] ./src/calc.ts#2-2 — a plain function
94
- """
95
72
  And step "build.review.carrying" runs its script
96
- And judge step "build.review.ranking" answers:
97
- """
98
- []
99
- """
100
- And judge step "build.quality.triage" answers:
101
- """
102
- [{"id":"Q1","answer":"design","p":0.9}]
103
- """
104
73
  And step "build.review.closing" runs its script
105
74
  And step "build.review.standards.recording" runs its script
106
75
  And step "build.review.standards.raising" runs its script
@@ -111,6 +80,18 @@ Feature: the review learns standards from the human's answers
111
80
  [{"id":"standard-1","answer":"yes","p":0.9}]
112
81
  """
113
82
  When the process is driven
83
+ And ".gtd/REVIEW.md" gets appended:
84
+ """
85
+ ## subtract has no class
86
+
87
+ Calculators read better as classes.
88
+
89
+ Tick exactly one:
90
+
91
+ - [ ] fix as suggested
92
+ - [ ] won't fix
93
+ - [ ] _your answer_
94
+ """
114
95
  And the text "- [ ] won't fix" in ".gtd/REVIEW.md" is replaced with "- [x] won't fix"
115
96
  And gtd lands
116
97
  And the process is driven
@@ -143,6 +124,18 @@ Feature: the review learns standards from the human's answers
143
124
  """
144
125
  And step "build.health.check" changes nothing
145
126
  When the process is driven
127
+ And ".gtd/REVIEW.md" gets appended:
128
+ """
129
+ ## subtract has no class
130
+
131
+ Calculators read better as classes.
132
+
133
+ Tick exactly one:
134
+
135
+ - [ ] fix as suggested
136
+ - [ ] won't fix
137
+ - [ ] _your answer_
138
+ """
146
139
  And the text "- [ ] _your answer_" in ".gtd/REVIEW.md" is replaced with "- [x] Calculators are plain functions."
147
140
  And gtd lands
148
141
  And the process is driven
@@ -162,6 +155,18 @@ Feature: the review learns standards from the human's answers
162
155
  [{"id":"standard-1","answer":"yes","p":0.6}]
163
156
  """
164
157
  When the process is driven
158
+ And ".gtd/REVIEW.md" gets appended:
159
+ """
160
+ ## subtract has no class
161
+
162
+ Calculators read better as classes.
163
+
164
+ Tick exactly one:
165
+
166
+ - [ ] fix as suggested
167
+ - [ ] won't fix
168
+ - [ ] _your answer_
169
+ """
165
170
  And the text "- [ ] won't fix" in ".gtd/REVIEW.md" is replaced with "- [x] won't fix"
166
171
  And gtd lands
167
172
  And the process is driven
@@ -210,6 +215,18 @@ Feature: the review learns standards from the human's answers
210
215
  [{"id":"answer-1","answer":"praise","p":0.9}]
211
216
  """
212
217
  When the process is driven
218
+ And ".gtd/REVIEW.md" gets appended:
219
+ """
220
+ ## subtract has no class
221
+
222
+ Calculators read better as classes.
223
+
224
+ Tick exactly one:
225
+
226
+ - [ ] fix as suggested
227
+ - [ ] won't fix
228
+ - [ ] _your answer_
229
+ """
213
230
  And the text "- [ ] _your answer_" in ".gtd/REVIEW.md" is replaced with "- [x] Calculators are plain functions."
214
231
  And gtd lands
215
232
  And the process is driven
@@ -267,6 +284,18 @@ Feature: the review learns standards from the human's answers
267
284
  [{"id":"standard-1","answer":"yes","p":0.9},{"id":"standard-1.contradicts-1","answer":"yes","p":0.8}]
268
285
  """
269
286
  When the process is driven
287
+ And ".gtd/REVIEW.md" gets appended:
288
+ """
289
+ ## subtract has no class
290
+
291
+ Calculators read better as classes.
292
+
293
+ Tick exactly one:
294
+
295
+ - [ ] fix as suggested
296
+ - [ ] won't fix
297
+ - [ ] _your answer_
298
+ """
270
299
  And the text "- [ ] won't fix" in ".gtd/REVIEW.md" is replaced with "- [x] won't fix"
271
300
  And gtd lands
272
301
  And the process is driven
@@ -303,6 +332,18 @@ Feature: the review learns standards from the human's answers
303
332
  [{"id":"standard-1","answer":"yes","p":0.9},{"id":"standard-1.contradicts-1","answer":"yes","p":0.8}]
304
333
  """
305
334
  When the process is driven
335
+ And ".gtd/REVIEW.md" gets appended:
336
+ """
337
+ ## subtract has no class
338
+
339
+ Calculators read better as classes.
340
+
341
+ Tick exactly one:
342
+
343
+ - [ ] fix as suggested
344
+ - [ ] won't fix
345
+ - [ ] _your answer_
346
+ """
306
347
  And the text "- [ ] won't fix" in ".gtd/REVIEW.md" is replaced with "- [x] won't fix"
307
348
  And gtd lands
308
349
  And the process is driven
@@ -1,9 +1,8 @@
1
1
  @inmem
2
- Feature: each finding the reviewer is handed names the lens that wrote it
2
+ Feature: each finding at the finding gate names the lens that wrote it
3
3
 
4
4
  Lenses append to `.gtd/QUALITY.md` in turn, so a finding belongs to the lens
5
- whose turn added it — even when two lenses give findings the same title,
6
- which the review then tells apart by a numbered suffix.
5
+ whose turn added it — even when two lenses give findings the same title.
7
6
 
8
7
  Background:
9
8
  Given the shipped workflows, as gtd install scaffolds them
@@ -62,7 +61,7 @@ Feature: each finding the reviewer is handed names the lens that wrote it
62
61
 
63
62
  From tests.
64
63
  """
65
- And step "build.quality.sorting" runs its script
64
+ And step "build.quality.gating" runs its script
66
65
  And step "build.quality.closing" runs its script
67
66
  And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
68
67
  """
@@ -84,8 +83,8 @@ Feature: each finding the reviewer is handed names the lens that wrote it
84
83
  """
85
84
  When gtd opens door "fix"
86
85
  And the process is driven
87
- Then the process rests at "build.review.await-review"
88
- And ".gtd/REVIEW.md" contains:
86
+ Then the process rests at "build.quality.gate"
87
+ And ".gtd/QUALITY.md" contains:
89
88
  """
90
89
  ## sum is untested
91
90
 
@@ -93,9 +92,23 @@ Feature: each finding the reviewer is handed names the lens that wrote it
93
92
 
94
93
  From correctness.
95
94
 
96
- ## sum is untested (2)
95
+ - [ ] fix
96
+
97
+ ## sum is untested
97
98
 
98
99
  _From lens tests._
99
100
 
100
101
  From tests.
102
+
103
+ - [ ] fix
104
+ """
105
+ When gtd lands
106
+ And the process is driven
107
+ Then the process rests at "build.review.await-review"
108
+ And ".gtd/REVIEW.md" contains:
109
+ """
110
+ Dropped at the finding gate:
111
+
112
+ - sum is untested (lens correctness)
113
+ - sum is untested (lens tests)
101
114
  """