@pmelab/gtd 56.0.0 → 58.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 -35
- package/dist/chunks/{reserved-B8EC-WE8.mjs → reserved-BzAFXuz_.mjs} +56 -131
- package/dist/chunks/{review-HdrX0FJ7.mjs → review-CVqRS0mc.mjs} +2 -2
- package/dist/chunks/{tail-BSLah2mA.mjs → tail-JQzB21Q3.mjs} +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +99 -170
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +150 -297
- package/package.json +1 -1
- package/src/workflows/feature/acceptance-criteria.feature +32 -0
- package/src/workflows/feature/criteria-coverage.feature +0 -2
- package/src/workflows/feature/criteria.test.ts +1 -41
- package/src/workflows/feature/criteria.ts +12 -18
- package/src/workflows/feature/fast-path.feature +4 -1
- package/src/workflows/feature/feature.test.ts +3 -39
- package/src/workflows/feature/feature.ts +6 -8
- package/src/workflows/feature/lens-models.feature +0 -2
- package/src/workflows/feature/lens-review.feature +0 -2
- package/src/workflows/feature/naive-acceptance.feature +4 -0
- package/src/workflows/feature/naive-test.feature +263 -0
- package/src/workflows/feature/one-step-land.feature +4 -1
- package/src/workflows/feature/planning.ts +2 -0
- package/src/workflows/feature/refactor-absorbs.feature +4 -1
- package/src/workflows/feature/refactor-first.feature +4 -1
- package/src/workflows/feature/refactor-lens-models.feature +4 -0
- package/src/workflows/feature/refactor-rebuild.feature +4 -1
- package/src/workflows/feature/refactor-review.feature +20 -1
- package/src/workflows/feature/release-flag.feature +167 -0
- package/src/workflows/feature/release-flag.test.ts +49 -0
- package/src/workflows/feature/release-flag.ts +46 -0
- package/src/workflows/feature/review-gate.feature +1 -3
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -2
- package/src/workflows/feature/risk-tier.feature +32 -0
- package/src/workflows/feature/seam-rejection.feature +8 -1
- package/src/workflows/feature/seam.feature +8 -1
- package/src/workflows/feature/seam.test.ts +21 -9
- package/src/workflows/feature/seam.ts +60 -14
- package/src/workflows/feature/standards.feature +0 -2
- package/src/workflows/shared/options.test.ts +3 -10
- package/src/workflows/shared/options.ts +5 -6
- package/src/workflows/shared/prose.ts +0 -6
- package/src/workflows/shared/skills.test.ts +0 -4
- package/src/workflows/shared/skills.ts +0 -5
- package/src/workflows/shared/steps.ts +1 -1
- package/src/workflows/shared/text.test.ts +58 -69
- package/src/workflows/shared/text.ts +79 -162
- package/src/workflows/shared/vars.ts +2 -4
- package/src/workflows/feature/flag-mode.feature +0 -88
- package/src/workflows/feature/hands-on-skip.feature +0 -316
- package/src/workflows/feature/prototype-rounds.feature +0 -156
- package/src/workflows/feature/prototype.feature +0 -428
- package/src/workflows/feature/prototype.test.ts +0 -43
- package/src/workflows/feature/prototype.ts +0 -198
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { read, refuse } from "../../flows/index.js"
|
|
2
|
+
import { HEADING, markFenced } from "../shared/fences.js"
|
|
3
|
+
import { REQUIREMENTS } from "../shared/steps.js"
|
|
4
|
+
import type { ReleaseFlag } from "../shared/text.js"
|
|
5
|
+
|
|
6
|
+
const NAME = /^existing:\s*`?([A-Za-z_][\w.-]*)`?$/i
|
|
7
|
+
|
|
8
|
+
const isFlagHeading = (h: RegExpExecArray | null): boolean =>
|
|
9
|
+
h !== null && h[1]!.length === 2 && /^release flag$/i.test(h[2]!.trim())
|
|
10
|
+
|
|
11
|
+
/** The first line under `## Release Flag`, `undefined` without the section. */
|
|
12
|
+
const requestLine = (text: string): string | undefined => {
|
|
13
|
+
const lines = markFenced(text)
|
|
14
|
+
const at = lines.findIndex((l) => !l.fenced && isFlagHeading(HEADING.exec(l.line)))
|
|
15
|
+
if (at === -1) return undefined
|
|
16
|
+
const body = lines.slice(at + 1)
|
|
17
|
+
const end = body.findIndex((l) => !l.fenced && HEADING.test(l.line))
|
|
18
|
+
return (end === -1 ? body : body.slice(0, end)).find((l) => l.line.trim() !== "")?.line.trim()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** What `text` asks for, or why it cannot be read. No section asks for no flag. */
|
|
22
|
+
export const releaseFlagOf = (text: string): ReleaseFlag | { readonly unreadable: string } => {
|
|
23
|
+
const line = requestLine(text)
|
|
24
|
+
if (line === undefined || /^none$/i.test(line)) return { kind: "none" }
|
|
25
|
+
if (/^new$/i.test(line)) return { kind: "new" }
|
|
26
|
+
const name = NAME.exec(line)?.[1]
|
|
27
|
+
return name === undefined ? { unreadable: line } : { kind: "existing", name }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Refuse `file` when its `## Release Flag` section is neither `none`, `new` nor `existing: <NAME>`. */
|
|
31
|
+
export const requireReleaseFlag = (file: string): void => {
|
|
32
|
+
const request = releaseFlagOf(read(file) ?? "")
|
|
33
|
+
if (!("unreadable" in request)) return
|
|
34
|
+
refuse(
|
|
35
|
+
`gtd land: release-flag: ${file}'s "## Release Flag" reads ${JSON.stringify(request.unreadable)} — its first line must be \`new\`, \`existing: <NAME>\` or \`none\`, or leave the section out for no flag`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** What `.gtd/REQUIREMENTS.md` asks for now; a person's unreadable edit is refused. */
|
|
40
|
+
export const requestedFlag = (): ReleaseFlag => {
|
|
41
|
+
requireReleaseFlag(REQUIREMENTS)
|
|
42
|
+
return releaseFlagOf(read(REQUIREMENTS) ?? "") as ReleaseFlag
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Whether the change goes behind a release flag: only then does the seam judge rule. */
|
|
46
|
+
export const flagged = (): boolean => requestedFlag().kind !== "none"
|
|
@@ -4,15 +4,13 @@ Feature: a feature process ends at the human review
|
|
|
4
4
|
After the quality lap the process rests at the review gate,
|
|
5
5
|
`.gtd/REVIEW.md`. A sign-off ends the process. A note asking for a change in
|
|
6
6
|
behaviour starts a feedback lap — design, then implement with no new naive
|
|
7
|
-
pass or seam judge, the
|
|
7
|
+
pass or seam judge, the quality lap — and the review
|
|
8
8
|
again. Its design keeps a built criterion's ID and numbers
|
|
9
9
|
new behaviour above every ID handed out.
|
|
10
10
|
|
|
11
11
|
Background:
|
|
12
12
|
Given the shipped workflows, as gtd install scaffolds them
|
|
13
|
-
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
14
13
|
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
15
|
-
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
16
14
|
And a file "src/calc.ts" with:
|
|
17
15
|
"""
|
|
18
16
|
export const add = (a: number, b: number) => a + b
|
|
@@ -10,9 +10,7 @@ Feature: a reviewer walks the human through the feature's code
|
|
|
10
10
|
|
|
11
11
|
Background:
|
|
12
12
|
Given the shipped workflows, as gtd install scaffolds them
|
|
13
|
-
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
14
13
|
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
15
|
-
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
16
14
|
And a file "src/calc.ts" with:
|
|
17
15
|
"""
|
|
18
16
|
export const add = (a: number, b: number) => a + b
|
|
@@ -54,6 +54,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
54
54
|
|
|
55
55
|
- the greeting module
|
|
56
56
|
|
|
57
|
+
## Release Flag
|
|
58
|
+
|
|
59
|
+
new
|
|
60
|
+
|
|
57
61
|
## Acceptance Criteria
|
|
58
62
|
|
|
59
63
|
- AC-1: the change behaves as described.
|
|
@@ -77,6 +81,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
77
81
|
|
|
78
82
|
- the greeting module
|
|
79
83
|
|
|
84
|
+
## Release Flag
|
|
85
|
+
|
|
86
|
+
new
|
|
87
|
+
|
|
80
88
|
## Acceptance Criteria
|
|
81
89
|
|
|
82
90
|
- AC-1: the change behaves as described.
|
|
@@ -101,6 +109,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
101
109
|
|
|
102
110
|
- the greeting module
|
|
103
111
|
|
|
112
|
+
## Release Flag
|
|
113
|
+
|
|
114
|
+
new
|
|
115
|
+
|
|
104
116
|
## Acceptance Criteria
|
|
105
117
|
|
|
106
118
|
- AC-1: the change behaves as described.
|
|
@@ -134,6 +146,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
134
146
|
|
|
135
147
|
- the greeting module
|
|
136
148
|
|
|
149
|
+
## Release Flag
|
|
150
|
+
|
|
151
|
+
new
|
|
152
|
+
|
|
137
153
|
## Acceptance Criteria
|
|
138
154
|
|
|
139
155
|
- AC-1: the change behaves as described.
|
|
@@ -162,6 +178,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
162
178
|
|
|
163
179
|
- the greeting module
|
|
164
180
|
|
|
181
|
+
## Release Flag
|
|
182
|
+
|
|
183
|
+
new
|
|
184
|
+
|
|
165
185
|
## Acceptance Criteria
|
|
166
186
|
|
|
167
187
|
- AC-1: the change behaves as described.
|
|
@@ -211,6 +231,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
211
231
|
|
|
212
232
|
- the greeting module
|
|
213
233
|
|
|
234
|
+
## Release Flag
|
|
235
|
+
|
|
236
|
+
new
|
|
237
|
+
|
|
214
238
|
## Acceptance Criteria
|
|
215
239
|
|
|
216
240
|
- AC-1: the change behaves as described.
|
|
@@ -261,6 +285,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
261
285
|
|
|
262
286
|
- the greeting module
|
|
263
287
|
|
|
288
|
+
## Release Flag
|
|
289
|
+
|
|
290
|
+
new
|
|
291
|
+
|
|
264
292
|
## Acceptance Criteria
|
|
265
293
|
|
|
266
294
|
- AC-1: the change behaves as described.
|
|
@@ -290,6 +318,10 @@ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_T
|
|
|
290
318
|
|
|
291
319
|
- the greeting module
|
|
292
320
|
|
|
321
|
+
## Release Flag
|
|
322
|
+
|
|
323
|
+
new
|
|
324
|
+
|
|
293
325
|
## Acceptance Criteria
|
|
294
326
|
|
|
295
327
|
- AC-1: the change behaves as described.
|
|
@@ -9,7 +9,6 @@ Feature: rejected naive passes walk one fixed path through the steer gate to the
|
|
|
9
9
|
|
|
10
10
|
Background:
|
|
11
11
|
Given the shipped workflows, as gtd install scaffolds them
|
|
12
|
-
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
13
12
|
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
14
13
|
And an environment variable "GTD_FLAG_RISK_TIER" set to "0"
|
|
15
14
|
And a file "src/calc.ts" with:
|
|
@@ -39,6 +38,10 @@ Feature: rejected naive passes walk one fixed path through the steer gate to the
|
|
|
39
38
|
|
|
40
39
|
- the greeting module
|
|
41
40
|
|
|
41
|
+
## Release Flag
|
|
42
|
+
|
|
43
|
+
new
|
|
44
|
+
|
|
42
45
|
## Acceptance Criteria
|
|
43
46
|
|
|
44
47
|
- AC-1: the change behaves as described.
|
|
@@ -84,6 +87,10 @@ Feature: rejected naive passes walk one fixed path through the steer gate to the
|
|
|
84
87
|
|
|
85
88
|
- the greeting module
|
|
86
89
|
|
|
90
|
+
## Release Flag
|
|
91
|
+
|
|
92
|
+
new
|
|
93
|
+
|
|
87
94
|
## Acceptance Criteria
|
|
88
95
|
|
|
89
96
|
- AC-1: the change behaves as described.
|
|
@@ -7,7 +7,6 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
7
7
|
|
|
8
8
|
Background:
|
|
9
9
|
Given the shipped workflows, as gtd install scaffolds them
|
|
10
|
-
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
11
10
|
And the gtd config ends with:
|
|
12
11
|
"""
|
|
13
12
|
export const config = {
|
|
@@ -49,6 +48,10 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
49
48
|
|
|
50
49
|
- the greeting module
|
|
51
50
|
|
|
51
|
+
## Release Flag
|
|
52
|
+
|
|
53
|
+
new
|
|
54
|
+
|
|
52
55
|
## Acceptance Criteria
|
|
53
56
|
|
|
54
57
|
- AC-1: the change behaves as described.
|
|
@@ -242,6 +245,10 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
242
245
|
|
|
243
246
|
- the greeting module
|
|
244
247
|
|
|
248
|
+
## Release Flag
|
|
249
|
+
|
|
250
|
+
new
|
|
251
|
+
|
|
245
252
|
## Acceptance Criteria
|
|
246
253
|
|
|
247
254
|
- AC-1: the change behaves as described.
|
|
@@ -234,18 +234,24 @@ interface Seamed {
|
|
|
234
234
|
readonly outcome: SeamOutcome | Error
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
const FLAGGED = "# Requirements\n\n## Release Flag\n\nnew\n"
|
|
238
|
+
|
|
237
239
|
/** Runs `seamBuild` against a scripted context: the judge answers `answer`, every review signs off. */
|
|
238
240
|
const driveSeam = async (
|
|
239
241
|
answer: string,
|
|
240
242
|
stray: Change = change("src/a.ts", "modified"),
|
|
241
243
|
vars: Record<string, string> = {},
|
|
242
244
|
reds = 0,
|
|
245
|
+
requirements = FLAGGED,
|
|
243
246
|
): Promise<Seamed> => {
|
|
244
247
|
let red = false
|
|
245
248
|
let redsLeft = reds
|
|
246
249
|
const names: string[] = []
|
|
247
250
|
const stack: string[] = []
|
|
248
|
-
const files = new Map<string, string>([
|
|
251
|
+
const files = new Map<string, string>([
|
|
252
|
+
["src/a.ts", "a"],
|
|
253
|
+
[".gtd/REQUIREMENTS.md", requirements],
|
|
254
|
+
])
|
|
249
255
|
let current = "c0"
|
|
250
256
|
const effects: Record<string, () => void> = {
|
|
251
257
|
note: () => void files.set(NOTE, "Seam: a shared op"),
|
|
@@ -302,8 +308,8 @@ describe("seamBuild", () => {
|
|
|
302
308
|
expect(h.names).toEqual(["seam.naive", "seam.check", "seam.judge"])
|
|
303
309
|
})
|
|
304
310
|
|
|
305
|
-
it("with
|
|
306
|
-
const h = await driveSeam("reject", undefined, {
|
|
311
|
+
it("with no release flag asked for resolves a vetted pass to land with no judge", async () => {
|
|
312
|
+
const h = await driveSeam("reject", undefined, {}, 0, "# Requirements\n")
|
|
307
313
|
expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
|
|
308
314
|
expect(h.names).toEqual(["seam.naive", "seam.check"])
|
|
309
315
|
})
|
|
@@ -393,6 +399,7 @@ describe("seamBuild", () => {
|
|
|
393
399
|
|
|
394
400
|
describe("implement", () => {
|
|
395
401
|
let lastOptions: Extract<StepRequest, { kind: "agent" }>["options"] | undefined
|
|
402
|
+
let requirements = FLAGGED
|
|
396
403
|
const building = async (
|
|
397
404
|
reference: string | undefined,
|
|
398
405
|
tier?: "low" | "high",
|
|
@@ -403,8 +410,7 @@ describe("implement", () => {
|
|
|
403
410
|
lastOptions = undefined
|
|
404
411
|
installContext(
|
|
405
412
|
fixtureContext(
|
|
406
|
-
|
|
407
|
-
{ vars: { prototypeRounds: "0" } },
|
|
413
|
+
{},
|
|
408
414
|
{
|
|
409
415
|
step: (request: StepRequest) => {
|
|
410
416
|
requests.push(request)
|
|
@@ -415,6 +421,7 @@ describe("implement", () => {
|
|
|
415
421
|
pushScope: () => undefined,
|
|
416
422
|
popScope: () => undefined,
|
|
417
423
|
changesSince: () => [],
|
|
424
|
+
read: (path: string) => (path === ".gtd/REQUIREMENTS.md" ? requirements : undefined),
|
|
418
425
|
},
|
|
419
426
|
),
|
|
420
427
|
)
|
|
@@ -507,9 +514,14 @@ describe("implement", () => {
|
|
|
507
514
|
expect(prompt).toContain("otherwise put")
|
|
508
515
|
})
|
|
509
516
|
|
|
510
|
-
it("
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
517
|
+
it("asks for no release flag when the requirements ask for none, whatever the tier", async () => {
|
|
518
|
+
requirements = "# Requirements\n"
|
|
519
|
+
try {
|
|
520
|
+
const prompt = await building(undefined, "high", true)
|
|
521
|
+
expect(prompt).toContain("Add no release flag")
|
|
522
|
+
expect(prompt).toContain("amend")
|
|
523
|
+
} finally {
|
|
524
|
+
requirements = FLAGGED
|
|
525
|
+
}
|
|
514
526
|
})
|
|
515
527
|
})
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
agent,
|
|
4
4
|
applyScript,
|
|
5
5
|
changes,
|
|
6
|
+
changesSince,
|
|
6
7
|
check,
|
|
7
8
|
codeChanges,
|
|
8
9
|
env,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
read,
|
|
15
16
|
refuse,
|
|
16
17
|
removeScript,
|
|
18
|
+
restoreScript,
|
|
17
19
|
run,
|
|
18
20
|
scope,
|
|
19
21
|
vars,
|
|
@@ -22,11 +24,10 @@ import {
|
|
|
22
24
|
type DiffShapeResult,
|
|
23
25
|
type KeptTip,
|
|
24
26
|
} from "../../flows/index.js"
|
|
25
|
-
import { coder, FEEDBACK,
|
|
27
|
+
import { coder, FEEDBACK, NAIVE_TEST, REFACTOR_NOTE, REQUIREMENTS } from "../shared/steps.js"
|
|
26
28
|
import * as t from "../shared/text.js"
|
|
27
29
|
import { defaults } from "../shared/vars.js"
|
|
28
30
|
import { green } from "./green.js"
|
|
29
|
-
import { prototypeTest } from "./prototype.js"
|
|
30
31
|
import { criterionIds, requireCriteria, requireCriteriaFrozen } from "./criteria.js"
|
|
31
32
|
import {
|
|
32
33
|
COMMENT,
|
|
@@ -36,7 +37,8 @@ import {
|
|
|
36
37
|
reviewedRefactor,
|
|
37
38
|
} from "./refactor.js"
|
|
38
39
|
import { isCode } from "../shared/review.js"
|
|
39
|
-
import {
|
|
40
|
+
import { naiveTestOn } from "../shared/options.js"
|
|
41
|
+
import { flagged, requestedFlag } from "./release-flag.js"
|
|
40
42
|
import type { RiskTier } from "./tier.js"
|
|
41
43
|
|
|
42
44
|
/** Naive-pass retries after a `reject` or a red suite; the next one rests at `seam.steer`. */
|
|
@@ -53,7 +55,7 @@ export const judgeSeam = (tip: KeptTip): Promise<DiffShapeResult> =>
|
|
|
53
55
|
exempt: tip.changes.filter((c) => c.status === "added").map((c) => c.path),
|
|
54
56
|
})
|
|
55
57
|
|
|
56
|
-
export const naivePass = (retry?: t.NaiveRetry): Promise<void> =>
|
|
58
|
+
export const naivePass = (retry?: t.NaiveRetry | t.NaiveTested): Promise<void> =>
|
|
57
59
|
t.agentWithSkills("naive", t.naivePassPrompt(retry), {
|
|
58
60
|
label: retry === undefined ? "Writing the naive pass" : "Rewriting the naive pass",
|
|
59
61
|
keep: true,
|
|
@@ -186,10 +188,9 @@ const build = async (options: ImplementOptions): Promise<void> => {
|
|
|
186
188
|
"building",
|
|
187
189
|
t.implementBuildingPrompt(
|
|
188
190
|
options.reference,
|
|
189
|
-
options.tier,
|
|
190
191
|
options.amend,
|
|
191
192
|
options.afterRefactor,
|
|
192
|
-
|
|
193
|
+
requestedFlag(),
|
|
193
194
|
options.rebuild,
|
|
194
195
|
),
|
|
195
196
|
{
|
|
@@ -226,9 +227,8 @@ export const implementing = (options: ImplementOptions): Promise<readonly string
|
|
|
226
227
|
if (built) await build(options)
|
|
227
228
|
else requireCriteriaFrozen(REQUIREMENTS)
|
|
228
229
|
await green()
|
|
229
|
-
await prototypeTest()
|
|
230
230
|
const ids = criterionIds(read(REQUIREMENTS) ?? "")
|
|
231
|
-
await run("closing", removeScript([REQUIREMENTS, REFACTOR_NOTE
|
|
231
|
+
await run("closing", removeScript([REQUIREMENTS, REFACTOR_NOTE]), {
|
|
232
232
|
label: "Closing out the implementation",
|
|
233
233
|
})
|
|
234
234
|
return ids
|
|
@@ -237,14 +237,14 @@ export const implementing = (options: ImplementOptions): Promise<readonly string
|
|
|
237
237
|
interface Decision {
|
|
238
238
|
readonly reference: string
|
|
239
239
|
readonly tip: KeptTip
|
|
240
|
-
/** Absent on `land` (or with
|
|
240
|
+
/** Absent on `land` (or unjudged, with no release flag asked for): the pass itself is the implementation. */
|
|
241
241
|
readonly shape?: DiffShapeResult
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
/** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
|
|
245
245
|
const steer = async (retry: t.NaiveRetry): Promise<boolean> => {
|
|
246
246
|
await human("steer", {
|
|
247
|
-
message: t.seamSteerMessage(retry,
|
|
247
|
+
message: t.seamSteerMessage(retry, flagged()),
|
|
248
248
|
label: "Steer or overrule the naive pass",
|
|
249
249
|
file: REQUIREMENTS,
|
|
250
250
|
acceptClean: true,
|
|
@@ -277,7 +277,7 @@ const budget = (): ((next: t.NaiveRetry) => Promise<Verdict>) => {
|
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
interface Vetting {
|
|
280
|
-
|
|
280
|
+
again: ReturnType<typeof budget>
|
|
281
281
|
/** Set once a human overrules a test fault: the rule does not fit this change, so later passes skip it. */
|
|
282
282
|
waived: boolean
|
|
283
283
|
}
|
|
@@ -304,16 +304,62 @@ const ruled = async (tip: KeptTip, again: Vetting["again"]): Promise<Decision |
|
|
|
304
304
|
return next.kind === "overruled" ? { reference: tip.hash, tip } : next
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
/** Back to `before`: every code path, and the notes file. `.gtd/` edits (the requirements) stay. */
|
|
308
|
+
const untryScript = (before: string): string => {
|
|
309
|
+
const code = changesSince(before).filter((c) => isCode(c.path))
|
|
310
|
+
return restoreScript(
|
|
311
|
+
head(),
|
|
312
|
+
{
|
|
313
|
+
restore: code.filter((c) => c.status !== "added").map((c) => c.path),
|
|
314
|
+
remove: [...code.filter((c) => c.status === "added").map((c) => c.path), NAIVE_TEST],
|
|
315
|
+
},
|
|
316
|
+
before,
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* A person tries the green pass `tip` in the working tree. Resolves
|
|
322
|
+
* `undefined` on a clean landing (signed off), else what they sent back;
|
|
323
|
+
* either way the pass, their code edits and the notes file leave the tree.
|
|
324
|
+
*/
|
|
325
|
+
const tried = async (tip: KeptTip): Promise<t.NaiveTested | undefined> => {
|
|
326
|
+
const before = head()
|
|
327
|
+
await run("applying", applyScript(tip.hash, FEEDBACK, t.naiveApplyFailure(tip.hash), [".gtd"]), {
|
|
328
|
+
label: "Applying the naive pass to try it",
|
|
329
|
+
})
|
|
330
|
+
await human("try", {
|
|
331
|
+
message: t.naiveTryMessage(flagged()),
|
|
332
|
+
label: "Try the naive pass",
|
|
333
|
+
file: NAIVE_TEST,
|
|
334
|
+
acceptClean: true,
|
|
335
|
+
})
|
|
336
|
+
const landed = changes()
|
|
337
|
+
const edits = landed.some((c) => isCode(c.path)) ? head() : undefined
|
|
338
|
+
const notes = read(NAIVE_TEST)?.trim() || undefined
|
|
339
|
+
if (landed.some((c) => c.path === REQUIREMENTS)) requireCriteria(REQUIREMENTS)
|
|
340
|
+
await run("restoring", untryScript(before), { label: "Taking the naive pass out again" })
|
|
341
|
+
return landed.length === 0 ? undefined : { tested: tip.hash, notes, edits }
|
|
342
|
+
}
|
|
343
|
+
|
|
307
344
|
const decide = (): Promise<Decision> =>
|
|
308
345
|
scope("seam", async () => {
|
|
309
346
|
const vetting: Vetting = { again: budget(), waived: false }
|
|
310
|
-
let retry: t.NaiveRetry | undefined
|
|
347
|
+
let retry: t.NaiveRetry | t.NaiveTested | undefined
|
|
311
348
|
for (;;) {
|
|
312
349
|
await naivePass(retry)
|
|
313
350
|
const tip = kept("naive")!
|
|
351
|
+
const verdict = await vetted(tip, vetting)
|
|
352
|
+
if (verdict === undefined && naiveTestOn()) {
|
|
353
|
+
const sentBack = await tried(tip)
|
|
354
|
+
if (sentBack !== undefined) {
|
|
355
|
+
// A person's feedback is no failed attempt: the next pass gets a full retry budget.
|
|
356
|
+
vetting.again = budget()
|
|
357
|
+
retry = sentBack
|
|
358
|
+
continue
|
|
359
|
+
}
|
|
360
|
+
}
|
|
314
361
|
const outcome =
|
|
315
|
-
(await
|
|
316
|
-
(flagModeOn() ? await ruled(tip, vetting.again) : { reference: tip.hash, tip })
|
|
362
|
+
verdict ?? (flagged() ? await ruled(tip, vetting.again) : { reference: tip.hash, tip })
|
|
317
363
|
if (!("kind" in outcome)) return outcome
|
|
318
364
|
retry = outcome.kind === "retry" ? outcome.retry : undefined
|
|
319
365
|
}
|
|
@@ -11,9 +11,7 @@ Feature: the review learns standards from the human's answers
|
|
|
11
11
|
|
|
12
12
|
Background:
|
|
13
13
|
Given the shipped workflows, as gtd install scaffolds them
|
|
14
|
-
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
15
14
|
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
16
|
-
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
17
15
|
And a file "src/calc.ts" with:
|
|
18
16
|
"""
|
|
19
17
|
export const add = (a: number, b: number) => a + b
|
|
@@ -30,16 +30,9 @@ describe("the shipped workflow factories", () => {
|
|
|
30
30
|
})
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
it("
|
|
34
|
-
expect(defaults.flagMode).
|
|
35
|
-
expect(configure(feature({ flagMode: false }))).toMatchObject({
|
|
36
|
-
ok: true,
|
|
37
|
-
build: { defaults: { flagMode: "false" } },
|
|
38
|
-
})
|
|
39
|
-
expect(configure(review({ flagMode: true }))).toMatchObject({
|
|
40
|
-
ok: true,
|
|
41
|
-
build: { defaults: { flagMode: "true" } },
|
|
42
|
-
})
|
|
33
|
+
it("refuse the removed flagMode option", () => {
|
|
34
|
+
expect(defaults.flagMode).toBeUndefined()
|
|
35
|
+
expect(configure(feature({ flagMode: false } as never))).toMatchObject({ ok: false })
|
|
43
36
|
})
|
|
44
37
|
|
|
45
38
|
it("declare flag_risk_tier off by default; it is no factory option", () => {
|
|
@@ -24,11 +24,10 @@ export const ShippedOptions = Schema.Struct({
|
|
|
24
24
|
seamAllow: setting,
|
|
25
25
|
qualityReviews: setting,
|
|
26
26
|
refactorQualityReviews: setting,
|
|
27
|
-
prototypeRounds: setting,
|
|
28
27
|
testCommand: setting,
|
|
29
28
|
plannerModel: setting,
|
|
30
29
|
coderModel: setting,
|
|
31
|
-
|
|
30
|
+
naiveTest: Schema.optionalWith(Schema.Boolean, { exact: true }),
|
|
32
31
|
})
|
|
33
32
|
|
|
34
33
|
export type ShippedOptions = Schema.Schema.Encoded<typeof ShippedOptions>
|
|
@@ -45,11 +44,11 @@ const over = (
|
|
|
45
44
|
),
|
|
46
45
|
})
|
|
47
46
|
|
|
48
|
-
/** The `
|
|
49
|
-
export const
|
|
50
|
-
const value = vars.
|
|
47
|
+
/** The `naiveTest` setting, pinned at process start. Anything but true/false fails rather than guess. */
|
|
48
|
+
export const naiveTestOn = (): boolean => {
|
|
49
|
+
const value = vars.naiveTest ?? defaults.naiveTest!
|
|
51
50
|
if (value !== "true" && value !== "false")
|
|
52
|
-
refuse(`gtd:
|
|
51
|
+
refuse(`gtd: naiveTest must be "true" or "false", got ${JSON.stringify(value)}`)
|
|
53
52
|
return value === "true"
|
|
54
53
|
}
|
|
55
54
|
|
|
@@ -88,12 +88,6 @@ things when redirected — a failing check, or reviewer feedback. Stay
|
|
|
88
88
|
strictly inside that task; never touch code outside it. Treat feedback as the
|
|
89
89
|
diagnosis to act on; discard it, with reason, only when simply wrong.`
|
|
90
90
|
|
|
91
|
-
export const testerPersona = `You are the hands-on tester in gtd's build pipeline: use what was just
|
|
92
|
-
built the way a person would — run the real program, open the real
|
|
93
|
-
page, call the real endpoint — and report exactly what you did and
|
|
94
|
-
what you saw. Never fix anything yourself, and never pass a criterion
|
|
95
|
-
you did not watch hold.`
|
|
96
|
-
|
|
97
91
|
export const finisherPersona = `You are the closing identity in gtd's build pipeline — the last coder
|
|
98
92
|
before it closes. Fix a late-breaking failing check after sign-off,
|
|
99
93
|
focused and minimal. Every turn lands its own commit — nothing here
|
|
@@ -29,10 +29,6 @@ describe("the bundled workflow's skills export", () => {
|
|
|
29
29
|
"implement",
|
|
30
30
|
"implement.fix.suite",
|
|
31
31
|
"implement.health",
|
|
32
|
-
"implement.prototype",
|
|
33
|
-
"implement.prototype.fix",
|
|
34
|
-
"implement.prototype.fix.suite",
|
|
35
|
-
"implement.prototype.health",
|
|
36
32
|
"build.quality.correctness",
|
|
37
33
|
"build.quality.owasp-security",
|
|
38
34
|
"build.quality.ponytail-review",
|
|
@@ -33,11 +33,6 @@ export const skills = (
|
|
|
33
33
|
seam: ["incremental-implementation"],
|
|
34
34
|
implement: ["test-driven-development", "incremental-implementation"],
|
|
35
35
|
"implement.fix.suite": ["debugging-and-error-recovery"],
|
|
36
|
-
// The hands-on tester runs the program, never the suite: no TDD.
|
|
37
|
-
"implement.prototype": [],
|
|
38
|
-
"implement.prototype.fix": ["test-driven-development", "incremental-implementation"],
|
|
39
|
-
"implement.prototype.fix.suite": ["debugging-and-error-recovery"],
|
|
40
|
-
"implement.prototype.health": ["debugging-and-error-recovery"],
|
|
41
36
|
// `health` is shared code reached from both `implement` and `build`.
|
|
42
37
|
"implement.health": ["debugging-and-error-recovery"],
|
|
43
38
|
...lensScopes(
|
|
@@ -14,7 +14,7 @@ export const REVIEW = ".gtd/REVIEW.md"
|
|
|
14
14
|
export const REFACTOR = ".gtd/REFACTOR.md"
|
|
15
15
|
export const REFACTOR_NOTE = ".gtd/REFACTOR-NOTE.md"
|
|
16
16
|
export const QUALITY = ".gtd/QUALITY.md"
|
|
17
|
-
export const
|
|
17
|
+
export const NAIVE_TEST = ".gtd/NAIVE-TEST.md"
|
|
18
18
|
|
|
19
19
|
export const planner = (): string => env.plannerModel ?? ""
|
|
20
20
|
export const coder = (): string => env.coderModel ?? ""
|