@pmelab/gtd 58.2.0 → 59.0.1
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 +120 -395
- package/dist/chunks/{Schema-B3o6eRNU.mjs → testkit-07Z_ENch.mjs} +13503 -3460
- package/dist/chunks/{wire-C12dahrH.mjs → wire-BTUhRGXj.mjs} +1 -1
- package/dist/flows.mjs +1 -1
- package/dist/gtd.bundle.mjs +484 -497
- package/dist/testkit/gherkin.mjs +145 -65
- package/dist/testkit.mjs +1 -1
- package/dist/wire.mjs +1 -1
- package/package.json +3 -42
- package/skills/authoring/SKILL.md +34 -27
- package/src/cli/Cli.test.ts +148 -71
- package/src/cli/Cli.ts +384 -463
- package/src/cli/Help.test.ts +47 -0
- package/src/cli/Help.ts +70 -0
- package/src/cli/Program.test.ts +127 -33
- package/src/cli/Program.ts +70 -25
- package/src/edge/Edge.ts +4 -4
- package/src/emit/Emit.test.ts +1 -1
- package/src/emit/Emit.ts +1 -1
- package/src/flags/index.test.ts +0 -13
- package/src/flags/index.ts +1 -3
- package/src/flows/factory.ts +3 -1
- package/src/gherkin/index.ts +135 -71
- package/src/gherkin/messages.test.ts +90 -0
- package/src/gherkin/messages.ts +84 -0
- package/src/install/Install.test.ts +14 -11
- package/src/install/Install.ts +38 -15
- package/src/install/index.ts +1 -2
- package/src/replay/Replay.test.ts +1 -1
- package/src/replay/index.ts +1 -1
- package/src/step/planStart.ts +6 -1
- package/src/testkit/TestRepo.test.ts +47 -2
- package/src/testkit/TestRepo.ts +51 -6
- package/src/twins/EmittedScriptRecognizer.ts +5 -8
- package/src/twins/FlowScripts.ts +1 -1
- package/src/wire/Land.ts +1 -1
- package/src/workflow/index.ts +1 -0
- package/src/workflow/load.test.ts +138 -113
- package/src/workflow/load.ts +7 -7
- package/src/workflow/skills.test.ts +5 -5
- package/src/workflow/skills.ts +1 -1
- package/src/workflow/vars.test.ts +30 -3
- package/src/workflow/vars.ts +34 -2
- package/src/workflow-kit/index.ts +1 -1
- package/dist/chunks/reserved-lMYCaapY.mjs +0 -1990
- package/dist/chunks/review-Bfaror38.mjs +0 -427
- package/dist/chunks/tail-Bd7L940p.mjs +0 -355
- package/dist/chunks/testkit-JDtpNKyi.mjs +0 -10410
- package/dist/workflows/cleanup.mjs +0 -36
- package/dist/workflows/feature.mjs +0 -1018
- package/dist/workflows/fix.mjs +0 -41
- package/dist/workflows/review.mjs +0 -45
- package/dist/workflows/rollout.mjs +0 -48
- package/dist/workflows.mjs +0 -40651
- package/src/install/ConfigScaffold.ts +0 -22
- package/src/workflows/cleanup/cleanup-path.feature +0 -94
- package/src/workflows/cleanup/cleanup.feature +0 -315
- package/src/workflows/cleanup/cleanup.test.ts +0 -276
- package/src/workflows/cleanup/cleanup.ts +0 -52
- package/src/workflows/cleanup/index.ts +0 -6
- package/src/workflows/feature/acceptance-criteria-tier.feature +0 -25
- package/src/workflows/feature/acceptance-criteria.feature +0 -389
- package/src/workflows/feature/criteria-coverage.feature +0 -102
- package/src/workflows/feature/criteria.test.ts +0 -147
- package/src/workflows/feature/criteria.ts +0 -267
- package/src/workflows/feature/fast-path.feature +0 -93
- package/src/workflows/feature/feature.test.ts +0 -338
- package/src/workflows/feature/feature.ts +0 -156
- package/src/workflows/feature/green.ts +0 -19
- package/src/workflows/feature/index.ts +0 -18
- package/src/workflows/feature/lens-models.feature +0 -69
- package/src/workflows/feature/lens-review.feature +0 -566
- package/src/workflows/feature/naive-acceptance.feature +0 -147
- package/src/workflows/feature/naive-test.feature +0 -263
- package/src/workflows/feature/one-step-land.feature +0 -93
- package/src/workflows/feature/planning.ts +0 -41
- package/src/workflows/feature/refactor-absorbs.feature +0 -131
- package/src/workflows/feature/refactor-first.feature +0 -158
- package/src/workflows/feature/refactor-lens-models.feature +0 -79
- package/src/workflows/feature/refactor-rebuild.feature +0 -273
- package/src/workflows/feature/refactor-review.feature +0 -1528
- package/src/workflows/feature/refactor.test.ts +0 -1220
- package/src/workflows/feature/refactor.ts +0 -368
- package/src/workflows/feature/release-flag.feature +0 -167
- package/src/workflows/feature/release-flag.test.ts +0 -49
- package/src/workflows/feature/release-flag.ts +0 -46
- package/src/workflows/feature/review-gate.feature +0 -241
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -142
- package/src/workflows/feature/risk-tier.feature +0 -351
- package/src/workflows/feature/seam-rejection.feature +0 -146
- package/src/workflows/feature/seam.feature +0 -297
- package/src/workflows/feature/seam.test.ts +0 -527
- package/src/workflows/feature/seam.ts +0 -426
- package/src/workflows/feature/standards.feature +0 -354
- package/src/workflows/feature/steps.test.ts +0 -65
- package/src/workflows/feature/steps.ts +0 -45
- package/src/workflows/feature/tier-pin.feature +0 -66
- package/src/workflows/feature/tier.test.ts +0 -166
- package/src/workflows/feature/tier.ts +0 -56
- package/src/workflows/fix/fix-lens-origin.feature +0 -114
- package/src/workflows/fix/fix-quality.feature +0 -212
- package/src/workflows/fix/fix.feature +0 -79
- package/src/workflows/fix/fix.test.ts +0 -74
- package/src/workflows/fix/fix.ts +0 -52
- package/src/workflows/fix/index.ts +0 -6
- package/src/workflows/index.ts +0 -47
- package/src/workflows/review/index.ts +0 -6
- package/src/workflows/review/review-door.feature +0 -199
- package/src/workflows/review/review.test.ts +0 -27
- package/src/workflows/review/review.ts +0 -60
- package/src/workflows/rollout/index.ts +0 -6
- package/src/workflows/rollout/rollout.feature +0 -510
- package/src/workflows/rollout/rollout.test.ts +0 -263
- package/src/workflows/rollout/rollout.ts +0 -82
- package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +0 -98
- package/src/workflows/shared/access.test.ts +0 -62
- package/src/workflows/shared/access.ts +0 -24
- package/src/workflows/shared/config.fixture.ts +0 -12
- package/src/workflows/shared/fences.ts +0 -24
- package/src/workflows/shared/health.test.ts +0 -148
- package/src/workflows/shared/health.ts +0 -191
- package/src/workflows/shared/learn.test.ts +0 -140
- package/src/workflows/shared/learn.ts +0 -201
- package/src/workflows/shared/lenses.test.ts +0 -39
- package/src/workflows/shared/lenses.ts +0 -294
- package/src/workflows/shared/options.test.ts +0 -157
- package/src/workflows/shared/options.ts +0 -92
- package/src/workflows/shared/progress.feature +0 -124
- package/src/workflows/shared/prose.ts +0 -263
- package/src/workflows/shared/recheck.feature +0 -60
- package/src/workflows/shared/registry.test.ts +0 -345
- package/src/workflows/shared/registry.ts +0 -183
- package/src/workflows/shared/reserved.ts +0 -11
- package/src/workflows/shared/review.test.ts +0 -1062
- package/src/workflows/shared/review.ts +0 -446
- package/src/workflows/shared/skills.test.ts +0 -121
- package/src/workflows/shared/skills.ts +0 -49
- package/src/workflows/shared/standards.test.ts +0 -90
- package/src/workflows/shared/standards.ts +0 -156
- package/src/workflows/shared/steps.test.ts +0 -219
- package/src/workflows/shared/steps.ts +0 -201
- package/src/workflows/shared/tail.test.ts +0 -454
- package/src/workflows/shared/tail.ts +0 -264
- package/src/workflows/shared/text.fixture.ts +0 -128
- package/src/workflows/shared/text.test.ts +0 -1430
- package/src/workflows/shared/text.ts +0 -1700
- package/src/workflows/shared/threads.test.ts +0 -129
- package/src/workflows/shared/vars.ts +0 -47
- package/src/workflows/shared/workflows.steps.ts +0 -54
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { runsItsScript, testRepo } from "../../testkit/index.js"
|
|
3
|
-
import { CONFIG_SCAFFOLD } from "../../install/index.js"
|
|
4
|
-
import { reviewDoc } from "../shared/config.fixture.js"
|
|
5
|
-
import type * as featureEntry from "./index.js"
|
|
6
|
-
|
|
7
|
-
describe("an ordinary feature start", () => {
|
|
8
|
-
it("unwinds a captured TODO, checks the baseline and starts design from it", async () => {
|
|
9
|
-
const repo = testRepo({ workflow: CONFIG_SCAFFOLD, env: { GTD_QUALITYREVIEWS: "" } })
|
|
10
|
-
repo.write("TODO.md", "# Add numbers\n\nadd(a, b) returns the sum.\n")
|
|
11
|
-
const rest = await repo.drive({ unwind: () => undefined, "start-gate.check": () => undefined })
|
|
12
|
-
expect(rest).toMatchObject({
|
|
13
|
-
state: "design.triage",
|
|
14
|
-
actor: "agent",
|
|
15
|
-
file: ".gtd/REQUIREMENTS.md",
|
|
16
|
-
})
|
|
17
|
-
expect(repo.subjects().slice(1)).toEqual([
|
|
18
|
-
"gtd(human): idle → unwind",
|
|
19
|
-
"gtd(check): unwind → start-gate.check",
|
|
20
|
-
"gtd(check): start-gate.check → design.triage",
|
|
21
|
-
])
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it("refuses a pending hand-off right after idle, before unwinding", async () => {
|
|
25
|
-
const repo = testRepo({
|
|
26
|
-
workflow: CONFIG_SCAFFOLD,
|
|
27
|
-
files: { ".gtd/REVIEW.md": reviewDoc("note"), ".gtd/REVIEW.orig.md": reviewDoc("orig") },
|
|
28
|
-
})
|
|
29
|
-
repo.write("TODO.md", "# Add numbers\n")
|
|
30
|
-
await expect(repo.land()).rejects.toThrow(".gtd/REVIEW.orig.md is a pending hand-off")
|
|
31
|
-
expect(repo.subjects()).toEqual(["chore: initial commit"])
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const review = (text?: string): string =>
|
|
36
|
-
[
|
|
37
|
-
"# Review: abc1234",
|
|
38
|
-
"",
|
|
39
|
-
"<!-- base: 0000000 -->",
|
|
40
|
-
"",
|
|
41
|
-
"## calc",
|
|
42
|
-
"",
|
|
43
|
-
"- [ ] ./src/calc.ts#1-1",
|
|
44
|
-
text === undefined ? " add" : " add[^why]",
|
|
45
|
-
...(text === undefined ? [] : ["", "[^why]:", ` - H: ${text}`]),
|
|
46
|
-
"",
|
|
47
|
-
].join("\n")
|
|
48
|
-
|
|
49
|
-
const CALC = "export const add = (a: number, b: number) => a + b\n"
|
|
50
|
-
|
|
51
|
-
const ACCEPTANCE = 'it("[AC-1] done", () => {})\n'
|
|
52
|
-
|
|
53
|
-
const naivePlanned = () => {
|
|
54
|
-
const repo = testRepo({
|
|
55
|
-
workflow: CONFIG_SCAFFOLD,
|
|
56
|
-
env: {
|
|
57
|
-
GTD_QUALITYREVIEWS: "",
|
|
58
|
-
GTD_REFACTORQUALITYREVIEWS: "",
|
|
59
|
-
GTD_FLAG_RISK_TIER: "0",
|
|
60
|
-
},
|
|
61
|
-
})
|
|
62
|
-
repo.write("src/calc.ts", CALC)
|
|
63
|
-
repo.commit("feat: add calculator")
|
|
64
|
-
repo.write("TODO.md", "# Add subtract\n")
|
|
65
|
-
return repo
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
describe("afterTail feedback lap", () => {
|
|
69
|
-
const TAIL = [
|
|
70
|
-
`import { start } from "@pmelab/gtd/flows"`,
|
|
71
|
-
`import { afterTail, buildTail, gate } from "@pmelab/gtd/workflows"`,
|
|
72
|
-
`export { feature as default, defaults, envDefaults, summary, base, steering, skills, access } from "@pmelab/gtd/workflows"`,
|
|
73
|
-
`export async function tail() {`,
|
|
74
|
-
` await gate("review-gate", "The baseline is red.")`,
|
|
75
|
-
` return afterTail(await buildTail(start()))`,
|
|
76
|
-
`}`,
|
|
77
|
-
].join("\n")
|
|
78
|
-
|
|
79
|
-
const feedbackLap = async () => {
|
|
80
|
-
const repo = testRepo({
|
|
81
|
-
workflow: TAIL,
|
|
82
|
-
env: {
|
|
83
|
-
GTD_QUALITYREVIEWS: "",
|
|
84
|
-
GTD_REFACTORQUALITYREVIEWS: "",
|
|
85
|
-
GTD_FLAG_RISK_TIER: "0",
|
|
86
|
-
},
|
|
87
|
-
})
|
|
88
|
-
repo.write("src/calc.ts", CALC)
|
|
89
|
-
repo.commit("feat: add calculator")
|
|
90
|
-
await repo.start("tail", "--var", `reviewBase=${repo.hashes()[0]}`)
|
|
91
|
-
await repo.drive({
|
|
92
|
-
"review-gate.check": () => undefined,
|
|
93
|
-
"build.health.check": () => undefined,
|
|
94
|
-
"build.review.reviewing": (_, r) => r.write(".gtd/REVIEW.md", review()),
|
|
95
|
-
})
|
|
96
|
-
repo.write(".gtd/REVIEW.md", review("also export multiply"))
|
|
97
|
-
await repo.land()
|
|
98
|
-
await repo.drive({
|
|
99
|
-
"build.review.collecting": (_, r) => {
|
|
100
|
-
r.write(
|
|
101
|
-
".gtd/REQUIREMENTS.md",
|
|
102
|
-
"# Requirements\n\n- multiply\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
103
|
-
)
|
|
104
|
-
r.write(".gtd/REVIEW.md", `${review("also export multiply")} - A: noted.\n`)
|
|
105
|
-
},
|
|
106
|
-
})
|
|
107
|
-
repo.write(".gtd/REVIEW.md", review())
|
|
108
|
-
await repo.land()
|
|
109
|
-
return repo
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const afterFeedback = {
|
|
113
|
-
"build.review.closing": runsItsScript,
|
|
114
|
-
"re-unwind": runsItsScript,
|
|
115
|
-
"design.triage": (_: unknown, r: ReturnType<typeof testRepo>) =>
|
|
116
|
-
r.write(
|
|
117
|
-
".gtd/REQUIREMENTS.md",
|
|
118
|
-
"# Requirements\n\n- multiply, designed\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
119
|
-
),
|
|
120
|
-
"design.gate.answer": () => undefined,
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
it("rests at design.triage, then implement.building, reaching no seam step", async () => {
|
|
124
|
-
const repo = await feedbackLap()
|
|
125
|
-
expect(
|
|
126
|
-
(await repo.drive({ "build.review.closing": runsItsScript, "re-unwind": runsItsScript }))
|
|
127
|
-
.state,
|
|
128
|
-
).toBe("design.triage")
|
|
129
|
-
const rest = await repo.drive(afterFeedback)
|
|
130
|
-
expect(rest.state).toBe("implement.building")
|
|
131
|
-
expect(rest.content).toContain("already exists")
|
|
132
|
-
expect(rest.content).not.toContain("write the change fresh")
|
|
133
|
-
expect(repo.subjects().some((subject) => subject.includes("seam."))).toBe(false)
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
describe("an in-flight 28.0.0 naive-pass process", () => {
|
|
138
|
-
type Edge = readonly [actor: string, from: string, to: string, trailer?: string]
|
|
139
|
-
const verdict = (answer: string) => `Gtd-Judge: {"id":"shape","answer":"${answer}","p":0.9}`
|
|
140
|
-
const approved = 'Gtd-Approved: {"step":"seam.approve#1","name":"Test","email":"test@test.com"}'
|
|
141
|
-
const building: readonly Edge[] = [
|
|
142
|
-
["agent", "implement.building", "implement.health.check"],
|
|
143
|
-
["check", "implement.health.check", "implement.closing"],
|
|
144
|
-
["check", "implement.closing", "build.review.reviewing"],
|
|
145
|
-
["agent", "build.review.reviewing", "build.review.await-review"],
|
|
146
|
-
]
|
|
147
|
-
const feedback: readonly Edge[] = [
|
|
148
|
-
["human", "build.review.await-review", "build.review.collecting"],
|
|
149
|
-
["agent", "build.review.collecting", "build.review.await-review"],
|
|
150
|
-
["human", "build.review.await-review", "build.review.closing"],
|
|
151
|
-
["check", "build.review.closing", "re-unwind"],
|
|
152
|
-
["check", "re-unwind", "design.triage"],
|
|
153
|
-
["agent", "design.triage", "design.gate.answer"],
|
|
154
|
-
["human", "design.gate.answer", "implement.building"],
|
|
155
|
-
]
|
|
156
|
-
const trails: Record<string, readonly Edge[]> = {
|
|
157
|
-
land: [["judge", "seam.judge", "implement.building", verdict("land")], ...building],
|
|
158
|
-
"land, then a feature feedback lap": [
|
|
159
|
-
["judge", "seam.judge", "implement.building", verdict("land")],
|
|
160
|
-
...building,
|
|
161
|
-
...feedback,
|
|
162
|
-
],
|
|
163
|
-
"rejected twice, steered, then landed": [
|
|
164
|
-
["judge", "seam.judge", "seam.naive", verdict("reject")],
|
|
165
|
-
["agent", "seam.naive", "seam.judge"],
|
|
166
|
-
["judge", "seam.judge", "seam.naive", verdict("reject")],
|
|
167
|
-
["agent", "seam.naive", "seam.judge"],
|
|
168
|
-
["judge", "seam.judge", "seam.steer", verdict("reject")],
|
|
169
|
-
["human", "seam.steer", "seam.naive"],
|
|
170
|
-
["agent", "seam.naive", "seam.judge"],
|
|
171
|
-
["judge", "seam.judge", "implement.building", verdict("land")],
|
|
172
|
-
...building,
|
|
173
|
-
],
|
|
174
|
-
"refactor-first": [
|
|
175
|
-
["judge", "seam.judge", "seam.approve", verdict("refactor-first")],
|
|
176
|
-
["human", "seam.approve", "refactor.deriving", approved],
|
|
177
|
-
["check", "refactor.deriving", "refactor.building"],
|
|
178
|
-
["agent", "refactor.building", "refactor.health.check"],
|
|
179
|
-
["check", "refactor.health.check", "refactor.closing"],
|
|
180
|
-
["check", "refactor.closing", "implement.building"],
|
|
181
|
-
...building,
|
|
182
|
-
],
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const writeEdgeFiles = (
|
|
186
|
-
repo: { write: (path: string, content: string) => void },
|
|
187
|
-
from: string,
|
|
188
|
-
to: string,
|
|
189
|
-
) => {
|
|
190
|
-
if (from === "seam.steer")
|
|
191
|
-
repo.write(
|
|
192
|
-
".gtd/REQUIREMENTS.md",
|
|
193
|
-
"# Requirements\n\n- steered\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
194
|
-
)
|
|
195
|
-
if (from === "build.review.reviewing") repo.write(".gtd/REVIEW.md", review())
|
|
196
|
-
if (to === "build.review.collecting")
|
|
197
|
-
repo.write(".gtd/REVIEW.md", review("also export multiply"))
|
|
198
|
-
if (from === "build.review.collecting") {
|
|
199
|
-
repo.write(".gtd/REVIEW.md", `${review("also export multiply")} - A: noted.\n`)
|
|
200
|
-
repo.write(
|
|
201
|
-
".gtd/REQUIREMENTS.md",
|
|
202
|
-
"# Requirements\n\n- multiply\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
if (to === "build.review.closing") repo.write(".gtd/REVIEW.md", review())
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** The repo at the judge's rest, then `trail` forged edge by edge as `Gtd-Step:` commits. */
|
|
209
|
-
const forged = async (trail: readonly Edge[], upTo: number) => {
|
|
210
|
-
const repo = naivePlanned()
|
|
211
|
-
await repo.drive({
|
|
212
|
-
unwind: () => undefined,
|
|
213
|
-
"start-gate.check": () => undefined,
|
|
214
|
-
"design.triage": (_, r) =>
|
|
215
|
-
r.write(
|
|
216
|
-
".gtd/REQUIREMENTS.md",
|
|
217
|
-
"# Requirements\n\n## Concerns\n\n### S\n\n- `s`\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
218
|
-
),
|
|
219
|
-
"design.gate.answer": () => undefined,
|
|
220
|
-
"seam.naive": (_, r) => {
|
|
221
|
-
r.write("src/calc.ts", `${CALC}// naive\n`)
|
|
222
|
-
r.write("src/calc.test.ts", ACCEPTANCE)
|
|
223
|
-
},
|
|
224
|
-
})
|
|
225
|
-
const kept = /Gtd-Kept: (\w+)/.exec(repo.messages().at(-1)!)![1]!
|
|
226
|
-
const seen: Record<string, number> = {
|
|
227
|
-
"seam.naive": 1,
|
|
228
|
-
"design.triage": 1,
|
|
229
|
-
"design.gate.answer": 1,
|
|
230
|
-
}
|
|
231
|
-
for (const [actor, from, to, trailer] of trail.slice(0, upTo)) {
|
|
232
|
-
seen[from] = (seen[from] ?? 0) + 1
|
|
233
|
-
writeEdgeFiles(repo, from, to)
|
|
234
|
-
repo.commit(
|
|
235
|
-
[
|
|
236
|
-
`gtd(${actor}): ${from} → ${to}`,
|
|
237
|
-
"",
|
|
238
|
-
`Gtd-Step: ${from}#${seen[from]}`,
|
|
239
|
-
...(trailer === undefined ? [] : [trailer]),
|
|
240
|
-
...(actor === "agent" && from === "seam.naive" ? [`Gtd-Kept: ${kept}`] : []),
|
|
241
|
-
"Gtd-Format: 1",
|
|
242
|
-
].join("\n"),
|
|
243
|
-
)
|
|
244
|
-
}
|
|
245
|
-
return repo
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// seam.check now follows every naive pass, so no rest past the first naive pass replays.
|
|
249
|
-
for (const [name, trail] of Object.entries(trails)) {
|
|
250
|
-
it(`${name}: every rest past the naive pass fails replay`, async () => {
|
|
251
|
-
for (let upTo = 1; upTo <= trail.length; upTo++) {
|
|
252
|
-
const rest = trail[upTo - 1]![2]
|
|
253
|
-
const repo = await forged(trail, upTo)
|
|
254
|
-
await expect(repo.next(), rest).rejects.toThrow("the workflow changed under this process")
|
|
255
|
-
}
|
|
256
|
-
})
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
describe("an in-flight process from before the architecture path was removed", () => {
|
|
261
|
-
// No `## Impact`: no version before it wrote one, and replaying its triage must not refuse.
|
|
262
|
-
const REQUIREMENTS =
|
|
263
|
-
"# Requirements\n\n## Concerns\n\n### Subtract\n\n- `subtract`\n\n## Acceptance Criteria\n\n- AC-1: done\n"
|
|
264
|
-
|
|
265
|
-
/** Its history up to the design gate, forged with the old opening commit's `flag_naive_pass=0`. */
|
|
266
|
-
const atDesignGate = async () => {
|
|
267
|
-
const live = naivePlanned()
|
|
268
|
-
await live.drive({ unwind: () => undefined, "start-gate.check": () => undefined })
|
|
269
|
-
const opening = live.messages().find((m) => m.startsWith("gtd(human): idle → unwind"))!
|
|
270
|
-
const repo = naivePlanned()
|
|
271
|
-
repo.commit(
|
|
272
|
-
opening.replace(
|
|
273
|
-
"Gtd-Var: flag_risk_tier=0",
|
|
274
|
-
"Gtd-Var: flag_naive_pass=0\nGtd-Var: flag_risk_tier=0",
|
|
275
|
-
),
|
|
276
|
-
)
|
|
277
|
-
repo.remove("TODO.md")
|
|
278
|
-
repo.commit("gtd(check): unwind → start-gate.check\n\nGtd-Step: unwind#1\nGtd-Format: 1")
|
|
279
|
-
repo.commit(
|
|
280
|
-
"gtd(check): start-gate.check → design.triage\n\nGtd-Step: start-gate.check#1\nGtd-Format: 1",
|
|
281
|
-
)
|
|
282
|
-
repo.write(".gtd/REQUIREMENTS.md", REQUIREMENTS)
|
|
283
|
-
repo.commit(
|
|
284
|
-
"gtd(agent): design.triage → design.gate.answer\n\nGtd-Step: design.triage#1\nGtd-Format: 1",
|
|
285
|
-
)
|
|
286
|
-
return repo
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
it("resting at the design gate, replays onto the naive pass", async () => {
|
|
290
|
-
const repo = await atDesignGate()
|
|
291
|
-
expect((await repo.next()).state).toBe("design.gate.answer")
|
|
292
|
-
await repo.land()
|
|
293
|
-
expect((await repo.next()).state).toBe("seam.naive")
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
it("past the design gate on the architecture path, fails replay", async () => {
|
|
297
|
-
const repo = await atDesignGate()
|
|
298
|
-
repo.commit(
|
|
299
|
-
"gtd(human): design.gate.answer → architecture.author\n\nGtd-Step: design.gate.answer#1\nGtd-Format: 1",
|
|
300
|
-
)
|
|
301
|
-
await expect(repo.next()).rejects.toThrow("the workflow changed under this process")
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
describe("release flag", () => {
|
|
306
|
-
it("with no Release Flag section, lands the naive pass with no seam judge", async () => {
|
|
307
|
-
const repo = testRepo({ workflow: CONFIG_SCAFFOLD })
|
|
308
|
-
repo.write("src/calc.ts", CALC)
|
|
309
|
-
repo.commit("feat: add calculator")
|
|
310
|
-
repo.write("TODO.md", "# Add subtract\n")
|
|
311
|
-
const rest = await repo.drive({
|
|
312
|
-
unwind: () => undefined,
|
|
313
|
-
"start-gate.check": () => undefined,
|
|
314
|
-
"design.triage": (_, r) =>
|
|
315
|
-
r.write(
|
|
316
|
-
".gtd/REQUIREMENTS.md",
|
|
317
|
-
"# Requirements\n\n## Concerns\n\n### S\n\n- `s`\n\n## Impact\n\n- the module\n\n## Acceptance Criteria\n\n- AC-1: done\n",
|
|
318
|
-
),
|
|
319
|
-
"design.gate.answer": () => undefined,
|
|
320
|
-
"seam.naive": (_, r) => {
|
|
321
|
-
r.write("src/calc.ts", `${CALC}// naive\n`)
|
|
322
|
-
r.write("src/calc.test.ts", ACCEPTANCE)
|
|
323
|
-
},
|
|
324
|
-
"seam.check": () => undefined,
|
|
325
|
-
"implement.applying": runsItsScript,
|
|
326
|
-
})
|
|
327
|
-
expect(rest.state).toBe("implement.health.check")
|
|
328
|
-
expect(repo.read("src/calc.ts")).toBe(`${CALC}// naive\n`)
|
|
329
|
-
expect(repo.subjects().some((subject) => subject.includes("seam.judge"))).toBe(false)
|
|
330
|
-
})
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
// pins the public re-exports from the feature entry point
|
|
334
|
-
export type _PublicBuildTailOptions = [
|
|
335
|
-
featureEntry.BuildTailOptions,
|
|
336
|
-
featureEntry.FeedbackTarget,
|
|
337
|
-
featureEntry.ReviewRange,
|
|
338
|
-
]
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
changes,
|
|
3
|
-
changesSince,
|
|
4
|
-
head,
|
|
5
|
-
human,
|
|
6
|
-
read,
|
|
7
|
-
revertScript,
|
|
8
|
-
run,
|
|
9
|
-
start,
|
|
10
|
-
} from "../../flows/index.js"
|
|
11
|
-
import { gate } from "../shared/health.js"
|
|
12
|
-
import { naiveTestOn, shipped } from "../shared/options.js"
|
|
13
|
-
import { FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
|
|
14
|
-
import { refusePendingHandOff } from "../shared/tail.js"
|
|
15
|
-
import * as t from "../shared/text.js"
|
|
16
|
-
import {
|
|
17
|
-
criterionIds,
|
|
18
|
-
requireStableIds,
|
|
19
|
-
type StableIds,
|
|
20
|
-
skipDesign,
|
|
21
|
-
statedCriteria,
|
|
22
|
-
type StatedCriteria,
|
|
23
|
-
} from "./criteria.js"
|
|
24
|
-
import { design } from "./planning.js"
|
|
25
|
-
import { buildTail, isCode, reUnwind, type ReviewOutcome } from "../shared/review.js"
|
|
26
|
-
import { implement, implementing, seamBuild } from "./seam.js"
|
|
27
|
-
import { recordRisk, riskTierOn } from "./tier.js"
|
|
28
|
-
|
|
29
|
-
// The ordinary start. Any change to the tree starts a process: `idle` →
|
|
30
|
-
// `unwind` reverts the sketch (its intent survives in history) → a
|
|
31
|
-
// green-baseline gate → design → a naive pass, tried by a person with
|
|
32
|
-
// `naiveTest` on and judged when the requirements ask for a release flag: it
|
|
33
|
-
// lands as the implementation, or (refactoring first when the judge asks) is
|
|
34
|
-
// built fresh from → the health loop → the triaged quality lap and its finding gate → a
|
|
35
|
-
// reviewer's walkthrough of the whole change for the human, which signs off
|
|
36
|
-
// (the episode ends back at `idle`) or sends a design → implement lap.
|
|
37
|
-
|
|
38
|
-
/** Revert the sketch that started the process out of the working tree; its intent survives in history. */
|
|
39
|
-
export const unwind = (): Promise<void> => {
|
|
40
|
-
const commit = head()
|
|
41
|
-
return run("unwind", revertScript(commit, FEEDBACK, t.unwindFailure(commit)), {
|
|
42
|
-
label: "Unwinding your input",
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export { reUnwind }
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* A naive pass (judged only when a release flag is asked for), then implement and the
|
|
50
|
-
* quality lap, then the review; design must have run since `firstBase`.
|
|
51
|
-
*/
|
|
52
|
-
export const buildFeature = async (firstBase: string): Promise<void> => {
|
|
53
|
-
const tier = riskTierOn() ? recordRisk() : undefined
|
|
54
|
-
// Every ID handed out, gathered before a later edit can drop one: none is ever reused.
|
|
55
|
-
const handedOut = new Set(listedIds())
|
|
56
|
-
const seam = await seamBuild(firstBase)
|
|
57
|
-
const used = gather(handedOut)
|
|
58
|
-
// Read before the implementation closes the file: the quality lap's coverage lens checks them.
|
|
59
|
-
const requirements = read(REQUIREMENTS)
|
|
60
|
-
const closed = await implementing({
|
|
61
|
-
reference: seam.reference,
|
|
62
|
-
tier,
|
|
63
|
-
afterRefactor: seam.refactored,
|
|
64
|
-
naive: seam.land === true ? seam.reference : undefined,
|
|
65
|
-
rebuild: seam.rebuild,
|
|
66
|
-
})
|
|
67
|
-
// A feature with no code beyond the carried-over tests: the refactor carried
|
|
68
|
-
// it, so the lap covers the whole change.
|
|
69
|
-
const carried = seam.rebuild?.acceptance?.tests ?? []
|
|
70
|
-
const base =
|
|
71
|
-
seam.refactored === true &&
|
|
72
|
-
!changesSince(seam.base).some((c) => isCode(c.path) && !carried.includes(c.path))
|
|
73
|
-
? firstBase
|
|
74
|
-
: seam.base
|
|
75
|
-
const outcome = await buildTail(base, { green: true, lensBase: base, requirements })
|
|
76
|
-
await featureFeedback(outcome, base, { closed, used })
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const listedIds = (): readonly string[] => criterionIds(read(REQUIREMENTS) ?? "")
|
|
80
|
-
|
|
81
|
-
const gather = (handedOut: Set<string>): readonly string[] => {
|
|
82
|
-
for (const id of listedIds()) handedOut.add(id)
|
|
83
|
-
return [...handedOut]
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// seam.naive / seam.judge never run on a feedback lap: its design → implement is built unjudged
|
|
87
|
-
// and unrefactored.
|
|
88
|
-
const featureFeedback = async (
|
|
89
|
-
first: ReviewOutcome,
|
|
90
|
-
lensBase: string,
|
|
91
|
-
ids: StableIds,
|
|
92
|
-
): Promise<void> => {
|
|
93
|
-
let outcome = first
|
|
94
|
-
let closed = ids.closed
|
|
95
|
-
const handedOut = new Set([...ids.used, ...ids.closed])
|
|
96
|
-
while (outcome.verdict === "feedback") {
|
|
97
|
-
await reUnwind(outcome)
|
|
98
|
-
const lap = { closed, used: [...handedOut] }
|
|
99
|
-
// Gathered every round, so an ID one round adds and the next drops is never reused.
|
|
100
|
-
await design(outcome.base, () => {
|
|
101
|
-
requireStableIds(REQUIREMENTS, lap)
|
|
102
|
-
gather(handedOut)
|
|
103
|
-
})
|
|
104
|
-
const tier = riskTierOn() ? recordRisk() : undefined
|
|
105
|
-
const requirements = read(REQUIREMENTS)
|
|
106
|
-
closed = await implementing({ tier, amend: true })
|
|
107
|
-
outcome = await buildTail(outcome.base, {
|
|
108
|
-
green: true,
|
|
109
|
-
lensBase,
|
|
110
|
-
requirements,
|
|
111
|
-
lap: "feedback",
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
|
|
117
|
-
export const planAndBuild = async (firstBase: string, stated?: StatedCriteria): Promise<void> => {
|
|
118
|
-
// GTD_FLAG_RISK_TIER: the tier is proposed at triage and set at the design gate, so no skip.
|
|
119
|
-
if (!(await skipDesign(riskTierOn() ? undefined : stated))) await design(firstBase)
|
|
120
|
-
await buildFeature(firstBase)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// seam.naive / seam.judge never run here: a re-plan's seam is built unjudged and unrefactored.
|
|
124
|
-
/** After a tail that skipped planning: stop on sign-off, re-plan on feedback. */
|
|
125
|
-
export const afterTail = async (first: ReviewOutcome): Promise<void> => {
|
|
126
|
-
let outcome = first
|
|
127
|
-
while (outcome.verdict === "feedback") {
|
|
128
|
-
await reUnwind(outcome)
|
|
129
|
-
await design(outcome.base)
|
|
130
|
-
const tier = riskTierOn() ? recordRisk() : undefined
|
|
131
|
-
const requirements = read(REQUIREMENTS)
|
|
132
|
-
await implement({ tier, amend: true })
|
|
133
|
-
outcome = await buildTail(outcome.base, { green: true, requirements })
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** An ordinary start: wait at `idle`, unwind the sketch, check the baseline, then plan and build. */
|
|
138
|
-
export const ordinaryStart = async (): Promise<void> => {
|
|
139
|
-
await human("idle", { message: t.idleMessage(), label: "Idle", file: ".gtd/TODO.md" })
|
|
140
|
-
refusePendingHandOff()
|
|
141
|
-
// Read right after idle, so a bad value refuses the landing that would pin it.
|
|
142
|
-
naiveTestOn()
|
|
143
|
-
const stated = statedCriteria(changes(), head())
|
|
144
|
-
await unwind()
|
|
145
|
-
if (changes(FEEDBACK).some((c) => c.status !== "deleted")) {
|
|
146
|
-
await human("unwind-failed", {
|
|
147
|
-
message: t.unwindFailedMessage(),
|
|
148
|
-
label: "Could not unwind your input",
|
|
149
|
-
file: FEEDBACK,
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
await gate("start-gate", t.startGateBlockedMessage())
|
|
153
|
-
await planAndBuild(start(), stated)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export const feature = shipped(ordinaryStart)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { healthy } from "../shared/health.js"
|
|
2
|
-
import { coder, FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
|
|
3
|
-
import * as t from "../shared/text.js"
|
|
4
|
-
import { requireCriteriaFrozen } from "./criteria.js"
|
|
5
|
-
|
|
6
|
-
export const green = (): Promise<void> =>
|
|
7
|
-
healthy(
|
|
8
|
-
() =>
|
|
9
|
-
t.agentWithSkills("fix.suite.fixing", t.implementFixPrompt(), {
|
|
10
|
-
label: "Fixing the check",
|
|
11
|
-
file: FEEDBACK,
|
|
12
|
-
model: coder(),
|
|
13
|
-
system: t.builderSystem(),
|
|
14
|
-
}),
|
|
15
|
-
{
|
|
16
|
-
guard: () => requireCriteriaFrozen(REQUIREMENTS),
|
|
17
|
-
guardRests: false,
|
|
18
|
-
},
|
|
19
|
-
)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { feature } from "./feature.js"
|
|
2
|
-
|
|
3
|
-
export * from "./steps.js"
|
|
4
|
-
export * from "./criteria.js"
|
|
5
|
-
export * from "./planning.js"
|
|
6
|
-
export * from "./refactor.js"
|
|
7
|
-
export * from "./seam.js"
|
|
8
|
-
export {
|
|
9
|
-
buildTail,
|
|
10
|
-
type BuildTailOptions,
|
|
11
|
-
type FeedbackTarget,
|
|
12
|
-
type ReviewOutcome,
|
|
13
|
-
} from "../shared/review.js"
|
|
14
|
-
export type { ReviewRange } from "../shared/text.js"
|
|
15
|
-
export * from "./feature.js"
|
|
16
|
-
export * from "../shared/reserved.js"
|
|
17
|
-
|
|
18
|
-
export default feature
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
@inmem
|
|
2
|
-
Feature: each quality lens runs on its own model hint
|
|
3
|
-
|
|
4
|
-
A `qualityReviews` entry may carry a model hint, `<lens>=<model>`. A lens
|
|
5
|
-
without one runs on the planner model (`smart`), except `conventions`,
|
|
6
|
-
`ponytail-review` and `test-audit`, which run on the coder model (`base`).
|
|
7
|
-
|
|
8
|
-
Background:
|
|
9
|
-
Given a gtd config with:
|
|
10
|
-
"""
|
|
11
|
-
import { feature } from "@pmelab/gtd/workflows/feature"
|
|
12
|
-
export { summary, steering, skills, access } from "@pmelab/gtd/workflows/feature"
|
|
13
|
-
export default feature({
|
|
14
|
-
qualityReviews: "correctness, conventions, test-audit=opus",
|
|
15
|
-
})
|
|
16
|
-
"""
|
|
17
|
-
And a file "src/calc.ts" with:
|
|
18
|
-
"""
|
|
19
|
-
export const add = (a: number, b: number) => a + b
|
|
20
|
-
"""
|
|
21
|
-
And the working tree is committed as "feat: add calculator"
|
|
22
|
-
And a file "TODO.md" with:
|
|
23
|
-
"""
|
|
24
|
-
# Add subtract
|
|
25
|
-
|
|
26
|
-
subtract(a, b) returns the difference.
|
|
27
|
-
"""
|
|
28
|
-
And step "unwind" changes nothing
|
|
29
|
-
And step "start-gate.check" changes nothing
|
|
30
|
-
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
31
|
-
"""
|
|
32
|
-
# Requirements
|
|
33
|
-
|
|
34
|
-
## Impact
|
|
35
|
-
|
|
36
|
-
- the greeting module
|
|
37
|
-
|
|
38
|
-
## Acceptance Criteria
|
|
39
|
-
|
|
40
|
-
- AC-1: `subtract(a, b)` returns `a - b`.
|
|
41
|
-
"""
|
|
42
|
-
And step "design.gate.answer" changes nothing
|
|
43
|
-
And step "seam.naive" writes "src/calc.test.ts" with:
|
|
44
|
-
"""
|
|
45
|
-
it("[AC-1] subtracts", () => {})
|
|
46
|
-
"""
|
|
47
|
-
And step "seam.naive" writes "src/calc.ts" with:
|
|
48
|
-
"""
|
|
49
|
-
export const add = (a: number, b: number) => a + b
|
|
50
|
-
export const subtract = (a: number, b: number) => a - b
|
|
51
|
-
"""
|
|
52
|
-
And step "seam.check" changes nothing
|
|
53
|
-
And step "implement.applying" runs its script
|
|
54
|
-
And step "implement.health.check" changes nothing
|
|
55
|
-
And step "implement.closing" runs its script
|
|
56
|
-
|
|
57
|
-
Scenario: a hinted lens runs on its hint, every other lens on its default
|
|
58
|
-
When the process is driven
|
|
59
|
-
Then the process rests at "build.quality.correctness.reviewing"
|
|
60
|
-
And the output of gtd "next --json" contains "\"model\":\"smart\""
|
|
61
|
-
Given step "build.quality.correctness.reviewing" changes nothing
|
|
62
|
-
When the process is driven
|
|
63
|
-
Then the process rests at "build.quality.conventions.reviewing"
|
|
64
|
-
And the output of gtd "next --json" contains "\"model\":\"base\""
|
|
65
|
-
Given step "build.quality.conventions.reviewing" changes nothing
|
|
66
|
-
When the process is driven
|
|
67
|
-
Then the process rests at "build.quality.test-audit.reviewing"
|
|
68
|
-
And the output of gtd "next --json" contains "\"model\":\"opus\""
|
|
69
|
-
And the resting step's content contains "test-audit"
|