@pmelab/gtd 51.0.1 → 52.1.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 +46 -40
- package/dist/chunks/{reserved-CUkk9XgL.mjs → reserved-DxB5CROj.mjs} +73 -38
- package/dist/chunks/{review-BaCXlIT2.mjs → review-CKMSdvgW.mjs} +11 -9
- package/dist/chunks/{steering-DtUVUz9l.mjs → steering-CLhu8C7q.mjs} +262 -52
- package/dist/chunks/{tail-CX6Pdsql.mjs → tail-QZc7Uz2U.mjs} +1 -1
- package/dist/gtd.bundle.mjs +198 -43
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +1 -1
- package/dist/web/main.css +1 -1
- package/dist/web/main.js +510 -147
- package/dist/workflow-kit.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +228 -222
- package/dist/workflows/fix.mjs +3 -3
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +570 -317
- package/package.json +5 -4
- package/src/steering/FORMATS.md +4 -1
- package/src/steering/SteeringFormat.ts +7 -1
- package/src/steering/contract.test.ts +2 -1
- package/src/steering/fixtures/review/sectioned.md +31 -0
- package/src/steering/fixtures/review/sectioned.read.json +119 -0
- package/src/steering/index.ts +4 -0
- package/src/steering/qa.ts +1 -1
- package/src/steering/review.test.ts +270 -4
- package/src/steering/review.ts +328 -69
- package/src/testkit/steps.ts +13 -0
- package/src/workflows/feature/acceptance-criteria.feature +159 -3
- package/src/workflows/feature/continuation-cap.feature +4 -0
- package/src/workflows/feature/continuation-rebuild.feature +4 -0
- package/src/workflows/feature/continuation-slices.feature +4 -0
- package/src/workflows/feature/continuation.feature +4 -0
- package/src/workflows/feature/criteria.test.ts +60 -1
- package/src/workflows/feature/criteria.ts +87 -8
- package/src/workflows/feature/fast-path.feature +5 -6
- package/src/workflows/feature/feature.test.ts +6 -5
- package/src/workflows/feature/feature.ts +1 -2
- package/src/workflows/feature/flag-mode.feature +5 -6
- package/src/workflows/feature/green.ts +22 -0
- package/src/workflows/feature/hands-on-skip.feature +384 -0
- package/src/workflows/feature/lens-models.feature +4 -4
- package/src/workflows/feature/lens-review.feature +10 -8
- package/src/workflows/feature/naive-acceptance.feature +4 -0
- package/src/workflows/feature/one-step-land.feature +5 -6
- package/src/workflows/feature/planning.ts +2 -1
- package/src/workflows/feature/prototype-rounds.feature +5 -1
- package/src/workflows/feature/prototype.feature +18 -9
- package/src/workflows/feature/prototype.ts +48 -17
- package/src/workflows/feature/refactor-absorbs.feature +7 -8
- package/src/workflows/feature/refactor-first.feature +7 -8
- package/src/workflows/feature/refactor-lens-models.feature +4 -0
- package/src/workflows/feature/refactor-rebuild.feature +11 -12
- package/src/workflows/feature/refactor-review.feature +72 -72
- package/src/workflows/feature/review-answers.feature +94 -5
- package/src/workflows/feature/review-gate.feature +23 -53
- package/src/workflows/feature/reviewer-walkthrough.feature +33 -12
- package/src/workflows/feature/risk-tier.feature +32 -0
- package/src/workflows/feature/seam-rejection.feature +9 -6
- package/src/workflows/feature/seam.feature +9 -6
- package/src/workflows/feature/seam.test.ts +5 -3
- package/src/workflows/feature/seam.ts +2 -3
- package/src/workflows/feature/severity-budget.feature +11 -7
- package/src/workflows/feature/severity-sorted-review.feature +54 -54
- package/src/workflows/feature/slice-findings.feature +9 -15
- package/src/workflows/feature/slicing.feature +36 -24
- package/src/workflows/feature/standards.feature +4 -4
- package/src/workflows/feature/tier-pin.feature +4 -0
- package/src/workflows/shared/review.ts +26 -6
- package/src/workflows/shared/text.test.ts +85 -11
- package/src/workflows/shared/text.ts +95 -42
- package/src/workflows/feature/coverage.feature +0 -165
- package/src/workflows/feature/coverage.ts +0 -120
|
@@ -36,6 +36,10 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
36
36
|
"""
|
|
37
37
|
# Requirements
|
|
38
38
|
|
|
39
|
+
## Impact
|
|
40
|
+
|
|
41
|
+
- the greeting module
|
|
42
|
+
|
|
39
43
|
## Acceptance Criteria
|
|
40
44
|
|
|
41
45
|
- AC-1: subtract(a, b) returns a - b.
|
|
@@ -47,10 +51,6 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
47
51
|
it("[AC-2] multiplies", () => {})
|
|
48
52
|
"""
|
|
49
53
|
And step "seam.naive" is prompted with "Read `.gtd/REQUIREMENTS.md`"
|
|
50
|
-
And judge step "implement.coverage" answers:
|
|
51
|
-
"""
|
|
52
|
-
[{"id":"AC-1","answer":"yes","p":0.9},{"id":"AC-2","answer":"yes","p":0.9}]
|
|
53
|
-
"""
|
|
54
54
|
When the process is driven
|
|
55
55
|
Then the process rests at "build.review.reviewing"
|
|
56
56
|
And the commit subjects end with:
|
|
@@ -59,8 +59,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
59
59
|
gtd(agent): seam.naive → seam.check
|
|
60
60
|
gtd(check): seam.check → implement.applying
|
|
61
61
|
gtd(check): implement.applying → implement.health.check
|
|
62
|
-
gtd(check): implement.health.check → implement.
|
|
63
|
-
gtd(judge): implement.coverage → implement.closing
|
|
62
|
+
gtd(check): implement.health.check → implement.closing
|
|
64
63
|
gtd(check): implement.closing → build.review.reviewing
|
|
65
64
|
"""
|
|
66
65
|
And the output lines of gtd "slices" start with:
|
|
@@ -75,6 +74,10 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
75
74
|
"""
|
|
76
75
|
# Requirements
|
|
77
76
|
|
|
77
|
+
## Impact
|
|
78
|
+
|
|
79
|
+
- the greeting module
|
|
80
|
+
|
|
78
81
|
## Acceptance Criteria
|
|
79
82
|
|
|
80
83
|
- AC-1: subtract(a, b) returns a - b.
|
|
@@ -104,14 +107,6 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
104
107
|
"""
|
|
105
108
|
it("[AC-2] multiplies", () => {})
|
|
106
109
|
"""
|
|
107
|
-
And judge step "implement.coverage", when its questions contain "AC-3", answers:
|
|
108
|
-
"""
|
|
109
|
-
[{"id":"AC-1","answer":"yes","p":0.9},{"id":"AC-3","answer":"yes","p":0.9}]
|
|
110
|
-
"""
|
|
111
|
-
And judge step "implement.coverage", when its questions contain "AC-2", answers:
|
|
112
|
-
"""
|
|
113
|
-
[{"id":"AC-2","answer":"yes","p":0.9}]
|
|
114
|
-
"""
|
|
115
110
|
When the process is driven
|
|
116
111
|
Then the process rests at "build.review.reviewing"
|
|
117
112
|
And the commit subjects end with:
|
|
@@ -122,14 +117,12 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
122
117
|
gtd(agent): seam.naive → seam.check
|
|
123
118
|
gtd(check): seam.check → implement.applying
|
|
124
119
|
gtd(check): implement.applying → implement.health.check
|
|
125
|
-
gtd(check): implement.health.check → implement.
|
|
126
|
-
gtd(judge): implement.coverage → implement.closing
|
|
120
|
+
gtd(check): implement.health.check → implement.closing
|
|
127
121
|
gtd(check): implement.closing → seam.naive
|
|
128
122
|
gtd(agent): seam.naive → seam.check
|
|
129
123
|
gtd(check): seam.check → implement.applying
|
|
130
124
|
gtd(check): implement.applying → implement.health.check
|
|
131
|
-
gtd(check): implement.health.check → implement.
|
|
132
|
-
gtd(judge): implement.coverage → implement.closing
|
|
125
|
+
gtd(check): implement.health.check → implement.closing
|
|
133
126
|
gtd(check): implement.closing → build.review.reviewing
|
|
134
127
|
"""
|
|
135
128
|
And the output lines of gtd "slices" start with:
|
|
@@ -148,6 +141,10 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
148
141
|
"""
|
|
149
142
|
# Requirements
|
|
150
143
|
|
|
144
|
+
## Impact
|
|
145
|
+
|
|
146
|
+
- the greeting module
|
|
147
|
+
|
|
151
148
|
## Acceptance Criteria
|
|
152
149
|
|
|
153
150
|
- AC-1: subtract(a, b) returns a - b.
|
|
@@ -171,6 +168,10 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
171
168
|
"""
|
|
172
169
|
# Requirements
|
|
173
170
|
|
|
171
|
+
## Impact
|
|
172
|
+
|
|
173
|
+
- the greeting module
|
|
174
|
+
|
|
174
175
|
## Acceptance Criteria
|
|
175
176
|
|
|
176
177
|
- AC-1: subtract(a, b) returns a - b.
|
|
@@ -196,6 +197,10 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
196
197
|
"""
|
|
197
198
|
# Requirements
|
|
198
199
|
|
|
200
|
+
## Impact
|
|
201
|
+
|
|
202
|
+
- the greeting module
|
|
203
|
+
|
|
199
204
|
## Acceptance Criteria
|
|
200
205
|
|
|
201
206
|
- AC-1: subtract(a, b) returns a - b.
|
|
@@ -206,6 +211,10 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
206
211
|
"""
|
|
207
212
|
# Requirements
|
|
208
213
|
|
|
214
|
+
## Impact
|
|
215
|
+
|
|
216
|
+
- the greeting module
|
|
217
|
+
|
|
209
218
|
## Acceptance Criteria
|
|
210
219
|
|
|
211
220
|
- AC-1: subtract(a, b) returns a - b.
|
|
@@ -230,6 +239,10 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
230
239
|
"""
|
|
231
240
|
# Requirements
|
|
232
241
|
|
|
242
|
+
## Impact
|
|
243
|
+
|
|
244
|
+
- the greeting module
|
|
245
|
+
|
|
233
246
|
## Acceptance Criteria
|
|
234
247
|
|
|
235
248
|
- AC-1: subtract(a, b) returns a - b.
|
|
@@ -253,16 +266,16 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
253
266
|
"""
|
|
254
267
|
it("[AC-1] subtracts", () => {})
|
|
255
268
|
"""
|
|
256
|
-
And judge step "implement.coverage" answers:
|
|
257
|
-
"""
|
|
258
|
-
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
259
|
-
"""
|
|
260
269
|
When the process is driven
|
|
261
270
|
Then the process rests at "seam.steer"
|
|
262
271
|
Given a file ".gtd/REQUIREMENTS.md" with:
|
|
263
272
|
"""
|
|
264
273
|
# Requirements
|
|
265
274
|
|
|
275
|
+
## Impact
|
|
276
|
+
|
|
277
|
+
- the greeting module
|
|
278
|
+
|
|
266
279
|
## Acceptance Criteria
|
|
267
280
|
|
|
268
281
|
- AC-1: subtract(a, b) returns a - b.
|
|
@@ -276,8 +289,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
276
289
|
gtd(agent): seam.naive → seam.check
|
|
277
290
|
gtd(check): seam.check → implement.applying
|
|
278
291
|
gtd(check): implement.applying → implement.health.check
|
|
279
|
-
gtd(check): implement.health.check → implement.
|
|
280
|
-
gtd(judge): implement.coverage → implement.closing
|
|
292
|
+
gtd(check): implement.health.check → implement.closing
|
|
281
293
|
gtd(check): implement.closing → build.review.reviewing
|
|
282
294
|
"""
|
|
283
295
|
And ".gtd/REQUIREMENTS.md" does not exist
|
|
@@ -37,6 +37,10 @@ Feature: the review learns standards from the human's answers
|
|
|
37
37
|
|
|
38
38
|
- `subtract(a, b)` returns `a - b`.
|
|
39
39
|
|
|
40
|
+
## Impact
|
|
41
|
+
|
|
42
|
+
- the greeting module
|
|
43
|
+
|
|
40
44
|
## Acceptance Criteria
|
|
41
45
|
|
|
42
46
|
- AC-1: subtract(a, b) returns a - b.
|
|
@@ -55,10 +59,6 @@ Feature: the review learns standards from the human's answers
|
|
|
55
59
|
And step "implement.applying" runs its script
|
|
56
60
|
And step "implement.health.check" changes nothing
|
|
57
61
|
And step "implement.closing" runs its script
|
|
58
|
-
And judge step "implement.coverage" answers:
|
|
59
|
-
"""
|
|
60
|
-
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
61
|
-
"""
|
|
62
62
|
And step "build.quality.correctness.reviewing" writes ".gtd/QUALITY.md" with:
|
|
63
63
|
"""
|
|
64
64
|
# Quality
|
|
@@ -16,7 +16,14 @@ import {
|
|
|
16
16
|
type Judgment,
|
|
17
17
|
type ScopeAccess,
|
|
18
18
|
} from "../../flows/index.js"
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
REVIEW_SECTIONS,
|
|
21
|
+
reviewChunks,
|
|
22
|
+
reviewRisks,
|
|
23
|
+
reviewSections,
|
|
24
|
+
sectionedReview,
|
|
25
|
+
type ReviewNote,
|
|
26
|
+
} from "../../steering/index.js"
|
|
20
27
|
import * as kit from "../../workflow-kit/index.js"
|
|
21
28
|
import { guarded, healthy, type EscalationCount } from "./health.js"
|
|
22
29
|
import { NO_FINDINGS, type LensReport } from "./lenses.js"
|
|
@@ -231,11 +238,16 @@ const batched = <T extends { readonly evidence: string }>(
|
|
|
231
238
|
}
|
|
232
239
|
|
|
233
240
|
/**
|
|
234
|
-
* Rates every chunk the reviewer wrote
|
|
235
|
-
*
|
|
236
|
-
*
|
|
241
|
+
* Rates every chunk the reviewer wrote and sorts the \`findings\` among them
|
|
242
|
+
* first in \`full\`, each led by its tier and reason; a walkthrough chunk
|
|
243
|
+
* keeps its place and carries no tier. It is still rated, so a review with no
|
|
244
|
+
* findings still reaches \`review.ranking\` and older histories replay.
|
|
237
245
|
*/
|
|
238
|
-
const ranked = async (
|
|
246
|
+
const ranked = async (
|
|
247
|
+
review: string,
|
|
248
|
+
full: string,
|
|
249
|
+
findings: ReadonlySet<string>,
|
|
250
|
+
): Promise<string> => {
|
|
239
251
|
const texts = t.chunkTexts(full)
|
|
240
252
|
const chunks = reviewChunks(review).map((c, i) => ({
|
|
241
253
|
id: `C${i + 1}`,
|
|
@@ -268,6 +280,7 @@ const ranked = async (review: string, full: string): Promise<string> => {
|
|
|
268
280
|
full,
|
|
269
281
|
chunks
|
|
270
282
|
.map((c, i) => ({ c, i, s: severities.get(c.id)! }))
|
|
283
|
+
.filter(({ c }) => findings.has(c.title))
|
|
271
284
|
.sort((x, y) => chunkKey(x.s) - chunkKey(y.s) || x.i - y.i)
|
|
272
285
|
.map(({ c, s }) => ({ title: c.title, label: s.reason })),
|
|
273
286
|
)
|
|
@@ -342,6 +355,10 @@ const requirePointers = (
|
|
|
342
355
|
...chunks
|
|
343
356
|
.filter((c, i) => chunks.findIndex((d) => d.title === c.title) !== i)
|
|
344
357
|
.map((c) => ` - ${REVIEW}:${c.line + 1}: ${c.title} — a second chunk with this title`),
|
|
358
|
+
...reviewSections(review).map(
|
|
359
|
+
(s) =>
|
|
360
|
+
` - ${REVIEW}:${s.line + 1}: ${REVIEW_SECTIONS[s.section]} — gtd sorts the chunks into sections; write \`## \` chunks only`,
|
|
361
|
+
),
|
|
345
362
|
// A risk is fixed before the human sees it; a note is the human's to decide.
|
|
346
363
|
...reviewRisks(review)
|
|
347
364
|
.filter((r) => [...noted].some((title) => r.anchor.startsWith(`${title} ./`)))
|
|
@@ -377,7 +394,10 @@ const walkthrough = async (
|
|
|
377
394
|
changesSince(base).some((c) => isCode(c.path)),
|
|
378
395
|
)
|
|
379
396
|
// Ranked even when it marks risks: the review written after their fix reaches the human with its own.
|
|
380
|
-
const full =
|
|
397
|
+
const full = sectionedReview(
|
|
398
|
+
await ranked(review, t.withWalkthrough(review, report), new Set(notes.map((n) => n.title))),
|
|
399
|
+
new Set(reviewChunks(review).map((c) => c.title)),
|
|
400
|
+
)
|
|
381
401
|
if (full !== written) {
|
|
382
402
|
await run("review.carrying", writeScript(REVIEW, full), {
|
|
383
403
|
label: "Ordering the review and adding the quality lap's findings",
|
|
@@ -4,6 +4,8 @@ import { tmpdir } from "node:os"
|
|
|
4
4
|
import { join } from "node:path"
|
|
5
5
|
import { afterEach, describe, expect, it } from "vitest"
|
|
6
6
|
import {
|
|
7
|
+
designImpactPrompt,
|
|
8
|
+
prototypeFixingPrompt,
|
|
7
9
|
buildReviewTriageMessage,
|
|
8
10
|
buildReviewReviewingPrompt,
|
|
9
11
|
agentWithSkills,
|
|
@@ -19,9 +21,12 @@ import {
|
|
|
19
21
|
buildReviewCollectingPrompt,
|
|
20
22
|
designGateAnswerMessage,
|
|
21
23
|
designTriagePrompt,
|
|
24
|
+
normaliseCriteriaScript,
|
|
25
|
+
prototypeTestingPrompt,
|
|
22
26
|
implementBuildingPrompt,
|
|
23
27
|
implementFixPrompt,
|
|
24
28
|
naivePassPrompt,
|
|
29
|
+
slicingPrompt,
|
|
25
30
|
refactorBuildingPrompt,
|
|
26
31
|
refactorDroppedRefusal,
|
|
27
32
|
refactorFixPrompt,
|
|
@@ -170,6 +175,37 @@ describe("the risk tier prompt and gate line", () => {
|
|
|
170
175
|
})
|
|
171
176
|
})
|
|
172
177
|
|
|
178
|
+
describe("the (no hands-on) tag", () => {
|
|
179
|
+
it("is explained at triage and at the design gate", () => {
|
|
180
|
+
expect(renderText(() => designTriagePrompt("base"))).toContain(
|
|
181
|
+
"`- AC-<n> (no hands-on): <criterion>`",
|
|
182
|
+
)
|
|
183
|
+
expect(renderText(() => designGateAnswerMessage())).toContain(
|
|
184
|
+
"add or remove `(no hands-on)` between an `AC-<n>` and its colon",
|
|
185
|
+
)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it("names the tagged criteria the testing turn skips, and only when there are any", () => {
|
|
189
|
+
expect(renderText(() => prototypeTestingPrompt(undefined, ["AC-2", "AC-3"], false))).toContain(
|
|
190
|
+
"- Skip AC-2, AC-3: no person can observe them",
|
|
191
|
+
)
|
|
192
|
+
expect(renderText(() => prototypeTestingPrompt(undefined, [], false))).not.toContain("Skip")
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it("is kept when the stated criteria are recorded", () => {
|
|
196
|
+
const script = normaliseCriteriaScript({
|
|
197
|
+
path: "TODO.md",
|
|
198
|
+
commit: "abc",
|
|
199
|
+
criteria: [
|
|
200
|
+
{ id: "AC-1", text: "a" },
|
|
201
|
+
{ id: "AC-2", text: "b", noHandsOn: true },
|
|
202
|
+
],
|
|
203
|
+
})
|
|
204
|
+
expect(script).toContain("'- AC-1: a'")
|
|
205
|
+
expect(script).toContain("'- AC-2 (no hands-on): b'")
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
173
209
|
describe("the shared open-question instruction", () => {
|
|
174
210
|
it("leaves the option count to the agent, floored at two real options", () => {
|
|
175
211
|
const prompt = renderText(() => designTriagePrompt("base"))
|
|
@@ -889,6 +925,29 @@ describe("buildReviewReviewingPrompt scenarios", () => {
|
|
|
889
925
|
})
|
|
890
926
|
})
|
|
891
927
|
|
|
928
|
+
describe("Impact in prompts", () => {
|
|
929
|
+
it("[AC-4] the slicing prompt slices along the units Impact names", () => {
|
|
930
|
+
const prompt = renderText(() => slicingPrompt(8))
|
|
931
|
+
expect(prompt).toContain("## Impact")
|
|
932
|
+
expect(prompt).toMatch(/slice\s+along\s+the\s+units\s+it\s+names/)
|
|
933
|
+
})
|
|
934
|
+
|
|
935
|
+
it.each([
|
|
936
|
+
["naive", () => naivePassPrompt()],
|
|
937
|
+
["implement", () => implementBuildingPrompt(undefined)],
|
|
938
|
+
["prototype fixing", () => prototypeFixingPrompt(undefined)],
|
|
939
|
+
])("[AC-5] [AC-7] the %s prompt gives Impact as hints, not allowed files", (_n, prompt) => {
|
|
940
|
+
const p = renderText(prompt).replace(/\s+/g, " ")
|
|
941
|
+
expect(p).toContain("`## Impact`")
|
|
942
|
+
expect(p).toContain("not a list of allowed files")
|
|
943
|
+
})
|
|
944
|
+
|
|
945
|
+
it("the prototype testing prompt, which changes no code, carries no Impact hints", () => {
|
|
946
|
+
const p = renderText(() => prototypeTestingPrompt(undefined, [], false))
|
|
947
|
+
expect(p).not.toContain("not a list of allowed files")
|
|
948
|
+
})
|
|
949
|
+
})
|
|
950
|
+
|
|
892
951
|
describe("the seam phase's texts", () => {
|
|
893
952
|
const rejection = { strays: ["src/flows/helpers.ts", "src/flows/runtime.ts"], p: 0.85 }
|
|
894
953
|
|
|
@@ -1281,7 +1340,7 @@ describe("composedReview", () => {
|
|
|
1281
1340
|
})
|
|
1282
1341
|
|
|
1283
1342
|
it("asks about each undecided standard in a note that blocks sign-off", () => {
|
|
1284
|
-
expect(withConflict).toMatch(/^## Standard conflict [0-9a-f]{8}$/m)
|
|
1343
|
+
expect(withConflict).toMatch(/^## Findings\n\n### Standard conflict [0-9a-f]{8}$/m)
|
|
1285
1344
|
expect(withConflict).toContain("- [ ] keep the new one (drop the old)")
|
|
1286
1345
|
expect(unansweredNotes(withConflict)).toHaveLength(1)
|
|
1287
1346
|
})
|
|
@@ -1317,8 +1376,8 @@ describe("composedReview", () => {
|
|
|
1317
1376
|
})
|
|
1318
1377
|
expect(steeringFormatFor("review")!.validate(doc)).toEqual([])
|
|
1319
1378
|
expect(doc).toContain("# Review: 0123456\n\n<!-- base: " + BASE + " -->")
|
|
1320
|
-
expect(doc).toContain("## same\n\none")
|
|
1321
|
-
expect(doc).toContain("
|
|
1379
|
+
expect(doc).toContain("## Findings\n\n### same\n\none")
|
|
1380
|
+
expect(doc).toContain("### same (2)\n\n_The fixer left this unresolved._\n\ntwo")
|
|
1322
1381
|
expect(doc).toContain("## Fixed automatically\n\n- f (fedcba9)")
|
|
1323
1382
|
expect(doc).toContain("## Dropped as noise\n\n- n (p=0.8)")
|
|
1324
1383
|
})
|
|
@@ -1340,12 +1399,13 @@ describe("composedReview", () => {
|
|
|
1340
1399
|
fixed: [{ title: "f", commit: "fedcba9876543210" }],
|
|
1341
1400
|
noise: [],
|
|
1342
1401
|
})
|
|
1343
|
-
const titles = doc.split("\n").filter((l) => l
|
|
1402
|
+
const titles = doc.split("\n").filter((l) => /^#{2,3} /.test(l))
|
|
1344
1403
|
expect(titles).toEqual([
|
|
1345
|
-
"##
|
|
1346
|
-
"
|
|
1347
|
-
"
|
|
1348
|
-
"
|
|
1404
|
+
"## Findings",
|
|
1405
|
+
"### a",
|
|
1406
|
+
"### a (2)",
|
|
1407
|
+
"### a (3)",
|
|
1408
|
+
"### Fixed automatically (2)",
|
|
1349
1409
|
"## Fixed automatically",
|
|
1350
1410
|
])
|
|
1351
1411
|
})
|
|
@@ -1376,10 +1436,10 @@ describe("composedReview", () => {
|
|
|
1376
1436
|
noise: [],
|
|
1377
1437
|
})
|
|
1378
1438
|
expect(doc).toContain(
|
|
1379
|
-
"
|
|
1439
|
+
"### a\n\n- ends in a list\n\nTick exactly one:\n\n- [ ] fix as suggested\n- [ ] won't fix\n- [ ] _your answer_\n\n## Fixed automatically",
|
|
1380
1440
|
)
|
|
1381
1441
|
expect(doc.match(/fix as suggested/g)).toHaveLength(1)
|
|
1382
|
-
expect(unansweredNotes(doc)).toEqual([{ title: "a", line:
|
|
1442
|
+
expect(unansweredNotes(doc)).toEqual([{ title: "a", line: 7, reason: "tick exactly one box" }])
|
|
1383
1443
|
})
|
|
1384
1444
|
|
|
1385
1445
|
it("leaves out the notes already answered", () => {
|
|
@@ -1392,7 +1452,7 @@ describe("composedReview", () => {
|
|
|
1392
1452
|
noise: [],
|
|
1393
1453
|
answered: new Set(["a"]),
|
|
1394
1454
|
})
|
|
1395
|
-
expect(doc).not.toContain("
|
|
1455
|
+
expect(doc).not.toContain("# a\n")
|
|
1396
1456
|
expect(doc).toContain("## a (2)\n\n2")
|
|
1397
1457
|
})
|
|
1398
1458
|
})
|
|
@@ -1607,3 +1667,17 @@ describe("rankedReview", () => {
|
|
|
1607
1667
|
expect(unranked(unranked(review))).toBe(unranked(review))
|
|
1608
1668
|
})
|
|
1609
1669
|
})
|
|
1670
|
+
|
|
1671
|
+
describe("impact prompts", () => {
|
|
1672
|
+
it.each([
|
|
1673
|
+
["triage", () => renderText(() => designTriagePrompt("abc123"))],
|
|
1674
|
+
["design.impact", () => renderText(() => designImpactPrompt())],
|
|
1675
|
+
])("[AC-2] the %s prompt asks for a `## Impact` section", (_n, prompt) => {
|
|
1676
|
+
expect(prompt()).toContain("`## Impact`")
|
|
1677
|
+
})
|
|
1678
|
+
|
|
1679
|
+
it("[AC-3] the triage prompt names where a return lap reads the human's Impact edit", () => {
|
|
1680
|
+
const p = renderText(() => designTriagePrompt("abc123")).replace(/\s+/g, " ")
|
|
1681
|
+
expect(p).toContain("`git log -1 -p -- .gtd/REQUIREMENTS.md`")
|
|
1682
|
+
})
|
|
1683
|
+
})
|