@pmelab/gtd 32.0.0 → 33.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 +28 -22
- package/dist/chunks/{reserved-CHTJTzpA.mjs → reserved-BU5Zi-nX.mjs} +74 -13
- package/dist/chunks/{tail-CCsDBbZe.mjs → tail-DuXanjIY.mjs} +6 -3
- package/dist/testkit.mjs +2 -2
- package/dist/workflow-kit.mjs +15 -3
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +158 -34
- 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 +246 -46
- package/package.json +1 -1
- package/src/workflow-kit/quality.test.ts +25 -1
- package/src/workflow-kit/quality.ts +22 -4
- package/src/workflows/feature/acceptance-criteria.feature +8 -0
- package/src/workflows/feature/fast-path.feature +8 -2
- package/src/workflows/feature/feature.test.ts +19 -9
- package/src/workflows/feature/lens-models.feature +60 -0
- package/src/workflows/feature/naive-acceptance.feature +140 -0
- package/src/workflows/feature/one-step-land.feature +8 -2
- package/src/workflows/feature/refactor-absorbs.feature +6 -1
- package/src/workflows/feature/refactor-first.feature +9 -3
- package/src/workflows/feature/refactor-lens-models.feature +67 -0
- package/src/workflows/feature/refactor-review.feature +50 -5
- package/src/workflows/feature/refactor.ts +6 -5
- package/src/workflows/feature/risk-tier.feature +10 -0
- package/src/workflows/feature/seam-rejection.feature +19 -6
- package/src/workflows/feature/seam.feature +70 -16
- package/src/workflows/feature/seam.test.ts +189 -3
- package/src/workflows/feature/seam.ts +195 -30
- package/src/workflows/feature/steps.test.ts +13 -0
- package/src/workflows/index.ts +1 -1
- package/src/workflows/shared/skills.test.ts +7 -0
- package/src/workflows/shared/steps.test.ts +45 -0
- package/src/workflows/shared/steps.ts +4 -2
- package/src/workflows/shared/tail.test.ts +31 -1
- package/src/workflows/shared/tail.ts +6 -3
- package/src/workflows/shared/text.test.ts +40 -0
- package/src/workflows/shared/text.ts +97 -16
|
@@ -124,3 +124,16 @@ describe("the feature workflow's own steps declare skills — a step's rendered
|
|
|
124
124
|
expect(request.options).toMatchObject({ file: ".gtd/TODO.md" })
|
|
125
125
|
})
|
|
126
126
|
})
|
|
127
|
+
|
|
128
|
+
describe("the feature workflow's planning steps run on the planner model", () => {
|
|
129
|
+
it("design triage defaults to smart and follows plannerModel", async () => {
|
|
130
|
+
const plain = await captureStep(() => steps.triage("base"), { scope: "design" })
|
|
131
|
+
const set = await captureStep(() => steps.triage("base"), {
|
|
132
|
+
scope: "design",
|
|
133
|
+
env: { plannerModel: "opus" },
|
|
134
|
+
})
|
|
135
|
+
if (plain.kind !== "agent" || set.kind !== "agent") throw new Error("unreachable")
|
|
136
|
+
expect(plain.options.model).toBe("smart")
|
|
137
|
+
expect(set.options.model).toBe("opus")
|
|
138
|
+
})
|
|
139
|
+
})
|
package/src/workflows/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ export {
|
|
|
19
19
|
tail,
|
|
20
20
|
type TailOptions,
|
|
21
21
|
} from "./shared/tail.js"
|
|
22
|
-
export { lensesOf } from "../workflow-kit/index.js"
|
|
22
|
+
export { lensEntriesOf, lensesOf, lensOf } from "../workflow-kit/index.js"
|
|
23
23
|
export * from "./feature/steps.js"
|
|
24
24
|
export * from "./feature/criteria.js"
|
|
25
25
|
export * from "./feature/planning.js"
|
|
@@ -107,4 +107,11 @@ describe("the bundled workflow's skills export", () => {
|
|
|
107
107
|
.sort(),
|
|
108
108
|
).toEqual(["build.quality.correctness", "build.quality.my-lens"])
|
|
109
109
|
})
|
|
110
|
+
|
|
111
|
+
it("keys a hinted entry by its lens alone, with the lens's own skills", () => {
|
|
112
|
+
const custom = skills({ ...defaults, qualityReviews: "my-lens=smart, correctness = base" })
|
|
113
|
+
expect(custom["build.quality.my-lens"]).toEqual(["my-lens"])
|
|
114
|
+
expect(custom["build.quality.correctness"]).toEqual(["code-review-and-quality"])
|
|
115
|
+
expect(Object.keys(custom).some((k) => k.includes("="))).toBe(false)
|
|
116
|
+
})
|
|
110
117
|
})
|
|
@@ -107,3 +107,48 @@ describe("the bundled workflow's steps declare skills — a bundled step's rende
|
|
|
107
107
|
expect(carried.prompt).toContain("Carry-over: commit `abc1234`")
|
|
108
108
|
})
|
|
109
109
|
})
|
|
110
|
+
|
|
111
|
+
describe("the bundled workflow's model hints", () => {
|
|
112
|
+
const modelOf = async (
|
|
113
|
+
fn: () => Promise<void>,
|
|
114
|
+
context: Parameters<typeof captureStep>[1] = {},
|
|
115
|
+
): Promise<string | undefined> => {
|
|
116
|
+
const request = await captureStep(fn, { scope: "build", ...context })
|
|
117
|
+
if (request.kind !== "agent") throw new Error(`expected an agent step, got ${request.kind}`)
|
|
118
|
+
return request.options.model
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
it.each([
|
|
122
|
+
["correctness", "smart"],
|
|
123
|
+
["owasp-security", "smart"],
|
|
124
|
+
["spec-challenge", "smart"],
|
|
125
|
+
["conventions", "base"],
|
|
126
|
+
["ponytail-review", "base"],
|
|
127
|
+
["test-audit", "base"],
|
|
128
|
+
["my-lens", "smart"],
|
|
129
|
+
])("lens %s defaults to %s", async (lens, model) => {
|
|
130
|
+
expect(await modelOf(() => steps.reviewQuality(lens))).toBe(model)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("a lens's default follows the planner or coder setting it stands for", async () => {
|
|
134
|
+
const env = { plannerModel: "opus", coderModel: "sonnet" }
|
|
135
|
+
expect(await modelOf(() => steps.reviewQuality("correctness"), { env })).toBe("opus")
|
|
136
|
+
expect(await modelOf(() => steps.reviewQuality("conventions"), { env })).toBe("sonnet")
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it("a model passed in wins over the lens's default; a blank one is none", async () => {
|
|
140
|
+
expect(await modelOf(() => steps.reviewQuality("correctness", undefined, "base"))).toBe("base")
|
|
141
|
+
expect(await modelOf(() => steps.reviewQuality("conventions", undefined, "opus"))).toBe("opus")
|
|
142
|
+
expect(await modelOf(() => steps.reviewQuality("conventions", undefined, ""))).toBe("base")
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it("a lens named like an Object member still gets its default", async () => {
|
|
146
|
+
expect(await modelOf(() => steps.reviewQuality("constructor"))).toBe("smart")
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it("the fix loop runs on the coder model, base by default", async () => {
|
|
150
|
+
expect(await modelOf(() => steps.fixCheck())).toBe("base")
|
|
151
|
+
expect(await modelOf(() => steps.fixQuality())).toBe("base")
|
|
152
|
+
expect(await modelOf(() => steps.fixCheck(), { env: { coderModel: "sonnet" } })).toBe("sonnet")
|
|
153
|
+
})
|
|
154
|
+
})
|
|
@@ -66,15 +66,17 @@ export const builtInLenses: Readonly<Record<string, BuiltInLens>> = {
|
|
|
66
66
|
"spec-challenge": { skills: [], brief: t.specChallengeBrief },
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
const coderLenses: ReadonlySet<string> = new Set(["conventions", "ponytail-review", "test-audit"])
|
|
70
|
+
|
|
69
71
|
/** One quality review through `lens`; each lens is its own scope, so its own conversation and skills. */
|
|
70
|
-
export const reviewQuality = (lens: string, base?: string): Promise<void> =>
|
|
72
|
+
export const reviewQuality = (lens: string, base?: string, model?: string): Promise<void> =>
|
|
71
73
|
t.agentWithSkills(
|
|
72
74
|
`quality.${lens}.reviewing`,
|
|
73
75
|
t.buildQualityReviewingPrompt(lens, builtInLenses[lens]?.brief, base),
|
|
74
76
|
{
|
|
75
77
|
label: "Reviewing (one quality lens)",
|
|
76
78
|
file: QUALITY,
|
|
77
|
-
model: planner(),
|
|
79
|
+
model: model || (coderLenses.has(lens) ? coder() : planner()),
|
|
78
80
|
system: t.reviewerSystem(),
|
|
79
81
|
allowEmpty: true,
|
|
80
82
|
},
|
|
@@ -208,18 +208,23 @@ describe("the hand-off review", () => {
|
|
|
208
208
|
})
|
|
209
209
|
|
|
210
210
|
const prompts: string[] = []
|
|
211
|
+
const models: (string | undefined)[] = []
|
|
211
212
|
|
|
212
213
|
describe("the quality lap's lenses", () => {
|
|
213
214
|
const lap = async (run: () => Promise<unknown>, vars: Record<string, string> = {}) => {
|
|
214
215
|
const log: string[] = []
|
|
215
216
|
prompts.length = 0
|
|
217
|
+
models.length = 0
|
|
216
218
|
installContext(
|
|
217
219
|
fixtureContext(
|
|
218
220
|
{ vars },
|
|
219
221
|
{
|
|
220
222
|
step: (request: StepRequest) => {
|
|
221
223
|
if (request.kind !== "restart") log.push(request.name)
|
|
222
|
-
if (request.kind === "agent")
|
|
224
|
+
if (request.kind === "agent") {
|
|
225
|
+
prompts.push(request.prompt)
|
|
226
|
+
models.push(request.options.model)
|
|
227
|
+
}
|
|
223
228
|
return Promise.resolve()
|
|
224
229
|
},
|
|
225
230
|
pushScope: () => undefined,
|
|
@@ -279,6 +284,31 @@ describe("the quality lap's lenses", () => {
|
|
|
279
284
|
expect(prompts[0]).not.toContain("from `R`")
|
|
280
285
|
})
|
|
281
286
|
|
|
287
|
+
it("each lens runs on its qualityReviews hint, else its default, under its plain name", async () => {
|
|
288
|
+
const log = await lap(() => qualityLap(), {
|
|
289
|
+
qualityReviews: "correctness=base, conventions, my-lens",
|
|
290
|
+
})
|
|
291
|
+
expect(log).toEqual([
|
|
292
|
+
"quality.correctness.reviewing",
|
|
293
|
+
"quality.conventions.reviewing",
|
|
294
|
+
"quality.my-lens.reviewing",
|
|
295
|
+
])
|
|
296
|
+
expect(models).toEqual(["base", "base", "smart"])
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it("lenses passed in carry their own hints, never qualityReviews'", async () => {
|
|
300
|
+
const log = await lap(
|
|
301
|
+
() =>
|
|
302
|
+
tail(() => Promise.resolve(), {
|
|
303
|
+
green: true,
|
|
304
|
+
lenses: ["correctness", "conventions=opus"],
|
|
305
|
+
}),
|
|
306
|
+
{ qualityReviews: "correctness=base" },
|
|
307
|
+
)
|
|
308
|
+
expect(log).toEqual(["quality.correctness.reviewing", "quality.conventions.reviewing"])
|
|
309
|
+
expect(models).toEqual(["smart", "opus"])
|
|
310
|
+
})
|
|
311
|
+
|
|
282
312
|
it("tail passes lenses to its lap and a guard after each lens turn", async () => {
|
|
283
313
|
let guards = 0
|
|
284
314
|
const log = await lap(() =>
|
|
@@ -35,8 +35,11 @@ export const qualityLap = (
|
|
|
35
35
|
base?: string,
|
|
36
36
|
): Promise<"clean" | "findings"> =>
|
|
37
37
|
kit.qualityLap(
|
|
38
|
-
lenses ?? kit.
|
|
39
|
-
guarded((
|
|
38
|
+
lenses ?? kit.lensEntriesOf(vars.qualityReviews ?? ""),
|
|
39
|
+
guarded((entry: string) => {
|
|
40
|
+
const { lens, model } = kit.lensOf(entry)
|
|
41
|
+
return reviewQuality(lens, base, model)
|
|
42
|
+
}, guard),
|
|
40
43
|
QUALITY,
|
|
41
44
|
)
|
|
42
45
|
|
|
@@ -77,7 +80,7 @@ export interface TailOptions {
|
|
|
77
80
|
readonly green?: boolean | undefined
|
|
78
81
|
/** Runs after every code-changing turn: fix-check, quality fix, risk fix, nit fix, escalation describe. */
|
|
79
82
|
readonly guard?: Guard | undefined
|
|
80
|
-
/** Quality-lap
|
|
83
|
+
/** Quality-lap lens entries, `<lens>` or `<lens>=<model>`; default `lensEntriesOf(vars.qualityReviews)`. */
|
|
81
84
|
readonly lenses?: readonly string[] | undefined
|
|
82
85
|
/** `false` leaves the escalation's human rests unguarded; default `true`. */
|
|
83
86
|
readonly guardRests?: boolean | undefined
|
|
@@ -965,6 +965,22 @@ describe("the seam phase's texts", () => {
|
|
|
965
965
|
expect(prompt).toContain("solved the task the wrong way")
|
|
966
966
|
})
|
|
967
967
|
|
|
968
|
+
it("asks for a breadth-first pass with ID-tagged behaviour tests, green before it ends", () => {
|
|
969
|
+
const prompt = renderText(() => naivePassPrompt())
|
|
970
|
+
expect(prompt).toContain("breadth-first")
|
|
971
|
+
expect(prompt).toContain("`[AC-3] …`")
|
|
972
|
+
expect(prompt).toContain("never a file this pass adds")
|
|
973
|
+
expect(prompt).toContain("green")
|
|
974
|
+
})
|
|
975
|
+
|
|
976
|
+
it("has a red retry pick up the kept attempt instead of starting over", () => {
|
|
977
|
+
const prompt = renderText(() => naivePassPrompt({ red: "abc123" }))
|
|
978
|
+
expect(prompt).toContain("left the suite red")
|
|
979
|
+
expect(prompt).toContain("git diff --binary abc123^ abc123 | git apply")
|
|
980
|
+
expect(prompt).toContain(".gtd/FEEDBACK.md")
|
|
981
|
+
expect(prompt).not.toContain("rejected")
|
|
982
|
+
})
|
|
983
|
+
|
|
968
984
|
it("cites the reference as reading material only and asks for a new release flag", () => {
|
|
969
985
|
const prompt = renderText(() => implementBuildingPrompt("abc123"))
|
|
970
986
|
expect(prompt).toContain("git show abc123")
|
|
@@ -1019,6 +1035,30 @@ describe("the seam phase's texts", () => {
|
|
|
1019
1035
|
expect(message).toContain("src/flows/helpers.ts")
|
|
1020
1036
|
})
|
|
1021
1037
|
|
|
1038
|
+
it("hands a faulty pass back with the fault and the kept attempt to pick up", () => {
|
|
1039
|
+
const fault = {
|
|
1040
|
+
fault: "acceptance-tests: no test of the naive pass names AC-2",
|
|
1041
|
+
hash: "abc123",
|
|
1042
|
+
}
|
|
1043
|
+
const prompt = renderText(() => naivePassPrompt(fault))
|
|
1044
|
+
expect(prompt).toContain("names AC-2")
|
|
1045
|
+
expect(prompt).toContain("git diff --binary abc123^ abc123 | git apply")
|
|
1046
|
+
const message = renderText(() => seamSteerMessage(fault))
|
|
1047
|
+
expect(message).toContain("names AC-2")
|
|
1048
|
+
expect(message).toContain("`seam.check` runs its suite")
|
|
1049
|
+
expect(message).toContain("Overrule")
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
it("names the red suite, its report and what overruling it means", () => {
|
|
1053
|
+
const message = renderText(() => seamSteerMessage({ red: "abc123" }))
|
|
1054
|
+
expect(message).toContain("The last\nattempt left the suite red")
|
|
1055
|
+
expect(message).toContain(".gtd/FEEDBACK.md")
|
|
1056
|
+
expect(message).toContain("edit `.gtd/REQUIREMENTS.md`")
|
|
1057
|
+
expect(message).toContain("Overrule")
|
|
1058
|
+
expect(message).toContain("seam judge")
|
|
1059
|
+
expect(message).not.toContain("outside the expected set")
|
|
1060
|
+
})
|
|
1061
|
+
|
|
1022
1062
|
it("names every unreadable path for a by-hand landing", () => {
|
|
1023
1063
|
const message = renderText(() =>
|
|
1024
1064
|
seamByHandMessage(["vendor/a.bin", "src/flows/helpers.ts"], ["vendor/a.bin"], "abc123"),
|
|
@@ -1134,31 +1134,72 @@ export interface SeamRejection {
|
|
|
1134
1134
|
readonly p: number
|
|
1135
1135
|
}
|
|
1136
1136
|
|
|
1137
|
+
/** A naive pass whose suite `seam.check` found red; `red` is its kept tip. */
|
|
1138
|
+
export interface NaiveRed {
|
|
1139
|
+
readonly red: string
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/** A naive pass whose tests break the acceptance-test rules; `hash` is its kept tip. */
|
|
1143
|
+
export interface NaiveFault {
|
|
1144
|
+
readonly fault: string
|
|
1145
|
+
readonly hash: string
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
export type NaiveRetry = SeamRejection | NaiveRed | NaiveFault
|
|
1149
|
+
|
|
1150
|
+
const isRed = (retry: NaiveRetry): retry is NaiveRed => "red" in retry
|
|
1151
|
+
const isFault = (retry: NaiveRetry): retry is NaiveFault => "fault" in retry
|
|
1152
|
+
|
|
1153
|
+
const pickUp = (hash: string): string =>
|
|
1154
|
+
`Pick it up — \`git diff --binary ${hash}^ ${hash} | git apply\``
|
|
1155
|
+
|
|
1137
1156
|
const criteriaBinding = (refused: boolean): string =>
|
|
1138
1157
|
`- Its \`## Acceptance Criteria\` (\`AC-<n>\`) bind this turn: re-read them from
|
|
1139
1158
|
the file, never from memory, and stop only when each one holds. They are
|
|
1140
1159
|
settled — ${refused ? "a turn that changes them is refused" : "leave them as they are"}`
|
|
1141
1160
|
|
|
1142
|
-
|
|
1161
|
+
const naiveRetry = (retry: NaiveRetry): string =>
|
|
1162
|
+
isFault(retry)
|
|
1163
|
+
? `
|
|
1164
|
+
Your previous attempt's (\`${retry.hash}\`) tests fall short:
|
|
1165
|
+
${retry.fault}
|
|
1166
|
+
${pickUp(retry.hash)} — and fix its tests; do not start over.
|
|
1167
|
+
`
|
|
1168
|
+
: isRed(retry)
|
|
1169
|
+
? `
|
|
1170
|
+
Your previous attempt (\`${retry.red}\`) left the suite red; \`.gtd/FEEDBACK.md\`
|
|
1171
|
+
holds the output. ${pickUp(retry.red)}
|
|
1172
|
+
— and fix it until the suite is green; do not start over.
|
|
1173
|
+
`
|
|
1174
|
+
: `
|
|
1175
|
+
Your previous attempt was rejected: the judge answered \`reject\` (p=${retry.p}),
|
|
1176
|
+
meaning the naive pass solved the task the wrong way — discard it and
|
|
1177
|
+
re-implement. It changed these files outside the expected set:
|
|
1178
|
+
${listed(retry.strays)}
|
|
1179
|
+
Start over and choose a different approach; do not rebuild the same diff.
|
|
1180
|
+
`
|
|
1181
|
+
|
|
1182
|
+
export const naivePassPrompt = (retry?: NaiveRetry): string =>
|
|
1143
1183
|
`${stateFileRules}
|
|
1144
1184
|
- Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
|
|
1145
1185
|
${criteriaBinding(false)}
|
|
1146
1186
|
- Implement them the quickest way that works — the naive version. Do not
|
|
1147
1187
|
restructure, abstract or refactor around the change; a later step judges
|
|
1148
1188
|
the shape of your diff and a reviewer makes it fit
|
|
1149
|
-
-
|
|
1189
|
+
- Work breadth-first: rough out every part of the change before finishing any
|
|
1190
|
+
one, so a run cut off midway still shows the whole shape
|
|
1191
|
+
- Write behaviour-level acceptance tests, at least one per criterion, each
|
|
1192
|
+
naming its ID in the test or scenario name (\`[AC-3] …\`). Test through code
|
|
1193
|
+
that existed before this pass — a public entry point, the CLI, an existing
|
|
1194
|
+
module's exports — never a file this pass adds: that is naive structure a
|
|
1195
|
+
later reset and refactor replaces. A pass that leaves a criterion's ID out
|
|
1196
|
+
of its tests, or whose test imports a file it added, is sent back
|
|
1197
|
+
- Run \`${env.testCommand}\` and finish only once it is green with every
|
|
1198
|
+
criterion's test passing: the next step runs it on your diff and sends a red
|
|
1199
|
+
pass back
|
|
1200
|
+
- Leave \`.gtd/\` untouched
|
|
1150
1201
|
- Leave everything uncommitted and finish your turn — do not commit
|
|
1151
|
-
${
|
|
1152
|
-
rejection === undefined
|
|
1153
|
-
? ""
|
|
1154
|
-
: `
|
|
1155
|
-
Your previous attempt was rejected: the judge answered \`reject\` (p=${rejection.p}),
|
|
1156
|
-
meaning the naive pass solved the task the wrong way — discard it and
|
|
1157
|
-
re-implement. It changed these files outside the expected set:
|
|
1158
|
-
${listed(rejection.strays)}
|
|
1159
|
-
Start over and choose a different approach; do not rebuild the same diff.
|
|
1160
|
-
`
|
|
1161
|
-
}`
|
|
1202
|
+
${retry === undefined ? "" : naiveRetry(retry)}`
|
|
1162
1203
|
|
|
1163
1204
|
export const implementBuildingPrompt = (
|
|
1164
1205
|
reference: string | undefined,
|
|
@@ -1216,9 +1257,49 @@ ${fixFeedbackPrompt}
|
|
|
1216
1257
|
- Leave everything uncommitted and finish your turn — do not commit
|
|
1217
1258
|
`
|
|
1218
1259
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1260
|
+
const steerChoices = (overrule: string): string => `Two ways forward (then run \`gtd land\`):
|
|
1261
|
+
- **Steer** — edit \`.gtd/REQUIREMENTS.md\` to redirect the change; the naive
|
|
1262
|
+
pass runs again with a fresh retry budget
|
|
1263
|
+
- **Overrule** — ${overrule}
|
|
1264
|
+
|
|
1265
|
+
An edit together with an approval counts as steer. Only
|
|
1266
|
+
\`.gtd/REQUIREMENTS.md\` is meant to change here: any other edit lands on the
|
|
1267
|
+
branch unjudged.
|
|
1268
|
+
`
|
|
1269
|
+
|
|
1270
|
+
export const seamSteerMessage = (retry: NaiveRetry): string =>
|
|
1271
|
+
isFault(retry)
|
|
1272
|
+
? `The naive pass failed three times and the retries are spent. The last
|
|
1273
|
+
attempt's tests (\`git show ${retry.hash}\`) fall short:
|
|
1274
|
+
${retry.fault}
|
|
1275
|
+
|
|
1276
|
+
${steerChoices(`land a clean tree to go on with the last attempt as it is:
|
|
1277
|
+
\`seam.check\` runs its suite and the seam judge rules on its shape. Use it
|
|
1278
|
+
when the rule cannot fit this change (tests the globs do not see, or no
|
|
1279
|
+
entry point that existed before). Risk: a criterion may have no test that
|
|
1280
|
+
survives a later refactor — the choice is recorded in history.`)}`
|
|
1281
|
+
: isRed(retry)
|
|
1282
|
+
? `The naive pass failed three times and the retries are spent. The last
|
|
1283
|
+
attempt left the suite red: \`.gtd/FEEDBACK.md\` holds the last run's output; \`git show ${retry.red}\` shows
|
|
1284
|
+
the last attempt.
|
|
1285
|
+
|
|
1286
|
+
Two ways forward (then run \`gtd land\`):
|
|
1287
|
+
- **Steer** — edit \`.gtd/REQUIREMENTS.md\` to redirect the change; the naive
|
|
1288
|
+
pass runs again with a fresh retry budget
|
|
1289
|
+
- **Overrule** — land a clean tree to go on with the last attempt as it is:
|
|
1290
|
+
the seam judge rules on its shape, and the implementation's health loop
|
|
1291
|
+
must turn the suite green. Risk: the criteria's tests never passed on the
|
|
1292
|
+
naive pass — the choice is recorded in history.
|
|
1293
|
+
|
|
1294
|
+
An edit together with an approval counts as steer. Only
|
|
1295
|
+
\`.gtd/REQUIREMENTS.md\` is meant to change here: any other edit lands on the
|
|
1296
|
+
branch unjudged.
|
|
1297
|
+
`
|
|
1298
|
+
: seamRejectedMessage(retry)
|
|
1299
|
+
|
|
1300
|
+
const seamRejectedMessage = (rejection: SeamRejection): string =>
|
|
1301
|
+
`The naive pass failed three times and the retries are spent. The judge
|
|
1302
|
+
rejected the last attempt: it changed these files outside the expected set:
|
|
1222
1303
|
${listed(rejection.strays)}
|
|
1223
1304
|
|
|
1224
1305
|
Two ways forward (then run \`gtd land\`):
|