@pmelab/gtd 30.1.0 → 32.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +29 -23
- package/claude/hooks/ship.ts +7 -3
- package/dist/chunks/{reserved-DDiiAZ8U.mjs → reserved-CHTJTzpA.mjs} +52 -3
- package/dist/chunks/{tail-ILPlELuy.mjs → tail-CCsDBbZe.mjs} +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +137 -8
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +188 -10
- package/package.json +1 -1
- package/src/workflows/feature/acceptance-criteria-tier.feature +25 -0
- package/src/workflows/feature/acceptance-criteria.feature +189 -0
- package/src/workflows/feature/criteria.test.ts +106 -0
- package/src/workflows/feature/criteria.ts +168 -0
- package/src/workflows/feature/fast-path.feature +4 -0
- package/src/workflows/feature/feature.test.ts +41 -11
- package/src/workflows/feature/feature.ts +7 -4
- package/src/workflows/feature/flag-mode.feature +8 -0
- package/src/workflows/feature/index.ts +1 -0
- package/src/workflows/feature/one-step-land.feature +4 -0
- package/src/workflows/feature/planning.ts +2 -0
- package/src/workflows/feature/refactor-absorbs.feature +4 -0
- package/src/workflows/feature/refactor-first.feature +4 -0
- package/src/workflows/feature/refactor-review.feature +24 -0
- package/src/workflows/feature/risk-tier.feature +32 -0
- package/src/workflows/feature/seam-rejection.feature +8 -0
- package/src/workflows/feature/seam.feature +8 -0
- package/src/workflows/feature/seam.ts +12 -7
- package/src/workflows/feature/tier-pin.feature +4 -0
- package/src/workflows/index.ts +1 -0
- package/src/workflows/shared/text.ts +77 -2
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
answered,
|
|
3
|
+
changes,
|
|
4
|
+
judge,
|
|
5
|
+
read,
|
|
6
|
+
refuse,
|
|
7
|
+
run,
|
|
8
|
+
scope,
|
|
9
|
+
type Change,
|
|
10
|
+
} from "../../flows/index.js"
|
|
11
|
+
import { markFenced } from "../shared/fences.js"
|
|
12
|
+
import { REQUIREMENTS } from "../shared/steps.js"
|
|
13
|
+
import * as t from "../shared/text.js"
|
|
14
|
+
|
|
15
|
+
export interface Criterion {
|
|
16
|
+
readonly id: string
|
|
17
|
+
readonly text: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Acceptance criteria the human's sketch stated outright, ready to normalise. */
|
|
21
|
+
export interface StatedCriteria {
|
|
22
|
+
readonly path: string
|
|
23
|
+
readonly commit: string
|
|
24
|
+
/** The file as the sketch left it: `unwind` reverts it out of the tree. */
|
|
25
|
+
readonly text: string
|
|
26
|
+
readonly criteria: readonly Criterion[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Below this the judge's `yes` is not trusted: a wrong skip builds against criteria nobody can check. */
|
|
30
|
+
const CLEAR_MIN_P = 0.8
|
|
31
|
+
|
|
32
|
+
const HEADING = /^ {0,3}(#{1,6})\s+(.*?)\s*#*\s*$/
|
|
33
|
+
const ITEM = /^ ?(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.*)$/
|
|
34
|
+
const ID = /^(AC-\d+):\s*(.*)$/
|
|
35
|
+
|
|
36
|
+
/** The list items under the first heading `title` matches (at `level` when given), up to the next heading; wrapped lines joined, fenced lines ignored. */
|
|
37
|
+
const listUnder = (text: string, title: RegExp, level?: number): string[] | undefined => {
|
|
38
|
+
const lines = markFenced(text)
|
|
39
|
+
const at = lines.findIndex((l) => {
|
|
40
|
+
const heading = l.fenced ? null : HEADING.exec(l.line)
|
|
41
|
+
return (
|
|
42
|
+
heading !== null &&
|
|
43
|
+
(level === undefined || heading[1]!.length === level) &&
|
|
44
|
+
title.test(heading[2]!)
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
if (at === -1) return undefined
|
|
48
|
+
const items: string[] = []
|
|
49
|
+
let open = false
|
|
50
|
+
for (const { line, fenced } of lines.slice(at + 1)) {
|
|
51
|
+
if (fenced) {
|
|
52
|
+
open = false
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
if (HEADING.test(line)) break
|
|
56
|
+
const item = ITEM.exec(line)
|
|
57
|
+
if (item !== null) {
|
|
58
|
+
items.push(item[1]!.trim())
|
|
59
|
+
open = true
|
|
60
|
+
} else if (open && line.trim() !== "") {
|
|
61
|
+
items[items.length - 1] = `${items.at(-1)!} ${line.trim()}`.trim()
|
|
62
|
+
} else open = false
|
|
63
|
+
}
|
|
64
|
+
return items
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The `- AC-<n>: …` list of `## Acceptance Criteria`, or `undefined` when the section is missing. */
|
|
68
|
+
export const criteriaOf = (text: string): readonly Criterion[] | undefined =>
|
|
69
|
+
listUnder(text, /^acceptance criteria$/i, 2)?.map((item) => {
|
|
70
|
+
const id = ID.exec(item)
|
|
71
|
+
return id === null ? { id: "", text: item } : { id: id[1]!, text: id[2]! }
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
/** Items numbered `AC-1`…, unless every one already carries its own distinct ID. */
|
|
75
|
+
const numbered = (items: readonly string[]): Criterion[] => {
|
|
76
|
+
const ids = items.map((item) => ID.exec(item))
|
|
77
|
+
const own = ids.every((id) => id !== null) && new Set(ids.map((id) => id![1])).size === ids.length
|
|
78
|
+
return items.map((item, i) =>
|
|
79
|
+
own
|
|
80
|
+
? { id: ids[i]![1]!, text: ids[i]![2]! }
|
|
81
|
+
: { id: `AC-${i + 1}`, text: item.replace(ID, "$2") },
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const STATED = /^acceptance criteria:?$/i
|
|
86
|
+
|
|
87
|
+
const statedItems = (text: string | undefined): string[] =>
|
|
88
|
+
text === undefined ? [] : (listUnder(text, STATED) ?? []).filter((item) => item !== "")
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The criteria a `TODO.md` in the sketch `sketch` (the idle turn's changes)
|
|
92
|
+
* added or changed under an `Acceptance criteria` heading. More than one such
|
|
93
|
+
* file is ambiguous, and any other change in the sketch is input only triage
|
|
94
|
+
* folds in, so none.
|
|
95
|
+
*/
|
|
96
|
+
export const statedCriteria = (
|
|
97
|
+
sketch: readonly Change[],
|
|
98
|
+
commit: string,
|
|
99
|
+
): StatedCriteria | undefined => {
|
|
100
|
+
const found = sketch.flatMap((change) => {
|
|
101
|
+
if (!/(^|\/)TODO\.md$/.test(change.path) || change.after === undefined) return []
|
|
102
|
+
const items = statedItems(change.after)
|
|
103
|
+
if (items.length === 0 || items.join("\n") === statedItems(change.before).join("\n")) return []
|
|
104
|
+
return [{ path: change.path, commit, text: change.after, criteria: numbered(items) }]
|
|
105
|
+
})
|
|
106
|
+
return found.length === 1 && sketch.length === 1 ? found[0] : undefined
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const listing = (criteria: readonly Criterion[]): string => criteria.map(t.criterionLine).join("\n")
|
|
110
|
+
|
|
111
|
+
/** Refuse a turn that leaves `file` without ID'd acceptance criteria. A deleted file is `requireProgress`'s call. */
|
|
112
|
+
export const requireCriteria = (file: string): void => {
|
|
113
|
+
const text = read(file)
|
|
114
|
+
if (text === undefined) return
|
|
115
|
+
const criteria = criteriaOf(text) ?? []
|
|
116
|
+
const unnamed = criteria.filter((c) => c.id === "")
|
|
117
|
+
const ids = criteria.map((c) => c.id).filter((id) => id !== "")
|
|
118
|
+
const twice = ids.filter((id, i) => ids.indexOf(id) !== i)
|
|
119
|
+
if (criteria.length > 0 && unnamed.length === 0 && twice.length === 0) return
|
|
120
|
+
const why =
|
|
121
|
+
criteria.length === 0
|
|
122
|
+
? `no "## Acceptance Criteria" list`
|
|
123
|
+
: unnamed.length > 0
|
|
124
|
+
? `${unnamed.length} criterion without an ID:\n${unnamed.map((c) => ` - ${c.text}`).join("\n")}`
|
|
125
|
+
: `duplicate IDs: ${[...new Set(twice)].join(", ")}`
|
|
126
|
+
refuse(
|
|
127
|
+
`gtd land: acceptance-criteria: ${file} has ${why} — list every criterion under "## Acceptance Criteria" as "- AC-<n>: <checkable criterion>", each ID unique and never reused`,
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Refuse a turn that changed the acceptance criteria in `file`: after the design gate only another design lap may. */
|
|
132
|
+
export const requireCriteriaFrozen = (file: string): void => {
|
|
133
|
+
const change = changes().get(file)
|
|
134
|
+
if (change === undefined) return
|
|
135
|
+
const before = listing(criteriaOf(change.before ?? "") ?? [])
|
|
136
|
+
if (before === listing(criteriaOf(change.after ?? "") ?? [])) return
|
|
137
|
+
refuse(
|
|
138
|
+
`gtd land: criteria-frozen: the acceptance criteria in ${file} are settled — restore them; only a design lap changes them`,
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Whether the judge trusts `stated` as clear and checkable, every criterion of it. Truncated evidence never is. */
|
|
143
|
+
const clear = async (stated: StatedCriteria): Promise<boolean> => {
|
|
144
|
+
const { answers, truncated } = await judge("clear", {
|
|
145
|
+
questions: [t.clearCriteriaQuestion],
|
|
146
|
+
evidence: { criteria: listing(stated.criteria), sketch: stated.text },
|
|
147
|
+
message: t.clearCriteriaMessage(),
|
|
148
|
+
label: "Judging the stated acceptance criteria",
|
|
149
|
+
})
|
|
150
|
+
return truncated.length === 0 && answered(answers.clear, "yes", CLEAR_MIN_P)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The skip path: when the sketch stated clear, checkable criteria, write them
|
|
155
|
+
* to `.gtd/REQUIREMENTS.md` as they are and resolve `true` — no triage, no
|
|
156
|
+
* design gate. `false` when design must run.
|
|
157
|
+
*/
|
|
158
|
+
export const skipDesign = (stated: StatedCriteria | undefined): Promise<boolean> =>
|
|
159
|
+
stated === undefined
|
|
160
|
+
? Promise.resolve(false)
|
|
161
|
+
: scope("design", async () => {
|
|
162
|
+
if (!(await clear(stated))) return false
|
|
163
|
+
await run("normalise", t.normaliseCriteriaScript(stated), {
|
|
164
|
+
label: "Recording the stated acceptance criteria",
|
|
165
|
+
file: REQUIREMENTS,
|
|
166
|
+
})
|
|
167
|
+
return true
|
|
168
|
+
})
|
|
@@ -48,6 +48,10 @@ Feature: a trivial change takes the same few beats at either risk tier, flag-fre
|
|
|
48
48
|
## Risk Tier
|
|
49
49
|
|
|
50
50
|
<tier> — one-line copy fix
|
|
51
|
+
|
|
52
|
+
## Acceptance Criteria
|
|
53
|
+
|
|
54
|
+
- AC-1: the change behaves as described.
|
|
51
55
|
"""
|
|
52
56
|
When the process is driven
|
|
53
57
|
Then the process rests at "seam.naive"
|
|
@@ -72,7 +72,10 @@ describe("planAndBuild", () => {
|
|
|
72
72
|
unwind: () => undefined,
|
|
73
73
|
"start-gate.check": () => undefined,
|
|
74
74
|
"design.triage": (_, r) =>
|
|
75
|
-
r.write(
|
|
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
|
+
),
|
|
76
79
|
"design.gate.answer": () => undefined,
|
|
77
80
|
"seam.naive": (_, r) => r.write("src/calc.ts", SUBTRACT),
|
|
78
81
|
"seam.judge": () => '[{"id":"shape","answer":"refactor-first","p":0.9}]',
|
|
@@ -102,7 +105,7 @@ describe("planAndBuild", () => {
|
|
|
102
105
|
const requirements = (_: unknown, r: typeof repo) =>
|
|
103
106
|
r.write(
|
|
104
107
|
".gtd/REQUIREMENTS.md",
|
|
105
|
-
"# Requirements\n\n## Concerns\n\n### Subtract\n\n- `subtract`\n",
|
|
108
|
+
"# Requirements\n\n## Concerns\n\n### Subtract\n\n- `subtract`\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
106
109
|
)
|
|
107
110
|
const lap = {
|
|
108
111
|
unwind: () => undefined,
|
|
@@ -134,7 +137,11 @@ describe("planAndBuild", () => {
|
|
|
134
137
|
const rest = await repo.drive({
|
|
135
138
|
"build.review.closing": runsItsScript,
|
|
136
139
|
"re-unwind": runsItsScript,
|
|
137
|
-
"design.triage": (_, r) =>
|
|
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
|
+
),
|
|
138
145
|
"design.gate.answer": () => undefined,
|
|
139
146
|
})
|
|
140
147
|
expect(rest.state).toBe("implement.building")
|
|
@@ -186,7 +193,10 @@ describe("afterTail feedback lap", () => {
|
|
|
186
193
|
await repo.land()
|
|
187
194
|
await repo.drive({
|
|
188
195
|
"build.review.collecting": (_, r) => {
|
|
189
|
-
r.write(
|
|
196
|
+
r.write(
|
|
197
|
+
".gtd/REQUIREMENTS.md",
|
|
198
|
+
"# Requirements\n\n- multiply\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
199
|
+
)
|
|
190
200
|
r.write(".gtd/REVIEW.md", `${review("also export multiply")} - A: noted.\n`)
|
|
191
201
|
},
|
|
192
202
|
})
|
|
@@ -199,7 +209,10 @@ describe("afterTail feedback lap", () => {
|
|
|
199
209
|
"build.review.closing": runsItsScript,
|
|
200
210
|
"re-unwind": runsItsScript,
|
|
201
211
|
"design.triage": (_: unknown, r: ReturnType<typeof testRepo>) =>
|
|
202
|
-
r.write(
|
|
212
|
+
r.write(
|
|
213
|
+
".gtd/REQUIREMENTS.md",
|
|
214
|
+
"# Requirements\n\n- multiply, designed\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
215
|
+
),
|
|
203
216
|
"design.gate.answer": () => undefined,
|
|
204
217
|
}
|
|
205
218
|
|
|
@@ -221,7 +234,10 @@ describe("afterTail feedback lap", () => {
|
|
|
221
234
|
expect(prompts[0]).toContain("it covers everything since the base")
|
|
222
235
|
expect(prompts[0]).not.toContain("only what is new since the previous review")
|
|
223
236
|
const writes: Record<string, () => [string, string]> = {
|
|
224
|
-
"design.triage": () => [
|
|
237
|
+
"design.triage": () => [
|
|
238
|
+
".gtd/REQUIREMENTS.md",
|
|
239
|
+
"# Requirements\n\n- multiply, designed\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
240
|
+
],
|
|
225
241
|
"implement.building": () => ["src/calc.ts", `${CALC}// again\n`],
|
|
226
242
|
}
|
|
227
243
|
const actors = new Proxy({} as Record<string, Actor>, {
|
|
@@ -295,13 +311,20 @@ describe("an in-flight 28.0.0 naive-pass process", () => {
|
|
|
295
311
|
from: string,
|
|
296
312
|
to: string,
|
|
297
313
|
) => {
|
|
298
|
-
if (from === "seam.steer")
|
|
314
|
+
if (from === "seam.steer")
|
|
315
|
+
repo.write(
|
|
316
|
+
".gtd/REQUIREMENTS.md",
|
|
317
|
+
"# Requirements\n\n- steered\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
318
|
+
)
|
|
299
319
|
if (from === "build.review.reviewing") repo.write(".gtd/REVIEW.md", review())
|
|
300
320
|
if (to === "build.review.collecting")
|
|
301
321
|
repo.write(".gtd/REVIEW.md", review("also export multiply"))
|
|
302
322
|
if (from === "build.review.collecting") {
|
|
303
323
|
repo.write(".gtd/REVIEW.md", `${review("also export multiply")} - A: noted.\n`)
|
|
304
|
-
repo.write(
|
|
324
|
+
repo.write(
|
|
325
|
+
".gtd/REQUIREMENTS.md",
|
|
326
|
+
"# Requirements\n\n- multiply\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
327
|
+
)
|
|
305
328
|
}
|
|
306
329
|
if (to === "build.review.closing") repo.write(".gtd/REVIEW.md", review())
|
|
307
330
|
}
|
|
@@ -313,7 +336,10 @@ describe("an in-flight 28.0.0 naive-pass process", () => {
|
|
|
313
336
|
unwind: () => undefined,
|
|
314
337
|
"start-gate.check": () => undefined,
|
|
315
338
|
"design.triage": (_, r) =>
|
|
316
|
-
r.write(
|
|
339
|
+
r.write(
|
|
340
|
+
".gtd/REQUIREMENTS.md",
|
|
341
|
+
"# Requirements\n\n## Concerns\n\n### S\n\n- `s`\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
342
|
+
),
|
|
317
343
|
"design.gate.answer": () => undefined,
|
|
318
344
|
"seam.naive": (_, r) => r.write("src/calc.ts", `${CALC}// naive\n`),
|
|
319
345
|
})
|
|
@@ -356,7 +382,8 @@ describe("an in-flight 28.0.0 naive-pass process", () => {
|
|
|
356
382
|
})
|
|
357
383
|
|
|
358
384
|
describe("an in-flight process from before the architecture path was removed", () => {
|
|
359
|
-
const REQUIREMENTS =
|
|
385
|
+
const REQUIREMENTS =
|
|
386
|
+
"# Requirements\n\n## Concerns\n\n### Subtract\n\n- `subtract`\n\n## Acceptance Criteria\n\n- AC-1: done\n"
|
|
360
387
|
|
|
361
388
|
/** Its history up to the design gate, forged with the old opening commit's `flag_naive_pass=0`. */
|
|
362
389
|
const atDesignGate = async () => {
|
|
@@ -416,7 +443,10 @@ describe("flagMode", () => {
|
|
|
416
443
|
unwind: () => undefined,
|
|
417
444
|
"start-gate.check": () => undefined,
|
|
418
445
|
"design.triage": (_, r) =>
|
|
419
|
-
r.write(
|
|
446
|
+
r.write(
|
|
447
|
+
".gtd/REQUIREMENTS.md",
|
|
448
|
+
"# Requirements\n\n## Concerns\n\n### S\n\n- `s`\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
449
|
+
),
|
|
420
450
|
"design.gate.answer": () => undefined,
|
|
421
451
|
})
|
|
422
452
|
}
|
|
@@ -13,6 +13,7 @@ import { flagModeOn, shipped } from "../shared/options.js"
|
|
|
13
13
|
import { FEEDBACK } from "../shared/steps.js"
|
|
14
14
|
import { refusePendingHandOff } from "../shared/tail.js"
|
|
15
15
|
import * as t from "../shared/text.js"
|
|
16
|
+
import { skipDesign, statedCriteria, type StatedCriteria } from "./criteria.js"
|
|
16
17
|
import { design } from "./planning.js"
|
|
17
18
|
import { buildTail, isCode, reUnwind, type ReviewOutcome } from "./review.js"
|
|
18
19
|
import { implement, seamBuild, type SeamOutcome } from "./seam.js"
|
|
@@ -54,9 +55,10 @@ export const buildFeature = async (firstBase: string): Promise<void> => {
|
|
|
54
55
|
})
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
/** Design
|
|
58
|
-
export const planAndBuild = async (firstBase: string): Promise<void> => {
|
|
59
|
-
|
|
58
|
+
/** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
|
|
59
|
+
export const planAndBuild = async (firstBase: string, stated?: StatedCriteria): Promise<void> => {
|
|
60
|
+
// GTD_FLAG_RISK_TIER: the tier is proposed at triage and set at the design gate, so no skip.
|
|
61
|
+
if (!(await skipDesign(riskTierOn() ? undefined : stated))) await design(firstBase)
|
|
60
62
|
await buildFeature(firstBase)
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -80,6 +82,7 @@ export const ordinaryStart = async (): Promise<void> => {
|
|
|
80
82
|
refusePendingHandOff()
|
|
81
83
|
// Read right after idle, so a bad value refuses the landing that would pin it.
|
|
82
84
|
flagModeOn()
|
|
85
|
+
const stated = statedCriteria(changes(), head())
|
|
83
86
|
await unwind()
|
|
84
87
|
if (changes(FEEDBACK).some((c) => c.status !== "deleted")) {
|
|
85
88
|
await human("unwind-failed", {
|
|
@@ -89,7 +92,7 @@ export const ordinaryStart = async (): Promise<void> => {
|
|
|
89
92
|
})
|
|
90
93
|
}
|
|
91
94
|
await gate("start-gate", t.startGateBlockedMessage())
|
|
92
|
-
await planAndBuild(start())
|
|
95
|
+
await planAndBuild(start(), stated)
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
export const feature = shipped(ordinaryStart)
|
|
@@ -36,6 +36,10 @@ Feature: flag mode off builds a feature with no release flag, seam judge or refa
|
|
|
36
36
|
### Subtract
|
|
37
37
|
|
|
38
38
|
- `subtract(a, b)` returns `a - b`.
|
|
39
|
+
|
|
40
|
+
## Acceptance Criteria
|
|
41
|
+
|
|
42
|
+
- AC-1: the change behaves as described.
|
|
39
43
|
"""
|
|
40
44
|
And step "design.gate.answer" changes nothing
|
|
41
45
|
|
|
@@ -116,6 +120,10 @@ Feature: flag mode off builds a feature with no release flag, seam judge or refa
|
|
|
116
120
|
### Multiply
|
|
117
121
|
|
|
118
122
|
- Export `multiply()`.
|
|
123
|
+
|
|
124
|
+
## Acceptance Criteria
|
|
125
|
+
|
|
126
|
+
- AC-1: the change behaves as described.
|
|
119
127
|
"""
|
|
120
128
|
And step "build.review.collecting" writes ".gtd/REVIEW.md" with:
|
|
121
129
|
"""
|
|
@@ -34,6 +34,10 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
|
|
|
34
34
|
### Typo
|
|
35
35
|
|
|
36
36
|
- greeting reads "Hello, world".
|
|
37
|
+
|
|
38
|
+
## Acceptance Criteria
|
|
39
|
+
|
|
40
|
+
- AC-1: the change behaves as described.
|
|
37
41
|
"""
|
|
38
42
|
And step "design.gate.answer" changes nothing
|
|
39
43
|
And step "seam.naive" writes "src/greeting.ts" with:
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
scope,
|
|
9
9
|
} from "../../flows/index.js"
|
|
10
10
|
import { REQUIREMENTS } from "../shared/steps.js"
|
|
11
|
+
import { requireCriteria } from "./criteria.js"
|
|
11
12
|
import { answerProductQuestions, triage } from "./steps.js"
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -31,5 +32,6 @@ export const design = (base: string): Promise<void> =>
|
|
|
31
32
|
await triage(base)
|
|
32
33
|
requireProgress(REQUIREMENTS)
|
|
33
34
|
requireReplies(REQUIREMENTS)
|
|
35
|
+
requireCriteria(REQUIREMENTS)
|
|
34
36
|
} while (await noteGate(REQUIREMENTS, answerProductQuestions))
|
|
35
37
|
})
|
|
@@ -35,6 +35,10 @@ Feature: a refactor that carried the whole feature still gets a feature review
|
|
|
35
35
|
### Subtract
|
|
36
36
|
|
|
37
37
|
- `subtract(a, b)` returns `a - b`.
|
|
38
|
+
|
|
39
|
+
## Acceptance Criteria
|
|
40
|
+
|
|
41
|
+
- AC-1: the change behaves as described.
|
|
38
42
|
"""
|
|
39
43
|
And step "design.gate.answer" changes nothing
|
|
40
44
|
|
|
@@ -34,6 +34,10 @@ Feature: a refactor-first judgment walks one fixed path to idle
|
|
|
34
34
|
### Subtract
|
|
35
35
|
|
|
36
36
|
- `subtract(a, b)` returns `a - b`.
|
|
37
|
+
|
|
38
|
+
## Acceptance Criteria
|
|
39
|
+
|
|
40
|
+
- AC-1: the change behaves as described.
|
|
37
41
|
"""
|
|
38
42
|
And step "design.gate.answer" changes nothing
|
|
39
43
|
|
|
@@ -46,6 +46,10 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
46
46
|
### Subtract
|
|
47
47
|
|
|
48
48
|
- `subtract(a, b)` returns `a - b`.
|
|
49
|
+
|
|
50
|
+
## Acceptance Criteria
|
|
51
|
+
|
|
52
|
+
- AC-1: the change behaves as described.
|
|
49
53
|
"""
|
|
50
54
|
And step "design.gate.answer" changes nothing
|
|
51
55
|
And step "seam.naive" writes "src/calc.ts" with:
|
|
@@ -126,6 +130,10 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
126
130
|
### Subtract
|
|
127
131
|
|
|
128
132
|
- `subtract(a, b)` returns `a - b`.
|
|
133
|
+
|
|
134
|
+
## Acceptance Criteria
|
|
135
|
+
|
|
136
|
+
- AC-1: the change behaves as described.
|
|
129
137
|
"""
|
|
130
138
|
And step "design.gate.answer" changes nothing
|
|
131
139
|
And step "seam.naive" writes "src/calc.ts" with:
|
|
@@ -205,6 +213,10 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
205
213
|
### Subtract
|
|
206
214
|
|
|
207
215
|
- `subtract(a, b)` returns `a - b`.
|
|
216
|
+
|
|
217
|
+
## Acceptance Criteria
|
|
218
|
+
|
|
219
|
+
- AC-1: the change behaves as described.
|
|
208
220
|
"""
|
|
209
221
|
And step "design.gate.answer" changes nothing
|
|
210
222
|
And step "seam.naive" writes "src/calc.ts" with:
|
|
@@ -449,6 +461,10 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
449
461
|
# Requirements
|
|
450
462
|
|
|
451
463
|
- multiply, designed
|
|
464
|
+
|
|
465
|
+
## Acceptance Criteria
|
|
466
|
+
|
|
467
|
+
- AC-1: the change behaves as described.
|
|
452
468
|
"""
|
|
453
469
|
And step "implement.building" writes "src/calc.ts" with:
|
|
454
470
|
"""
|
|
@@ -1404,6 +1420,10 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1404
1420
|
### Subtract
|
|
1405
1421
|
|
|
1406
1422
|
- `subtract(a, b)` returns `a - b`.
|
|
1423
|
+
|
|
1424
|
+
## Acceptance Criteria
|
|
1425
|
+
|
|
1426
|
+
- AC-1: the change behaves as described.
|
|
1407
1427
|
"""
|
|
1408
1428
|
And step "design.gate.answer" changes nothing
|
|
1409
1429
|
And step "seam.naive" writes "src/calc.ts" with:
|
|
@@ -1582,6 +1602,10 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1582
1602
|
### Subtract
|
|
1583
1603
|
|
|
1584
1604
|
- `subtract(a, b)` returns `a - b`.
|
|
1605
|
+
|
|
1606
|
+
## Acceptance Criteria
|
|
1607
|
+
|
|
1608
|
+
- AC-1: the change behaves as described.
|
|
1585
1609
|
"""
|
|
1586
1610
|
And step "design.gate.answer" changes nothing
|
|
1587
1611
|
And judge step "seam.judge" answers:
|
|
@@ -49,6 +49,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
49
49
|
## Risk Tier
|
|
50
50
|
|
|
51
51
|
low — trivial copy change
|
|
52
|
+
|
|
53
|
+
## Acceptance Criteria
|
|
54
|
+
|
|
55
|
+
- AC-1: the change behaves as described.
|
|
52
56
|
"""
|
|
53
57
|
And step "design.gate.answer" changes nothing
|
|
54
58
|
When the process is driven
|
|
@@ -64,6 +68,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
64
68
|
## Risk Tier
|
|
65
69
|
|
|
66
70
|
high — touches payments
|
|
71
|
+
|
|
72
|
+
## Acceptance Criteria
|
|
73
|
+
|
|
74
|
+
- AC-1: the change behaves as described.
|
|
67
75
|
"""
|
|
68
76
|
And step "design.gate.answer" changes nothing
|
|
69
77
|
When the process is driven
|
|
@@ -80,6 +88,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
80
88
|
## Concerns
|
|
81
89
|
|
|
82
90
|
### Subtract
|
|
91
|
+
|
|
92
|
+
## Acceptance Criteria
|
|
93
|
+
|
|
94
|
+
- AC-1: the change behaves as described.
|
|
83
95
|
"""
|
|
84
96
|
And step "design.gate.answer" changes nothing
|
|
85
97
|
When the process is driven
|
|
@@ -105,6 +117,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
105
117
|
|
|
106
118
|
- error rate on /subtract stays flat
|
|
107
119
|
- p95 latency
|
|
120
|
+
|
|
121
|
+
## Acceptance Criteria
|
|
122
|
+
|
|
123
|
+
- AC-1: the change behaves as described.
|
|
108
124
|
"""
|
|
109
125
|
And step "design.gate.answer" changes nothing
|
|
110
126
|
When the process is driven
|
|
@@ -125,6 +141,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
125
141
|
## Risk Tier
|
|
126
142
|
|
|
127
143
|
low — trivial copy change
|
|
144
|
+
|
|
145
|
+
## Acceptance Criteria
|
|
146
|
+
|
|
147
|
+
- AC-1: the change behaves as described.
|
|
128
148
|
"""
|
|
129
149
|
And step "seam.naive" writes "src/calc.ts" with:
|
|
130
150
|
"""
|
|
@@ -157,6 +177,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
157
177
|
## Concerns
|
|
158
178
|
|
|
159
179
|
### Subtract
|
|
180
|
+
|
|
181
|
+
## Acceptance Criteria
|
|
182
|
+
|
|
183
|
+
- AC-1: the change behaves as described.
|
|
160
184
|
"""
|
|
161
185
|
And step "design.gate.answer" changes nothing
|
|
162
186
|
When the process is driven
|
|
@@ -199,6 +223,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
199
223
|
## Production Signals
|
|
200
224
|
|
|
201
225
|
- error rate stays flat
|
|
226
|
+
|
|
227
|
+
## Acceptance Criteria
|
|
228
|
+
|
|
229
|
+
- AC-1: the change behaves as described.
|
|
202
230
|
"""
|
|
203
231
|
And step "design.gate.answer" changes nothing
|
|
204
232
|
When the process is driven
|
|
@@ -220,6 +248,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
220
248
|
## Risk Tier
|
|
221
249
|
|
|
222
250
|
low — trivial
|
|
251
|
+
|
|
252
|
+
## Acceptance Criteria
|
|
253
|
+
|
|
254
|
+
- AC-1: the change behaves as described.
|
|
223
255
|
"""
|
|
224
256
|
And step "design.gate.answer" changes nothing
|
|
225
257
|
And step "seam.naive" writes "src/calc.ts" with:
|
|
@@ -33,6 +33,10 @@ Feature: rejected naive passes walk one fixed path through the steer gate to idl
|
|
|
33
33
|
### Subtract
|
|
34
34
|
|
|
35
35
|
- `subtract(a, b)` returns `a - b`.
|
|
36
|
+
|
|
37
|
+
## Acceptance Criteria
|
|
38
|
+
|
|
39
|
+
- AC-1: the change behaves as described.
|
|
36
40
|
"""
|
|
37
41
|
And step "design.gate.answer" changes nothing
|
|
38
42
|
|
|
@@ -77,6 +81,10 @@ Feature: rejected naive passes walk one fixed path through the steer gate to idl
|
|
|
77
81
|
### Subtract
|
|
78
82
|
|
|
79
83
|
- `subtract(a, b)` returns `a - b`, in the calculator module.
|
|
84
|
+
|
|
85
|
+
## Acceptance Criteria
|
|
86
|
+
|
|
87
|
+
- AC-1: the change behaves as described.
|
|
80
88
|
"""
|
|
81
89
|
And step "seam.naive" from now on changes nothing
|
|
82
90
|
And step "seam.naive" writes "src/calc.ts" with:
|
|
@@ -43,6 +43,10 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
43
43
|
### Subtract
|
|
44
44
|
|
|
45
45
|
- `subtract(a, b)` returns `a - b`.
|
|
46
|
+
|
|
47
|
+
## Acceptance Criteria
|
|
48
|
+
|
|
49
|
+
- AC-1: the change behaves as described.
|
|
46
50
|
"""
|
|
47
51
|
And step "design.gate.answer" changes nothing
|
|
48
52
|
|
|
@@ -184,6 +188,10 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
184
188
|
### Subtract
|
|
185
189
|
|
|
186
190
|
- `subtract(a, b)` returns `a - b`, in a new module.
|
|
191
|
+
|
|
192
|
+
## Acceptance Criteria
|
|
193
|
+
|
|
194
|
+
- AC-1: the change behaves as described.
|
|
187
195
|
"""
|
|
188
196
|
When gtd lands
|
|
189
197
|
Then the process rests at "seam.naive"
|
|
@@ -16,6 +16,7 @@ import { healthy } from "../shared/health.js"
|
|
|
16
16
|
import { coder, FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
|
|
17
17
|
import * as t from "../shared/text.js"
|
|
18
18
|
import { defaults } from "../shared/vars.js"
|
|
19
|
+
import { requireCriteria, requireCriteriaFrozen } from "./criteria.js"
|
|
19
20
|
import { refactorStrays, reviewedRefactor } from "./refactor.js"
|
|
20
21
|
import { flagModeOn } from "../shared/options.js"
|
|
21
22
|
import type { RiskTier } from "./tier.js"
|
|
@@ -81,13 +82,16 @@ export function implement(
|
|
|
81
82
|
...(options.afterRefactor === true ? { allowEmpty: true } : {}),
|
|
82
83
|
},
|
|
83
84
|
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
requireCriteriaFrozen(REQUIREMENTS)
|
|
86
|
+
await healthy(
|
|
87
|
+
() =>
|
|
88
|
+
t.agentWithSkills("fix.suite.fixing", t.implementFixPrompt(), {
|
|
89
|
+
label: "Fixing the check",
|
|
90
|
+
file: FEEDBACK,
|
|
91
|
+
model: coder(),
|
|
92
|
+
system: t.builderSystem(),
|
|
93
|
+
}),
|
|
94
|
+
{ guard: () => requireCriteriaFrozen(REQUIREMENTS), guardRests: false },
|
|
91
95
|
)
|
|
92
96
|
await run("closing", removeScript([REQUIREMENTS]), { label: "Closing out the implementation" })
|
|
93
97
|
})
|
|
@@ -124,6 +128,7 @@ const decide = (): Promise<Decision> =>
|
|
|
124
128
|
})
|
|
125
129
|
// Any edit steers, with a fresh budget and no stale rejection; clean overrules.
|
|
126
130
|
if (changes().length === 0) return { reference: tip.hash }
|
|
131
|
+
requireCriteria(REQUIREMENTS)
|
|
127
132
|
rejection = undefined
|
|
128
133
|
retries = 0
|
|
129
134
|
}
|
|
@@ -33,6 +33,10 @@ Feature: a process keeps the risk tier setting it started with when GTD_FLAG_RIS
|
|
|
33
33
|
## Risk Tier
|
|
34
34
|
|
|
35
35
|
low — a new pure function
|
|
36
|
+
|
|
37
|
+
## Acceptance Criteria
|
|
38
|
+
|
|
39
|
+
- AC-1: the change behaves as described.
|
|
36
40
|
"""
|
|
37
41
|
|
|
38
42
|
Scenario: a process started with the flag off records no tier after the flag turns on
|