@pmelab/gtd 57.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 +9 -8
- package/dist/chunks/{reserved-X2RkNHin.mjs → reserved-BzAFXuz_.mjs} +26 -19
- package/dist/chunks/{review-v7sh5GF4.mjs → review-CVqRS0mc.mjs} +2 -2
- package/dist/chunks/{tail-S04KEewi.mjs → tail-JQzB21Q3.mjs} +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +52 -13
- 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 +74 -28
- package/package.json +1 -1
- package/src/workflows/feature/acceptance-criteria.feature +32 -0
- package/src/workflows/feature/criteria-coverage.feature +0 -1
- package/src/workflows/feature/criteria.ts +10 -4
- package/src/workflows/feature/fast-path.feature +4 -0
- package/src/workflows/feature/feature.test.ts +3 -39
- package/src/workflows/feature/feature.ts +3 -4
- package/src/workflows/feature/lens-models.feature +0 -1
- package/src/workflows/feature/lens-review.feature +0 -1
- package/src/workflows/feature/naive-acceptance.feature +4 -0
- package/src/workflows/feature/naive-test.feature +11 -6
- package/src/workflows/feature/one-step-land.feature +4 -0
- package/src/workflows/feature/planning.ts +2 -0
- package/src/workflows/feature/refactor-absorbs.feature +4 -0
- package/src/workflows/feature/refactor-first.feature +4 -0
- package/src/workflows/feature/refactor-lens-models.feature +4 -0
- package/src/workflows/feature/refactor-rebuild.feature +4 -0
- package/src/workflows/feature/refactor-review.feature +20 -0
- 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 +0 -1
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -1
- package/src/workflows/feature/risk-tier.feature +32 -0
- package/src/workflows/feature/seam-rejection.feature +8 -0
- package/src/workflows/feature/seam.feature +8 -0
- package/src/workflows/feature/seam.test.ts +20 -7
- package/src/workflows/feature/seam.ts +7 -7
- package/src/workflows/feature/standards.feature +0 -1
- package/src/workflows/shared/options.test.ts +3 -10
- package/src/workflows/shared/options.ts +0 -9
- package/src/workflows/shared/text.test.ts +36 -28
- package/src/workflows/shared/text.ts +37 -20
- package/src/workflows/shared/vars.ts +0 -1
- package/src/workflows/feature/flag-mode.feature +0 -88
|
@@ -51,6 +51,10 @@ Feature: the refactor is reviewed on its own before the feature is implemented
|
|
|
51
51
|
|
|
52
52
|
- the greeting module
|
|
53
53
|
|
|
54
|
+
## Release Flag
|
|
55
|
+
|
|
56
|
+
new
|
|
57
|
+
|
|
54
58
|
## Acceptance Criteria
|
|
55
59
|
|
|
56
60
|
- AC-1: the change behaves as described.
|
|
@@ -149,6 +153,10 @@ Feature: the refactor is reviewed on its own before the feature is implemented
|
|
|
149
153
|
|
|
150
154
|
- the greeting module
|
|
151
155
|
|
|
156
|
+
## Release Flag
|
|
157
|
+
|
|
158
|
+
new
|
|
159
|
+
|
|
152
160
|
## Acceptance Criteria
|
|
153
161
|
|
|
154
162
|
- AC-1: the change behaves as described.
|
|
@@ -246,6 +254,10 @@ Feature: the refactor is reviewed on its own before the feature is implemented
|
|
|
246
254
|
|
|
247
255
|
- the greeting module
|
|
248
256
|
|
|
257
|
+
## Release Flag
|
|
258
|
+
|
|
259
|
+
new
|
|
260
|
+
|
|
249
261
|
## Acceptance Criteria
|
|
250
262
|
|
|
251
263
|
- AC-1: the change behaves as described.
|
|
@@ -1193,6 +1205,10 @@ Feature: the refactor is reviewed on its own before the feature is implemented
|
|
|
1193
1205
|
|
|
1194
1206
|
- the greeting module
|
|
1195
1207
|
|
|
1208
|
+
## Release Flag
|
|
1209
|
+
|
|
1210
|
+
new
|
|
1211
|
+
|
|
1196
1212
|
## Acceptance Criteria
|
|
1197
1213
|
|
|
1198
1214
|
- AC-1: the change behaves as described.
|
|
@@ -1318,6 +1334,10 @@ Feature: the refactor is reviewed on its own before the feature is implemented
|
|
|
1318
1334
|
|
|
1319
1335
|
- the greeting module
|
|
1320
1336
|
|
|
1337
|
+
## Release Flag
|
|
1338
|
+
|
|
1339
|
+
new
|
|
1340
|
+
|
|
1321
1341
|
## Acceptance Criteria
|
|
1322
1342
|
|
|
1323
1343
|
- AC-1: the change behaves as described.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
@inmem
|
|
2
|
+
Feature: a release flag only when the requirements ask for one
|
|
3
|
+
|
|
4
|
+
A change ships unflagged unless `.gtd/REQUIREMENTS.md` carries a
|
|
5
|
+
`## Release Flag` section — written by triage, or by the skip path's impact
|
|
6
|
+
turn, only when the sketch asks for a flag, and editable at the design gate.
|
|
7
|
+
Its first line is `new` (a new release flag) or `existing: <NAME>` (behind a
|
|
8
|
+
flag that already exists). Without it no seam judge or refactor runs and the
|
|
9
|
+
naive pass lands as the implementation; with it the seam judge rules on the
|
|
10
|
+
pass.
|
|
11
|
+
|
|
12
|
+
Background:
|
|
13
|
+
Given the shipped workflows, as gtd install scaffolds them
|
|
14
|
+
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
15
|
+
And an environment variable "GTD_FLAG_RISK_TIER" set to "0"
|
|
16
|
+
And a file "src/calc.ts" with:
|
|
17
|
+
"""
|
|
18
|
+
export const add = (a: number, b: number) => a + b
|
|
19
|
+
"""
|
|
20
|
+
And the working tree is committed as "feat: add calculator"
|
|
21
|
+
And step "unwind" changes nothing
|
|
22
|
+
And step "start-gate.check" changes nothing
|
|
23
|
+
And step "seam.naive" writes "src/calc.test.ts" with:
|
|
24
|
+
"""
|
|
25
|
+
it("[AC-1] subtracts", () => {})
|
|
26
|
+
"""
|
|
27
|
+
And step "seam.naive" writes "src/calc.ts" with:
|
|
28
|
+
"""
|
|
29
|
+
export const add = (a: number, b: number) => a + b
|
|
30
|
+
export const subtract = (a: number, b: number) => a - b
|
|
31
|
+
"""
|
|
32
|
+
And step "seam.check" changes nothing
|
|
33
|
+
|
|
34
|
+
Rule: the design path
|
|
35
|
+
|
|
36
|
+
Background:
|
|
37
|
+
Given a file "TODO.md" with:
|
|
38
|
+
"""
|
|
39
|
+
# Add subtract
|
|
40
|
+
|
|
41
|
+
subtract(a, b) returns the difference.
|
|
42
|
+
"""
|
|
43
|
+
And step "design.gate.answer" changes nothing
|
|
44
|
+
|
|
45
|
+
Scenario: with no Release Flag section the naive pass lands unjudged, and no flag is asked for
|
|
46
|
+
Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
47
|
+
"""
|
|
48
|
+
# Requirements
|
|
49
|
+
|
|
50
|
+
## Impact
|
|
51
|
+
|
|
52
|
+
- the calculator module
|
|
53
|
+
|
|
54
|
+
## Acceptance Criteria
|
|
55
|
+
|
|
56
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
57
|
+
"""
|
|
58
|
+
And step "implement.applying" runs its script
|
|
59
|
+
And step "implement.health.check" changes nothing
|
|
60
|
+
And step "implement.closing" runs its script
|
|
61
|
+
When the process is driven
|
|
62
|
+
Then the process rests at "build.review.reviewing"
|
|
63
|
+
And "src/calc.ts" contains "subtract"
|
|
64
|
+
And no commit body contains "seam.judge"
|
|
65
|
+
And the commit subjects end with:
|
|
66
|
+
"""
|
|
67
|
+
gtd(human): design.gate.answer → seam.naive
|
|
68
|
+
gtd(agent): seam.naive → seam.check
|
|
69
|
+
gtd(check): seam.check → implement.applying
|
|
70
|
+
gtd(check): implement.applying → implement.health.check
|
|
71
|
+
gtd(check): implement.health.check → implement.closing
|
|
72
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
Scenario: a new release flag asked for sends the naive pass to the seam judge
|
|
76
|
+
Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
77
|
+
"""
|
|
78
|
+
# Requirements
|
|
79
|
+
|
|
80
|
+
## Impact
|
|
81
|
+
|
|
82
|
+
- the calculator module
|
|
83
|
+
|
|
84
|
+
## Release Flag
|
|
85
|
+
|
|
86
|
+
new
|
|
87
|
+
|
|
88
|
+
## Acceptance Criteria
|
|
89
|
+
|
|
90
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
91
|
+
"""
|
|
92
|
+
When the process is driven
|
|
93
|
+
Then the process rests at "seam.judge"
|
|
94
|
+
|
|
95
|
+
Scenario: an existing flag asked for sends the naive pass to the seam judge too
|
|
96
|
+
Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
97
|
+
"""
|
|
98
|
+
# Requirements
|
|
99
|
+
|
|
100
|
+
## Impact
|
|
101
|
+
|
|
102
|
+
- the calculator module
|
|
103
|
+
|
|
104
|
+
## Release Flag
|
|
105
|
+
|
|
106
|
+
existing: GTD_FLAG_BETA
|
|
107
|
+
|
|
108
|
+
## Acceptance Criteria
|
|
109
|
+
|
|
110
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
111
|
+
"""
|
|
112
|
+
When the process is driven
|
|
113
|
+
Then the process rests at "seam.judge"
|
|
114
|
+
|
|
115
|
+
Scenario: a Release Flag section that asks for neither is refused
|
|
116
|
+
Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
117
|
+
"""
|
|
118
|
+
# Requirements
|
|
119
|
+
|
|
120
|
+
## Impact
|
|
121
|
+
|
|
122
|
+
- the calculator module
|
|
123
|
+
|
|
124
|
+
## Release Flag
|
|
125
|
+
|
|
126
|
+
maybe later
|
|
127
|
+
|
|
128
|
+
## Acceptance Criteria
|
|
129
|
+
|
|
130
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
131
|
+
"""
|
|
132
|
+
Then driving the process is refused with "release-flag"
|
|
133
|
+
|
|
134
|
+
Rule: the skip path
|
|
135
|
+
|
|
136
|
+
Background:
|
|
137
|
+
Given a file "TODO.md" with:
|
|
138
|
+
"""
|
|
139
|
+
# Add subtract
|
|
140
|
+
|
|
141
|
+
Ship it behind the existing GTD_FLAG_BETA flag.
|
|
142
|
+
|
|
143
|
+
## Acceptance criteria
|
|
144
|
+
|
|
145
|
+
- subtract(a, b) returns a - b.
|
|
146
|
+
"""
|
|
147
|
+
And judge step "design.clear" answers:
|
|
148
|
+
"""
|
|
149
|
+
[{"id":"clear","answer":"yes","p":0.9}]
|
|
150
|
+
"""
|
|
151
|
+
And step "design.normalise" runs its script
|
|
152
|
+
|
|
153
|
+
Scenario: the impact turn records the flag the sketch asks for, and the seam judge rules
|
|
154
|
+
Given step "design.impact" appends to ".gtd/REQUIREMENTS.md":
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
## Impact
|
|
158
|
+
|
|
159
|
+
- the calculator module
|
|
160
|
+
|
|
161
|
+
## Release Flag
|
|
162
|
+
|
|
163
|
+
existing: GTD_FLAG_BETA
|
|
164
|
+
"""
|
|
165
|
+
When the process is driven
|
|
166
|
+
Then the process rests at "seam.judge"
|
|
167
|
+
And ".gtd/REQUIREMENTS.md" contains "existing: GTD_FLAG_BETA"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { releaseFlagOf } from "./release-flag.js"
|
|
3
|
+
|
|
4
|
+
const requirements = (section: string): string =>
|
|
5
|
+
`# Requirements\n\n## Impact\n\n- a module\n\n${section}## Acceptance Criteria\n\n- AC-1: a\n`
|
|
6
|
+
|
|
7
|
+
describe("releaseFlagOf", () => {
|
|
8
|
+
it("asks for no flag without the section, or with none", () => {
|
|
9
|
+
expect(releaseFlagOf(requirements(""))).toEqual({ kind: "none" })
|
|
10
|
+
expect(releaseFlagOf(requirements("## Release Flag\n\nnone\n\n"))).toEqual({ kind: "none" })
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it("reads new and an existing flag's name, backticked or not", () => {
|
|
14
|
+
expect(releaseFlagOf(requirements("## Release Flag\n\nnew\n\n"))).toEqual({ kind: "new" })
|
|
15
|
+
for (const line of [
|
|
16
|
+
"existing: GTD_FLAG_BETA",
|
|
17
|
+
"existing: `GTD_FLAG_BETA`",
|
|
18
|
+
"Existing:GTD_FLAG_BETA",
|
|
19
|
+
])
|
|
20
|
+
expect(releaseFlagOf(requirements(`## Release Flag\n\n${line}\n\n`))).toEqual({
|
|
21
|
+
kind: "existing",
|
|
22
|
+
name: "GTD_FLAG_BETA",
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it("reads only the first line under the heading, and only a level-2 heading", () => {
|
|
27
|
+
expect(
|
|
28
|
+
releaseFlagOf(requirements("## Release Flag\n\nnew\n\nbecause the sketch says so\n\n")),
|
|
29
|
+
).toEqual({
|
|
30
|
+
kind: "new",
|
|
31
|
+
})
|
|
32
|
+
expect(releaseFlagOf(requirements("### Release Flag\n\nnew\n\n"))).toEqual({ kind: "none" })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("ignores a fenced heading", () => {
|
|
36
|
+
expect(releaseFlagOf(requirements("```\n## Release Flag\n\nnew\n```\n\n"))).toEqual({
|
|
37
|
+
kind: "none",
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("names the line it cannot read", () => {
|
|
42
|
+
expect(releaseFlagOf(requirements("## Release Flag\n\nmaybe later\n\n"))).toEqual({
|
|
43
|
+
unreadable: "maybe later",
|
|
44
|
+
})
|
|
45
|
+
expect(releaseFlagOf(requirements("## Release Flag\n\nexisting:\n\n"))).toEqual({
|
|
46
|
+
unreadable: "existing:",
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -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"
|
|
@@ -11,7 +11,6 @@ Feature: a feature process ends at the human review
|
|
|
11
11
|
Background:
|
|
12
12
|
Given the shipped workflows, as gtd install scaffolds them
|
|
13
13
|
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
14
|
-
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
15
14
|
And a file "src/calc.ts" with:
|
|
16
15
|
"""
|
|
17
16
|
export const add = (a: number, b: number) => a + b
|
|
@@ -11,7 +11,6 @@ Feature: a reviewer walks the human through the feature's code
|
|
|
11
11
|
Background:
|
|
12
12
|
Given the shipped workflows, as gtd install scaffolds them
|
|
13
13
|
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
14
|
-
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
15
14
|
And a file "src/calc.ts" with:
|
|
16
15
|
"""
|
|
17
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.
|
|
@@ -38,6 +38,10 @@ Feature: rejected naive passes walk one fixed path through the steer gate to the
|
|
|
38
38
|
|
|
39
39
|
- the greeting module
|
|
40
40
|
|
|
41
|
+
## Release Flag
|
|
42
|
+
|
|
43
|
+
new
|
|
44
|
+
|
|
41
45
|
## Acceptance Criteria
|
|
42
46
|
|
|
43
47
|
- AC-1: the change behaves as described.
|
|
@@ -83,6 +87,10 @@ Feature: rejected naive passes walk one fixed path through the steer gate to the
|
|
|
83
87
|
|
|
84
88
|
- the greeting module
|
|
85
89
|
|
|
90
|
+
## Release Flag
|
|
91
|
+
|
|
92
|
+
new
|
|
93
|
+
|
|
86
94
|
## Acceptance Criteria
|
|
87
95
|
|
|
88
96
|
- AC-1: the change behaves as described.
|
|
@@ -48,6 +48,10 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
48
48
|
|
|
49
49
|
- the greeting module
|
|
50
50
|
|
|
51
|
+
## Release Flag
|
|
52
|
+
|
|
53
|
+
new
|
|
54
|
+
|
|
51
55
|
## Acceptance Criteria
|
|
52
56
|
|
|
53
57
|
- AC-1: the change behaves as described.
|
|
@@ -241,6 +245,10 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
241
245
|
|
|
242
246
|
- the greeting module
|
|
243
247
|
|
|
248
|
+
## Release Flag
|
|
249
|
+
|
|
250
|
+
new
|
|
251
|
+
|
|
244
252
|
## Acceptance Criteria
|
|
245
253
|
|
|
246
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",
|
|
@@ -414,6 +421,7 @@ describe("implement", () => {
|
|
|
414
421
|
pushScope: () => undefined,
|
|
415
422
|
popScope: () => undefined,
|
|
416
423
|
changesSince: () => [],
|
|
424
|
+
read: (path: string) => (path === ".gtd/REQUIREMENTS.md" ? requirements : undefined),
|
|
417
425
|
},
|
|
418
426
|
),
|
|
419
427
|
)
|
|
@@ -506,9 +514,14 @@ describe("implement", () => {
|
|
|
506
514
|
expect(prompt).toContain("otherwise put")
|
|
507
515
|
})
|
|
508
516
|
|
|
509
|
-
it("
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
+
}
|
|
513
526
|
})
|
|
514
527
|
})
|
|
@@ -37,7 +37,8 @@ import {
|
|
|
37
37
|
reviewedRefactor,
|
|
38
38
|
} from "./refactor.js"
|
|
39
39
|
import { isCode } from "../shared/review.js"
|
|
40
|
-
import {
|
|
40
|
+
import { naiveTestOn } from "../shared/options.js"
|
|
41
|
+
import { flagged, requestedFlag } from "./release-flag.js"
|
|
41
42
|
import type { RiskTier } from "./tier.js"
|
|
42
43
|
|
|
43
44
|
/** Naive-pass retries after a `reject` or a red suite; the next one rests at `seam.steer`. */
|
|
@@ -187,10 +188,9 @@ const build = async (options: ImplementOptions): Promise<void> => {
|
|
|
187
188
|
"building",
|
|
188
189
|
t.implementBuildingPrompt(
|
|
189
190
|
options.reference,
|
|
190
|
-
options.tier,
|
|
191
191
|
options.amend,
|
|
192
192
|
options.afterRefactor,
|
|
193
|
-
|
|
193
|
+
requestedFlag(),
|
|
194
194
|
options.rebuild,
|
|
195
195
|
),
|
|
196
196
|
{
|
|
@@ -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,
|
|
@@ -328,7 +328,7 @@ const tried = async (tip: KeptTip): Promise<t.NaiveTested | undefined> => {
|
|
|
328
328
|
label: "Applying the naive pass to try it",
|
|
329
329
|
})
|
|
330
330
|
await human("try", {
|
|
331
|
-
message: t.naiveTryMessage(
|
|
331
|
+
message: t.naiveTryMessage(flagged()),
|
|
332
332
|
label: "Try the naive pass",
|
|
333
333
|
file: NAIVE_TEST,
|
|
334
334
|
acceptClean: true,
|
|
@@ -359,7 +359,7 @@ const decide = (): Promise<Decision> =>
|
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
const outcome =
|
|
362
|
-
verdict ?? (
|
|
362
|
+
verdict ?? (flagged() ? await ruled(tip, vetting.again) : { reference: tip.hash, tip })
|
|
363
363
|
if (!("kind" in outcome)) return outcome
|
|
364
364
|
retry = outcome.kind === "retry" ? outcome.retry : undefined
|
|
365
365
|
}
|
|
@@ -12,7 +12,6 @@ Feature: the review learns standards from the human's answers
|
|
|
12
12
|
Background:
|
|
13
13
|
Given the shipped workflows, as gtd install scaffolds them
|
|
14
14
|
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
15
|
-
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
16
15
|
And a file "src/calc.ts" with:
|
|
17
16
|
"""
|
|
18
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", () => {
|
|
@@ -27,7 +27,6 @@ export const ShippedOptions = Schema.Struct({
|
|
|
27
27
|
testCommand: setting,
|
|
28
28
|
plannerModel: setting,
|
|
29
29
|
coderModel: setting,
|
|
30
|
-
flagMode: Schema.optionalWith(Schema.Boolean, { exact: true }),
|
|
31
30
|
naiveTest: Schema.optionalWith(Schema.Boolean, { exact: true }),
|
|
32
31
|
})
|
|
33
32
|
|
|
@@ -45,14 +44,6 @@ const over = (
|
|
|
45
44
|
),
|
|
46
45
|
})
|
|
47
46
|
|
|
48
|
-
/** The `flagMode` setting, pinned at process start. Anything but true/false fails rather than guess a side. */
|
|
49
|
-
export const flagModeOn = (): boolean => {
|
|
50
|
-
const value = vars.flagMode ?? defaults.flagMode!
|
|
51
|
-
if (value !== "true" && value !== "false")
|
|
52
|
-
refuse(`gtd: flagMode must be "true" or "false", got ${JSON.stringify(value)}`)
|
|
53
|
-
return value === "true"
|
|
54
|
-
}
|
|
55
|
-
|
|
56
47
|
/** The `naiveTest` setting, pinned at process start. Anything but true/false fails rather than guess. */
|
|
57
48
|
export const naiveTestOn = (): boolean => {
|
|
58
49
|
const value = vars.naiveTest ?? defaults.naiveTest!
|