@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +66 -53
- package/dist/chunks/{reserved-2fV0mj14.mjs → reserved-BcJgt5_R.mjs} +79 -12
- package/dist/chunks/{tail-BynhCfuv.mjs → tail-CM_fa93v.mjs} +1 -1
- package/dist/testkit.mjs +2 -2
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +174 -40
- 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 +249 -48
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +2 -2
- package/src/testkit/steps.ts +4 -0
- package/src/workflows/feature/acceptance-criteria.feature +8 -0
- package/src/workflows/feature/fast-path.feature +11 -35
- package/src/workflows/feature/feature.test.ts +9 -100
- package/src/workflows/feature/feature.ts +29 -16
- package/src/workflows/feature/flag-mode.feature +2 -91
- package/src/workflows/feature/naive-acceptance.feature +140 -0
- package/src/workflows/feature/one-step-land.feature +11 -34
- package/src/workflows/feature/quality-unresolved.feature +96 -0
- package/src/workflows/feature/refactor-absorbs.feature +17 -25
- package/src/workflows/feature/refactor-first.feature +11 -35
- package/src/workflows/feature/refactor-lens-models.feature +6 -1
- package/src/workflows/feature/refactor-review.feature +67 -360
- package/src/workflows/feature/refactor.ts +4 -3
- package/src/workflows/feature/risk-tier.feature +10 -0
- package/src/workflows/feature/seam-rejection.feature +20 -37
- package/src/workflows/feature/seam.feature +66 -113
- package/src/workflows/feature/seam.test.ts +173 -3
- package/src/workflows/feature/seam.ts +196 -31
- package/src/workflows/review/review-door.feature +108 -0
- package/src/workflows/shared/tail.ts +2 -2
- package/src/workflows/shared/text.test.ts +40 -0
- 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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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"
|
|
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 "
|
|
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 →
|
|
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
|
|
8
|
-
|
|
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.
|
|
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 →
|
|
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.
|
|
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
|
+
"""
|