@pmelab/gtd 49.0.0 → 51.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 (60) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +16 -3
  3. package/dist/chunks/{reserved-CYZ1bSBy.mjs → reserved-CUkk9XgL.mjs} +151 -5
  4. package/dist/chunks/{review-BewyJflS.mjs → review-BaCXlIT2.mjs} +84 -20
  5. package/dist/chunks/{steering-BXwjmocB.mjs → steering-DtUVUz9l.mjs} +44 -14
  6. package/dist/chunks/{tail-C1Oi2YEy.mjs → tail-CX6Pdsql.mjs} +1 -1
  7. package/dist/gtd.bundle.mjs +22 -5
  8. package/dist/steering.mjs +2 -2
  9. package/dist/testkit.mjs +1 -1
  10. package/dist/web/main.js +27 -3
  11. package/dist/workflow-kit.mjs +1 -1
  12. package/dist/workflows/cleanup.mjs +2 -2
  13. package/dist/workflows/feature.mjs +144 -11
  14. package/dist/workflows/fix.mjs +3 -3
  15. package/dist/workflows/review.mjs +3 -3
  16. package/dist/workflows/rollout.mjs +1 -1
  17. package/dist/workflows.mjs +415 -42
  18. package/package.json +1 -1
  19. package/src/steering/index.ts +1 -0
  20. package/src/steering/review.test.ts +60 -2
  21. package/src/steering/review.ts +66 -9
  22. package/src/workflows/feature/coverage.feature +1 -0
  23. package/src/workflows/feature/fast-path.feature +1 -0
  24. package/src/workflows/feature/feature.ts +4 -1
  25. package/src/workflows/feature/flag-mode.feature +1 -1
  26. package/src/workflows/feature/lens-models.feature +1 -0
  27. package/src/workflows/feature/lens-review.feature +17 -4
  28. package/src/workflows/feature/one-step-land.feature +1 -0
  29. package/src/workflows/feature/prototype-rounds.feature +152 -0
  30. package/src/workflows/feature/prototype.feature +419 -0
  31. package/src/workflows/feature/prototype.test.ts +43 -0
  32. package/src/workflows/feature/prototype.ts +181 -0
  33. package/src/workflows/feature/refactor-absorbs.feature +9 -1
  34. package/src/workflows/feature/refactor-first.feature +13 -3
  35. package/src/workflows/feature/refactor-rebuild.feature +29 -5
  36. package/src/workflows/feature/refactor-review.feature +111 -13
  37. package/src/workflows/feature/refactor.test.ts +14 -4
  38. package/src/workflows/feature/refactor.ts +2 -0
  39. package/src/workflows/feature/review-answers.feature +9 -1
  40. package/src/workflows/feature/review-gate.feature +21 -3
  41. package/src/workflows/feature/reviewer-walkthrough.feature +15 -3
  42. package/src/workflows/feature/seam-rejection.feature +1 -0
  43. package/src/workflows/feature/seam.feature +1 -0
  44. package/src/workflows/feature/seam.test.ts +3 -2
  45. package/src/workflows/feature/seam.ts +19 -5
  46. package/src/workflows/feature/severity-budget.feature +112 -0
  47. package/src/workflows/feature/severity-sorted-review.feature +198 -0
  48. package/src/workflows/feature/slice-findings.feature +15 -2
  49. package/src/workflows/feature/slicing-cap.feature +9 -0
  50. package/src/workflows/feature/slicing.feature +1 -0
  51. package/src/workflows/feature/standards.feature +5 -0
  52. package/src/workflows/shared/options.ts +1 -0
  53. package/src/workflows/shared/prose.ts +6 -0
  54. package/src/workflows/shared/review.ts +124 -18
  55. package/src/workflows/shared/skills.test.ts +4 -0
  56. package/src/workflows/shared/skills.ts +5 -0
  57. package/src/workflows/shared/steps.ts +1 -0
  58. package/src/workflows/shared/text.test.ts +90 -0
  59. package/src/workflows/shared/text.ts +181 -4
  60. package/src/workflows/shared/vars.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "49.0.0",
3
+ "version": "51.0.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {
@@ -16,6 +16,7 @@ export {
16
16
  reviewDecisions,
17
17
  reviewNotes,
18
18
  reviewRisks,
19
+ severityLine,
19
20
  unansweredNotes,
20
21
  type ReviewChoice,
21
22
  type ReviewDecision,
@@ -1276,6 +1276,33 @@ describe("review.view", () => {
1276
1276
  expect(view.nodes[1]!.detail).toBe("")
1277
1277
  })
1278
1278
 
1279
+ it("shows a chunk's leading severity line as its status, not its prose", () => {
1280
+ const view = review.view(
1281
+ [
1282
+ "# Review: abc1234",
1283
+ "",
1284
+ "## Ranked",
1285
+ "",
1286
+ "_critical: security, one-way door_",
1287
+ "",
1288
+ "The new export.",
1289
+ "",
1290
+ "- [ ] ./a.ts#1-1",
1291
+ "",
1292
+ "## Unranked",
1293
+ "",
1294
+ "_emphasis, not a tier_",
1295
+ "",
1296
+ "- [ ] ./a.ts#2-2",
1297
+ "",
1298
+ ].join("\n"),
1299
+ )
1300
+ expect(view.nodes.map((c) => [c.status, c.detail])).toEqual([
1301
+ ["critical: security, one-way door", "The new export."],
1302
+ [undefined, "_emphasis, not a tier_"],
1303
+ ])
1304
+ })
1305
+
1279
1306
  it("is built from one parse of the document, not one per element", () => {
1280
1307
  const uniqueContent = [
1281
1308
  "# Review: def4567",
@@ -1706,6 +1733,29 @@ describe("reviewNotes", async () => {
1706
1733
  expect(reviewNotes(base, base)).toEqual([])
1707
1734
  })
1708
1735
 
1736
+ it("an edited tier line is a note", () => {
1737
+ const was = doc("_could_\n\nProse.")
1738
+ const after = doc("_critical: this leaks the token_\n\nProse.")
1739
+ expect(reviewNotes(was, after)).toMatchObject([
1740
+ {
1741
+ kind: "chunk",
1742
+ before: "Prose.\n\n_could_",
1743
+ text: "Prose.\n\n_critical: this leaks the token_",
1744
+ },
1745
+ ])
1746
+ })
1747
+
1748
+ it("prose added under a tier is the human's alone, and a leading ! still forces", () => {
1749
+ const was = doc("_could_\n\nProse.")
1750
+ expect(reviewNotes(was, doc("_could_\n\n! Not here.\n\nProse."))).toMatchObject([
1751
+ { kind: "chunk", text: "! Not here. Prose.", forced: true },
1752
+ ])
1753
+ })
1754
+
1755
+ it("a deleted tier line asks nothing", () => {
1756
+ expect(reviewNotes(doc("_could_\n\nProse."), base)).toEqual([])
1757
+ })
1758
+
1709
1759
  it("a pointer note added", () => {
1710
1760
  const after = doc("Prose.").replace("#1-3", "#1-3 rename this")
1711
1761
  expect(reviewNotes(base, after)).toEqual([
@@ -1863,8 +1913,16 @@ describe("reviewChunks", async () => {
1863
1913
  "- [ ] _your answer_",
1864
1914
  ].join("\n")
1865
1915
  expect(reviewChunks(content)).toEqual([
1866
- { title: "Walkthrough", line: 4, pointers: 2 },
1867
- { title: "Fixed automatically", line: 9, pointers: 0 },
1916
+ {
1917
+ title: "Walkthrough",
1918
+ line: 4,
1919
+ pointers: 2,
1920
+ files: [
1921
+ { path: "./src/a.ts", line: 1, rangeEnd: 3 },
1922
+ { path: "./src/b.ts", line: 4, rangeEnd: 6 },
1923
+ ],
1924
+ },
1925
+ { title: "Fixed automatically", line: 9, pointers: 0, files: [] },
1868
1926
  ])
1869
1927
  })
1870
1928
  })
@@ -77,6 +77,8 @@ interface Changeset {
77
77
  readonly headingLine: number
78
78
  /** The chunk's answer boxes, when gtd gave it some. */
79
79
  readonly answers?: readonly AnswerOption[]
80
+ /** The tier and reason gtd ranked the chunk with, from its leading severity line. */
81
+ readonly severity?: { readonly label: string; readonly line: number }
80
82
  }
81
83
 
82
84
  interface AnswerOption {
@@ -89,6 +91,10 @@ export const ANSWER_LEAD = "Tick exactly one:"
89
91
  export const ANSWER_FIX = "fix as suggested"
90
92
  export const ANSWER_WONT_FIX = "won't fix"
91
93
 
94
+ /** A chunk's leading `_critical: security_` paragraph: the tier gtd ranked it with, and why. */
95
+ const SEVERITY_LINE_RE = /^_((?:critical|should|could)(?::[^_\n]*)?)_$/
96
+ export const severityLine = (label: string): string => `_${label}_`
97
+
92
98
  interface ReviewDoc {
93
99
  readonly shortHash?: string
94
100
  readonly fullHash?: string
@@ -521,6 +527,26 @@ const answerListOf = (
521
527
  return { lead, list, options }
522
528
  }
523
529
 
530
+ /** A leading severity line is the chunk's tier, never its prose. */
531
+ const splitSeverity = (
532
+ content: string,
533
+ run: readonly RootContent[],
534
+ ): {
535
+ readonly severity?: { readonly label: string; readonly line: number } | undefined
536
+ readonly leadingRun: readonly RootContent[]
537
+ } => {
538
+ const first = run[0]
539
+ const label =
540
+ first?.type === "paragraph"
541
+ ? SEVERITY_LINE_RE.exec(sourceText(content, first).trim())?.[1]
542
+ : undefined
543
+ if (label === undefined) return { leadingRun: run }
544
+ return {
545
+ severity: { label, line: toLspPosition(first!.position!.start).line },
546
+ leadingRun: run.slice(1),
547
+ }
548
+ }
549
+
524
550
  /** Splits one chunk's body nodes into its file pointers (hunk pointers are task items, collected recursively at ANY nesting depth via `taskItems` — a nested hunk is the same kind of hunk as a top-level one) and description prose. The description is the chunk's own leading run and never a node that contains a hunk pointer: it stops at the first node whose `taskItems` include a real pointer (at any depth — a blockquote wrapping a pointer list stops it just as a top-level list would). Every node kind in what's left before that point contributes — a `footnoteDefinition` is the one exception, excluded by `blockNodesOfRun` itself (it's a note, never document content). */
525
551
  const parseChunkBody = (
526
552
  content: string,
@@ -534,6 +560,7 @@ const parseChunkBody = (
534
560
  readonly files: readonly ReviewFile[]
535
561
  readonly errors: readonly SteeringFinding[]
536
562
  readonly answers?: readonly AnswerOption[]
563
+ readonly severity?: { readonly label: string; readonly line: number } | undefined
537
564
  /** Whether a task box outside the answer list exists. */
538
565
  readonly boxed: boolean
539
566
  } => {
@@ -543,9 +570,10 @@ const parseChunkBody = (
543
570
  const firstPointerIndex = body.findIndex(
544
571
  (n) => !isAnswer(n) && taskItems(n).some((item) => hasPointerToken(content, item)),
545
572
  )
546
- const leadingRun = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter(
573
+ const run = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter(
547
574
  (n) => !isAnswer(n),
548
575
  )
576
+ const { severity, leadingRun } = splitSeverity(content, run)
549
577
  const descriptionNodes = blockNodesOfRun(content, leadingRun)
550
578
  const descriptionInline = collapseImages(blockRunInline(content, leadingRun))
551
579
  const description = leadingRun
@@ -569,6 +597,7 @@ const parseChunkBody = (
569
597
  files,
570
598
  errors,
571
599
  ...(answer === undefined ? {} : { answers: answer.options }),
600
+ ...(severity === undefined ? {} : { severity }),
572
601
  boxed: body.some((n) => !isAnswer(n) && taskItems(n).length > 0),
573
602
  }
574
603
  }
@@ -618,6 +647,7 @@ const parseChangesets = (
618
647
  descriptionInline,
619
648
  files,
620
649
  answers,
650
+ severity,
621
651
  boxed,
622
652
  errors: bodyErrors,
623
653
  } = parseChunkBody(content, lines, title, body)
@@ -638,6 +668,7 @@ const parseChangesets = (
638
668
  files,
639
669
  headingLine,
640
670
  ...(answers === undefined ? {} : { answers }),
671
+ ...(severity === undefined ? {} : { severity }),
641
672
  })
642
673
  }
643
674
  if (changesets.length === 0) errors.push({ message: "REVIEW.md has no '##' chunks" })
@@ -997,6 +1028,7 @@ const reviewView = (content: string): SteeringView => {
997
1028
  detail: chunk.description,
998
1029
  detailInline: chunk.descriptionInline,
999
1030
  body: chunk.descriptionNodes,
1031
+ ...(chunk.severity === undefined ? {} : { status: chunk.severity.label }),
1000
1032
  anchor: { kind: "chunk", index: chunkIndex },
1001
1033
  ...noteAt(chunk.headingLine),
1002
1034
  children: chunk.files.map((file, index) => ({
@@ -1177,11 +1209,22 @@ const pointerLabel = (f: ReviewFile): string =>
1177
1209
 
1178
1210
  type Found = { line: number; note: Omit<ReviewNote, "id" | "forced"> }
1179
1211
 
1180
- // A chunk added with its own answer boxes (a driver's imported note) is
1181
- // answered by its tick, never judged as prose as well.
1182
- const chunkNote = (chunk: Changeset, prior: Changeset | undefined): Found[] =>
1183
- chunk.description === (prior?.description ?? "") ||
1184
- (prior === undefined && chunk.answers !== undefined)
1212
+ // Rewording a tier disputes it, so a changed tier joins the note — after the
1213
+ // prose, so a leading `!` still forces; deleting one asks nothing.
1214
+ const chunkProse = (chunk: Changeset | undefined, retiered: boolean): string =>
1215
+ [
1216
+ chunk?.description ?? "",
1217
+ retiered && chunk?.severity !== undefined ? severityLine(chunk.severity.label) : "",
1218
+ ]
1219
+ .filter((p) => p !== "")
1220
+ .join("\n\n")
1221
+
1222
+ const chunkNote = (chunk: Changeset, prior: Changeset | undefined): Found[] => {
1223
+ const retiered = chunk.severity !== undefined && chunk.severity.label !== prior?.severity?.label
1224
+ // A chunk added with its own answer boxes (a driver's imported note) is
1225
+ // answered by its tick, never judged as prose as well.
1226
+ return chunkProse(chunk, retiered) === chunkProse(prior, retiered) ||
1227
+ (prior === undefined && chunk.answers !== undefined)
1185
1228
  ? []
1186
1229
  : [
1187
1230
  {
@@ -1189,11 +1232,12 @@ const chunkNote = (chunk: Changeset, prior: Changeset | undefined): Found[] =>
1189
1232
  note: {
1190
1233
  kind: "chunk",
1191
1234
  anchor: chunk.title,
1192
- before: prior?.description ?? "",
1193
- text: chunk.description,
1235
+ before: chunkProse(prior, retiered),
1236
+ text: chunkProse(chunk, retiered),
1194
1237
  },
1195
1238
  },
1196
1239
  ]
1240
+ }
1197
1241
 
1198
1242
  const pointerNotes = (chunk: Changeset, prior: Changeset | undefined): Found[] =>
1199
1243
  chunk.files.flatMap((file): Found[] => {
@@ -1342,9 +1386,22 @@ export const reviewDecisions = (before: string, after: string): readonly ReviewD
1342
1386
  /** Each `##` chunk in document order: its title, 0-based heading line and how many hunk pointers it holds. */
1343
1387
  export const reviewChunks = (
1344
1388
  content: string,
1345
- ): readonly { title: string; line: number; pointers: number }[] =>
1389
+ ): readonly {
1390
+ title: string
1391
+ line: number
1392
+ pointers: number
1393
+ /** 0-based line of the chunk's severity line, when it has one. */
1394
+ severityLine?: number
1395
+ files: readonly { path: string; line?: number; rangeEnd?: number }[]
1396
+ }[] =>
1346
1397
  parseReviewDoc(content).changesets.map((c) => ({
1347
1398
  title: c.title,
1348
1399
  line: c.headingLine,
1349
1400
  pointers: c.files.length,
1401
+ ...(c.severity === undefined ? {} : { severityLine: c.severity.line }),
1402
+ files: c.files.map((f) => ({
1403
+ path: f.path,
1404
+ ...(f.line === undefined ? {} : { line: f.line }),
1405
+ ...(f.rangeEnd === undefined ? {} : { rangeEnd: f.rangeEnd }),
1406
+ })),
1350
1407
  }))
@@ -9,6 +9,7 @@ Feature: a feature is done only when every acceptance criterion has a passing te
9
9
 
10
10
  Background:
11
11
  Given the shipped workflows, as gtd install scaffolds them
12
+ And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
12
13
  And an environment variable "GTD_QUALITYREVIEWS" set to ""
13
14
  And an environment variable "GTD_FLAGMODE" set to "false"
14
15
  And a file "src/calc.ts" with:
@@ -8,6 +8,7 @@ Feature: a trivial change takes the same few beats at either risk tier
8
8
 
9
9
  Background:
10
10
  Given the shipped workflows, as gtd install scaffolds them
11
+ And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
11
12
  And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
12
13
  And an environment variable "GTD_FLAG_RISK_TIER" set to "1"
13
14
  And a file "src/greeting.ts" with:
@@ -28,6 +28,7 @@ import {
28
28
  } from "./criteria.js"
29
29
  import { design } from "./planning.js"
30
30
  import { buildTail, humanReview, isCode, reUnwind, type ReviewOutcome } from "../shared/review.js"
31
+ import { prototypeRounds } from "./prototype.js"
31
32
  import { implement, implementing, seamBuild } from "./seam.js"
32
33
  import { sliceCap, sliceCriteria, slicing } from "./slicing.js"
33
34
  import { recordRisk, riskTierOn, type RiskTier } from "./tier.js"
@@ -36,7 +37,8 @@ import { recordRisk, riskTierOn, type RiskTier } from "./tier.js"
36
37
  // `unwind` reverts the sketch (its intent survives in history) → a
37
38
  // green-baseline gate → design → per slice of the criteria, a naive pass,
38
39
  // judged with `flagMode` on: it lands as the implementation, or (refactoring
39
- // first when the judge asks) is built fresh from → the health loop → the
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
40
42
  // triaged quality lap, whose leftovers are carried on → after the last slice,
41
43
  // a reviewer's walkthrough of the whole change, every slice's findings
42
44
  // among its chunks, for the human, which
@@ -227,6 +229,7 @@ export const ordinaryStart = async (): Promise<void> => {
227
229
  // Read right after idle, so a bad value refuses the landing that would pin it.
228
230
  flagModeOn()
229
231
  sliceCap()
232
+ prototypeRounds()
230
233
  const stated = statedCriteria(changes(), head())
231
234
  await unwind()
232
235
  if (changes(FEEDBACK).some((c) => c.status !== "deleted")) {
@@ -11,7 +11,7 @@ Feature: flag mode off lands the naive pass with no release flag, seam judge or
11
11
  """
12
12
  import { feature } from "@pmelab/gtd/workflows/feature"
13
13
  export { summary, steering, skills, access } from "@pmelab/gtd/workflows/feature"
14
- export default feature({ flagMode: false })
14
+ export default feature({ flagMode: false, prototypeRounds: "0" })
15
15
  """
16
16
  And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
17
17
  And a file "src/calc.ts" with:
@@ -12,6 +12,7 @@ Feature: each quality lens runs on its own model hint
12
12
  export { summary, steering, skills, access } from "@pmelab/gtd/workflows/feature"
13
13
  export default feature({
14
14
  flagMode: false,
15
+ prototypeRounds: "0",
15
16
  qualityReviews: "correctness, conventions, test-audit=opus",
16
17
  })
17
18
  """
@@ -9,6 +9,7 @@ Feature: the quality lap's findings are triaged before the review
9
9
 
10
10
  Background:
11
11
  Given the shipped workflows, as gtd install scaffolds them
12
+ And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
12
13
  And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
13
14
  And an environment variable "GTD_FLAGMODE" set to "false"
14
15
  And a file "src/calc.ts" with:
@@ -75,6 +76,10 @@ Feature: the quality lap's findings are triaged before the review
75
76
  And step "build.quality.sorting" runs its script
76
77
  And step "build.quality.closing" runs its script
77
78
  And step "build.review.carrying" runs its script
79
+ And judge step "build.review.ranking" answers:
80
+ """
81
+ []
82
+ """
78
83
 
79
84
  Scenario: a mechanical finding is fixed and listed, noise dropped and listed, a design one noted
80
85
  Given judge step "build.quality.triage" answers:
@@ -110,6 +115,8 @@ Feature: the quality lap's findings are triaged before the review
110
115
  """
111
116
  ## AC-1 ignores floating point
112
117
 
118
+ _critical: judge unsure_
119
+
113
120
  Should subtract round? The criterion does not say.
114
121
 
115
122
  In the code:
@@ -147,7 +154,8 @@ Feature: the quality lap's findings are triaged before the review
147
154
  gtd(check): build.quality.sorting → build.fix.quality.fixing
148
155
  gtd(agent): build.fix.quality.fixing → build.health.check
149
156
  gtd(check): build.health.check → build.review.reviewing
150
- gtd(agent): build.review.reviewing → build.review.carrying
157
+ gtd(agent): build.review.reviewing → build.review.ranking
158
+ gtd(judge): build.review.ranking → build.review.carrying
151
159
  gtd(check): build.review.carrying → build.review.await-review
152
160
  """
153
161
 
@@ -189,7 +197,8 @@ Feature: the quality lap's findings are triaged before the review
189
197
  """
190
198
  gtd(judge): build.quality.triage → build.quality.sorting
191
199
  gtd(check): build.quality.sorting → build.review.reviewing
192
- gtd(agent): build.review.reviewing → build.review.carrying
200
+ gtd(agent): build.review.reviewing → build.review.ranking
201
+ gtd(judge): build.review.ranking → build.review.carrying
193
202
  gtd(check): build.review.carrying → build.review.await-review
194
203
  """
195
204
 
@@ -228,6 +237,8 @@ Feature: the quality lap's findings are triaged before the review
228
237
  """
229
238
  ## add could be a method
230
239
 
240
+ _critical: judge unsure_
241
+
231
242
  _The fixer left this unresolved._
232
243
 
233
244
  A class would read better.
@@ -242,7 +253,8 @@ Feature: the quality lap's findings are triaged before the review
242
253
  gtd(agent): build.fix.quality.fixing → build.health.check
243
254
  gtd(check): build.health.check → build.quality.closing
244
255
  gtd(check): build.quality.closing → build.review.reviewing
245
- gtd(agent): build.review.reviewing → build.review.carrying
256
+ gtd(agent): build.review.reviewing → build.review.ranking
257
+ gtd(judge): build.review.ranking → build.review.carrying
246
258
  gtd(check): build.review.carrying → build.review.await-review
247
259
  """
248
260
 
@@ -344,7 +356,8 @@ Feature: the quality lap's findings are triaged before the review
344
356
  And ".gtd/REVIEW.md" does not exist
345
357
  And the commit subjects end with:
346
358
  """
347
- gtd(agent): build.review.reviewing → build.review.carrying
359
+ gtd(agent): build.review.reviewing → build.review.ranking
360
+ gtd(judge): build.review.ranking → build.review.carrying
348
361
  gtd(check): build.review.carrying → build.review.await-review
349
362
  gtd(human): build.review.await-review → build.review.closing
350
363
  gtd(check): build.review.closing → idle
@@ -10,6 +10,7 @@ Feature: a naive pass the judge rules land is itself the implementation
10
10
 
11
11
  Background:
12
12
  Given the shipped workflows, as gtd install scaffolds them
13
+ And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
13
14
  And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
14
15
  And an environment variable "GTD_FLAG_RISK_TIER" set to "0"
15
16
  And a file "src/greeting.ts" with:
@@ -0,0 +1,152 @@
1
+ @inmem
2
+ Feature: past its round cap the hands-on test rests at a person's gate
3
+
4
+ `prototypeRounds` caps the fix turns a slice's hands-on test gets. Once they
5
+ are spent and the report still has open items, the process rests at
6
+ implement.prototype.open naming them; a person's fix there is tested again
7
+ with a fresh budget.
8
+
9
+ Background:
10
+ Given the shipped workflows, as gtd install scaffolds them
11
+ And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
12
+ And an environment variable "GTD_FLAGMODE" set to "false"
13
+ And an environment variable "GTD_PROTOTYPEROUNDS" set to "1"
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(a, b) returns a - b.
34
+ - AC-2: add is unchanged.
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
+ And step "seam.naive" writes "src/calc.test.ts" with:
43
+ """
44
+ it("[AC-1] subtracts", () => expect(subtract(3, 1)).toBe(2))
45
+ it("[AC-2] adds", () => expect(add(1, 1)).toBe(2))
46
+ """
47
+ And step "seam.check" changes nothing
48
+ And step "implement.applying" runs its script
49
+ And step "implement.health.check" changes nothing
50
+ And judge step "implement.coverage" answers:
51
+ """
52
+ [{"id":"AC-1","answer":"yes","p":0.9},{"id":"AC-2","answer":"yes","p":0.9}]
53
+ """
54
+ And step "implement.closing" runs its script
55
+
56
+ Scenario: past the round cap a person is asked, naming the open items
57
+ Given step "implement.prototype.testing", when prompted with "Find how this project", writes ".gtd/PROTOTYPE.md" with:
58
+ """
59
+ ## AC-1: fail
60
+
61
+ `subtract(3, 1)` printed 4. Expected 2.
62
+
63
+ ## AC-2: pass
64
+
65
+ Printed 2.
66
+ """
67
+ And step "implement.prototype.testing", when prompted with "This is a re-test", writes ".gtd/PROTOTYPE.md" with:
68
+ """
69
+ ## AC-1: fail
70
+
71
+ Re-tested: `subtract(3, 1)` printed -2. Expected 2.
72
+
73
+ ## AC-2: pass
74
+
75
+ Printed 2.
76
+ """
77
+ And step "implement.prototype.fix.fixing" writes "src/calc.ts" with:
78
+ """
79
+ export const add = (a: number, b: number) => a + b
80
+ export const subtract = (a: number, b: number) => b - a
81
+ """
82
+ And step "implement.prototype.health.check" changes nothing
83
+ When the process is driven
84
+ Then the process rests at "implement.prototype.open"
85
+ And the resting step's content contains "- AC-1: fail"
86
+ And the resting step's content contains "after 1 fix round"
87
+ And the resting step's content does not contain "AC-2"
88
+
89
+ Scenario: a person's change at the gate is tested again
90
+ Given step "implement.prototype.testing", when prompted with "Find how this project", writes ".gtd/PROTOTYPE.md" with:
91
+ """
92
+ ## AC-1: blocked
93
+
94
+ `subtract` is not exported from any entry point I could run.
95
+
96
+ ## AC-2: pass
97
+
98
+ Printed 2.
99
+ """
100
+ And step "implement.prototype.open" writes "src/calc.ts" with:
101
+ """
102
+ export const add = (a: number, b: number) => a + b
103
+ export const subtract = (a: number, b: number) => a - b
104
+ """
105
+ And step "implement.prototype.health.check" changes nothing
106
+ And step "implement.prototype.testing", when prompted with "This is a re-test", writes ".gtd/PROTOTYPE.md" with:
107
+ """
108
+ ## AC-1: pass
109
+
110
+ `subtract(3, 1)` printed 2.
111
+
112
+ ## AC-2: pass
113
+
114
+ Printed 2.
115
+ """
116
+ When the process is driven
117
+ Then the process rests at "build.quality.correctness.reviewing"
118
+ And the commit subjects end with:
119
+ """
120
+ gtd(agent): implement.prototype.testing → implement.prototype.open
121
+ gtd(human): implement.prototype.open → implement.prototype.health.check
122
+ gtd(check): implement.prototype.health.check → implement.prototype.testing
123
+ gtd(agent): implement.prototype.testing → implement.closing
124
+ gtd(check): implement.closing → build.quality.correctness.reviewing
125
+ """
126
+
127
+ Scenario: a re-test that sees exactly what the last round saw still ends the round
128
+ Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
129
+ """
130
+ ## AC-1: fail
131
+
132
+ `subtract(3, 1)` printed 4. Expected 2.
133
+
134
+ ## AC-2: pass
135
+
136
+ Printed 2.
137
+ """
138
+ And step "implement.prototype.fix.fixing" writes "src/calc.ts" with:
139
+ """
140
+ export const add = (a: number, b: number) => a + b
141
+ export const subtract = (a: number, b: number) => b - a
142
+ """
143
+ And step "implement.prototype.health.check" changes nothing
144
+ When the process is driven
145
+ Then the process rests at "implement.prototype.open"
146
+ And the commit subjects end with:
147
+ """
148
+ gtd(agent): implement.prototype.testing → implement.prototype.fix.fixing
149
+ gtd(agent): implement.prototype.fix.fixing → implement.prototype.health.check
150
+ gtd(check): implement.prototype.health.check → implement.prototype.testing
151
+ gtd(agent): implement.prototype.testing → implement.prototype.open
152
+ """