@pmelab/gtd 49.0.0 → 50.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 +31 -26
- package/dist/chunks/{reserved-CYZ1bSBy.mjs → reserved-CdOzKFVX.mjs} +59 -2
- package/dist/chunks/{review-BewyJflS.mjs → review-BChXEl2Y.mjs} +84 -20
- package/dist/chunks/{steering-BXwjmocB.mjs → steering-DtUVUz9l.mjs} +44 -14
- package/dist/chunks/{tail-C1Oi2YEy.mjs → tail-DRM2eC1z.mjs} +1 -1
- package/dist/gtd.bundle.mjs +22 -5
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +1 -1
- package/dist/web/main.js +27 -3
- package/dist/workflow-kit.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +3 -3
- 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 +181 -30
- package/package.json +1 -1
- package/src/steering/index.ts +1 -0
- package/src/steering/review.test.ts +60 -2
- package/src/steering/review.ts +66 -9
- package/src/workflows/feature/lens-review.feature +16 -4
- package/src/workflows/feature/review-answers.feature +8 -1
- package/src/workflows/feature/review-gate.feature +20 -3
- package/src/workflows/feature/reviewer-walkthrough.feature +14 -3
- package/src/workflows/feature/severity-budget.feature +111 -0
- package/src/workflows/feature/severity-sorted-review.feature +197 -0
- package/src/workflows/feature/slice-findings.feature +14 -2
- package/src/workflows/feature/standards.feature +4 -0
- package/src/workflows/shared/review.ts +124 -18
- package/src/workflows/shared/text.test.ts +90 -0
- package/src/workflows/shared/text.ts +77 -1
package/src/steering/review.ts
CHANGED
|
@@ -77,6 +77,8 @@ interface Changeset {
|
|
|
77
77
|
readonly headingLine: number
|
|
78
78
|
/** The chunk's answer boxes, when gtd gave it some. */
|
|
79
79
|
readonly answers?: readonly AnswerOption[]
|
|
80
|
+
/** The tier and reason gtd ranked the chunk with, from its leading severity line. */
|
|
81
|
+
readonly severity?: { readonly label: string; readonly line: number }
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
interface AnswerOption {
|
|
@@ -89,6 +91,10 @@ export const ANSWER_LEAD = "Tick exactly one:"
|
|
|
89
91
|
export const ANSWER_FIX = "fix as suggested"
|
|
90
92
|
export const ANSWER_WONT_FIX = "won't fix"
|
|
91
93
|
|
|
94
|
+
/** A chunk's leading `_critical: security_` paragraph: the tier gtd ranked it with, and why. */
|
|
95
|
+
const SEVERITY_LINE_RE = /^_((?:critical|should|could)(?::[^_\n]*)?)_$/
|
|
96
|
+
export const severityLine = (label: string): string => `_${label}_`
|
|
97
|
+
|
|
92
98
|
interface ReviewDoc {
|
|
93
99
|
readonly shortHash?: string
|
|
94
100
|
readonly fullHash?: string
|
|
@@ -521,6 +527,26 @@ const answerListOf = (
|
|
|
521
527
|
return { lead, list, options }
|
|
522
528
|
}
|
|
523
529
|
|
|
530
|
+
/** A leading severity line is the chunk's tier, never its prose. */
|
|
531
|
+
const splitSeverity = (
|
|
532
|
+
content: string,
|
|
533
|
+
run: readonly RootContent[],
|
|
534
|
+
): {
|
|
535
|
+
readonly severity?: { readonly label: string; readonly line: number } | undefined
|
|
536
|
+
readonly leadingRun: readonly RootContent[]
|
|
537
|
+
} => {
|
|
538
|
+
const first = run[0]
|
|
539
|
+
const label =
|
|
540
|
+
first?.type === "paragraph"
|
|
541
|
+
? SEVERITY_LINE_RE.exec(sourceText(content, first).trim())?.[1]
|
|
542
|
+
: undefined
|
|
543
|
+
if (label === undefined) return { leadingRun: run }
|
|
544
|
+
return {
|
|
545
|
+
severity: { label, line: toLspPosition(first!.position!.start).line },
|
|
546
|
+
leadingRun: run.slice(1),
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
524
550
|
/** Splits one chunk's body nodes into its file pointers (hunk pointers are task items, collected recursively at ANY nesting depth via `taskItems` — a nested hunk is the same kind of hunk as a top-level one) and description prose. The description is the chunk's own leading run and never a node that contains a hunk pointer: it stops at the first node whose `taskItems` include a real pointer (at any depth — a blockquote wrapping a pointer list stops it just as a top-level list would). Every node kind in what's left before that point contributes — a `footnoteDefinition` is the one exception, excluded by `blockNodesOfRun` itself (it's a note, never document content). */
|
|
525
551
|
const parseChunkBody = (
|
|
526
552
|
content: string,
|
|
@@ -534,6 +560,7 @@ const parseChunkBody = (
|
|
|
534
560
|
readonly files: readonly ReviewFile[]
|
|
535
561
|
readonly errors: readonly SteeringFinding[]
|
|
536
562
|
readonly answers?: readonly AnswerOption[]
|
|
563
|
+
readonly severity?: { readonly label: string; readonly line: number } | undefined
|
|
537
564
|
/** Whether a task box outside the answer list exists. */
|
|
538
565
|
readonly boxed: boolean
|
|
539
566
|
} => {
|
|
@@ -543,9 +570,10 @@ const parseChunkBody = (
|
|
|
543
570
|
const firstPointerIndex = body.findIndex(
|
|
544
571
|
(n) => !isAnswer(n) && taskItems(n).some((item) => hasPointerToken(content, item)),
|
|
545
572
|
)
|
|
546
|
-
const
|
|
573
|
+
const run = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter(
|
|
547
574
|
(n) => !isAnswer(n),
|
|
548
575
|
)
|
|
576
|
+
const { severity, leadingRun } = splitSeverity(content, run)
|
|
549
577
|
const descriptionNodes = blockNodesOfRun(content, leadingRun)
|
|
550
578
|
const descriptionInline = collapseImages(blockRunInline(content, leadingRun))
|
|
551
579
|
const description = leadingRun
|
|
@@ -569,6 +597,7 @@ const parseChunkBody = (
|
|
|
569
597
|
files,
|
|
570
598
|
errors,
|
|
571
599
|
...(answer === undefined ? {} : { answers: answer.options }),
|
|
600
|
+
...(severity === undefined ? {} : { severity }),
|
|
572
601
|
boxed: body.some((n) => !isAnswer(n) && taskItems(n).length > 0),
|
|
573
602
|
}
|
|
574
603
|
}
|
|
@@ -618,6 +647,7 @@ const parseChangesets = (
|
|
|
618
647
|
descriptionInline,
|
|
619
648
|
files,
|
|
620
649
|
answers,
|
|
650
|
+
severity,
|
|
621
651
|
boxed,
|
|
622
652
|
errors: bodyErrors,
|
|
623
653
|
} = parseChunkBody(content, lines, title, body)
|
|
@@ -638,6 +668,7 @@ const parseChangesets = (
|
|
|
638
668
|
files,
|
|
639
669
|
headingLine,
|
|
640
670
|
...(answers === undefined ? {} : { answers }),
|
|
671
|
+
...(severity === undefined ? {} : { severity }),
|
|
641
672
|
})
|
|
642
673
|
}
|
|
643
674
|
if (changesets.length === 0) errors.push({ message: "REVIEW.md has no '##' chunks" })
|
|
@@ -997,6 +1028,7 @@ const reviewView = (content: string): SteeringView => {
|
|
|
997
1028
|
detail: chunk.description,
|
|
998
1029
|
detailInline: chunk.descriptionInline,
|
|
999
1030
|
body: chunk.descriptionNodes,
|
|
1031
|
+
...(chunk.severity === undefined ? {} : { status: chunk.severity.label }),
|
|
1000
1032
|
anchor: { kind: "chunk", index: chunkIndex },
|
|
1001
1033
|
...noteAt(chunk.headingLine),
|
|
1002
1034
|
children: chunk.files.map((file, index) => ({
|
|
@@ -1177,11 +1209,22 @@ const pointerLabel = (f: ReviewFile): string =>
|
|
|
1177
1209
|
|
|
1178
1210
|
type Found = { line: number; note: Omit<ReviewNote, "id" | "forced"> }
|
|
1179
1211
|
|
|
1180
|
-
//
|
|
1181
|
-
//
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1212
|
+
// Rewording a tier disputes it, so a changed tier joins the note — after the
|
|
1213
|
+
// prose, so a leading `!` still forces; deleting one asks nothing.
|
|
1214
|
+
const chunkProse = (chunk: Changeset | undefined, retiered: boolean): string =>
|
|
1215
|
+
[
|
|
1216
|
+
chunk?.description ?? "",
|
|
1217
|
+
retiered && chunk?.severity !== undefined ? severityLine(chunk.severity.label) : "",
|
|
1218
|
+
]
|
|
1219
|
+
.filter((p) => p !== "")
|
|
1220
|
+
.join("\n\n")
|
|
1221
|
+
|
|
1222
|
+
const chunkNote = (chunk: Changeset, prior: Changeset | undefined): Found[] => {
|
|
1223
|
+
const retiered = chunk.severity !== undefined && chunk.severity.label !== prior?.severity?.label
|
|
1224
|
+
// A chunk added with its own answer boxes (a driver's imported note) is
|
|
1225
|
+
// answered by its tick, never judged as prose as well.
|
|
1226
|
+
return chunkProse(chunk, retiered) === chunkProse(prior, retiered) ||
|
|
1227
|
+
(prior === undefined && chunk.answers !== undefined)
|
|
1185
1228
|
? []
|
|
1186
1229
|
: [
|
|
1187
1230
|
{
|
|
@@ -1189,11 +1232,12 @@ const chunkNote = (chunk: Changeset, prior: Changeset | undefined): Found[] =>
|
|
|
1189
1232
|
note: {
|
|
1190
1233
|
kind: "chunk",
|
|
1191
1234
|
anchor: chunk.title,
|
|
1192
|
-
before: prior
|
|
1193
|
-
text: chunk
|
|
1235
|
+
before: chunkProse(prior, retiered),
|
|
1236
|
+
text: chunkProse(chunk, retiered),
|
|
1194
1237
|
},
|
|
1195
1238
|
},
|
|
1196
1239
|
]
|
|
1240
|
+
}
|
|
1197
1241
|
|
|
1198
1242
|
const pointerNotes = (chunk: Changeset, prior: Changeset | undefined): Found[] =>
|
|
1199
1243
|
chunk.files.flatMap((file): Found[] => {
|
|
@@ -1342,9 +1386,22 @@ export const reviewDecisions = (before: string, after: string): readonly ReviewD
|
|
|
1342
1386
|
/** Each `##` chunk in document order: its title, 0-based heading line and how many hunk pointers it holds. */
|
|
1343
1387
|
export const reviewChunks = (
|
|
1344
1388
|
content: string,
|
|
1345
|
-
): readonly {
|
|
1389
|
+
): readonly {
|
|
1390
|
+
title: string
|
|
1391
|
+
line: number
|
|
1392
|
+
pointers: number
|
|
1393
|
+
/** 0-based line of the chunk's severity line, when it has one. */
|
|
1394
|
+
severityLine?: number
|
|
1395
|
+
files: readonly { path: string; line?: number; rangeEnd?: number }[]
|
|
1396
|
+
}[] =>
|
|
1346
1397
|
parseReviewDoc(content).changesets.map((c) => ({
|
|
1347
1398
|
title: c.title,
|
|
1348
1399
|
line: c.headingLine,
|
|
1349
1400
|
pointers: c.files.length,
|
|
1401
|
+
...(c.severity === undefined ? {} : { severityLine: c.severity.line }),
|
|
1402
|
+
files: c.files.map((f) => ({
|
|
1403
|
+
path: f.path,
|
|
1404
|
+
...(f.line === undefined ? {} : { line: f.line }),
|
|
1405
|
+
...(f.rangeEnd === undefined ? {} : { rangeEnd: f.rangeEnd }),
|
|
1406
|
+
})),
|
|
1350
1407
|
}))
|
|
@@ -75,6 +75,10 @@ Feature: the quality lap's findings are triaged before the review
|
|
|
75
75
|
And step "build.quality.sorting" runs its script
|
|
76
76
|
And step "build.quality.closing" runs its script
|
|
77
77
|
And step "build.review.carrying" runs its script
|
|
78
|
+
And judge step "build.review.ranking" answers:
|
|
79
|
+
"""
|
|
80
|
+
[]
|
|
81
|
+
"""
|
|
78
82
|
|
|
79
83
|
Scenario: a mechanical finding is fixed and listed, noise dropped and listed, a design one noted
|
|
80
84
|
Given judge step "build.quality.triage" answers:
|
|
@@ -110,6 +114,8 @@ Feature: the quality lap's findings are triaged before the review
|
|
|
110
114
|
"""
|
|
111
115
|
## AC-1 ignores floating point
|
|
112
116
|
|
|
117
|
+
_critical: judge unsure_
|
|
118
|
+
|
|
113
119
|
Should subtract round? The criterion does not say.
|
|
114
120
|
|
|
115
121
|
In the code:
|
|
@@ -147,7 +153,8 @@ Feature: the quality lap's findings are triaged before the review
|
|
|
147
153
|
gtd(check): build.quality.sorting → build.fix.quality.fixing
|
|
148
154
|
gtd(agent): build.fix.quality.fixing → build.health.check
|
|
149
155
|
gtd(check): build.health.check → build.review.reviewing
|
|
150
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
156
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
157
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
151
158
|
gtd(check): build.review.carrying → build.review.await-review
|
|
152
159
|
"""
|
|
153
160
|
|
|
@@ -189,7 +196,8 @@ Feature: the quality lap's findings are triaged before the review
|
|
|
189
196
|
"""
|
|
190
197
|
gtd(judge): build.quality.triage → build.quality.sorting
|
|
191
198
|
gtd(check): build.quality.sorting → build.review.reviewing
|
|
192
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
199
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
200
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
193
201
|
gtd(check): build.review.carrying → build.review.await-review
|
|
194
202
|
"""
|
|
195
203
|
|
|
@@ -228,6 +236,8 @@ Feature: the quality lap's findings are triaged before the review
|
|
|
228
236
|
"""
|
|
229
237
|
## add could be a method
|
|
230
238
|
|
|
239
|
+
_critical: judge unsure_
|
|
240
|
+
|
|
231
241
|
_The fixer left this unresolved._
|
|
232
242
|
|
|
233
243
|
A class would read better.
|
|
@@ -242,7 +252,8 @@ Feature: the quality lap's findings are triaged before the review
|
|
|
242
252
|
gtd(agent): build.fix.quality.fixing → build.health.check
|
|
243
253
|
gtd(check): build.health.check → build.quality.closing
|
|
244
254
|
gtd(check): build.quality.closing → build.review.reviewing
|
|
245
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
255
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
256
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
246
257
|
gtd(check): build.review.carrying → build.review.await-review
|
|
247
258
|
"""
|
|
248
259
|
|
|
@@ -344,7 +355,8 @@ Feature: the quality lap's findings are triaged before the review
|
|
|
344
355
|
And ".gtd/REVIEW.md" does not exist
|
|
345
356
|
And the commit subjects end with:
|
|
346
357
|
"""
|
|
347
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
358
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
359
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
348
360
|
gtd(check): build.review.carrying → build.review.await-review
|
|
349
361
|
gtd(human): build.review.await-review → build.review.closing
|
|
350
362
|
gtd(check): build.review.closing → idle
|
|
@@ -109,6 +109,10 @@ Feature: every note in the review is answered with one tick
|
|
|
109
109
|
- [ ] ./src/calc.ts#2-2 — now rounds the difference
|
|
110
110
|
"""
|
|
111
111
|
And step "build.review.carrying" runs its script
|
|
112
|
+
And judge step "build.review.ranking" answers:
|
|
113
|
+
"""
|
|
114
|
+
[]
|
|
115
|
+
"""
|
|
112
116
|
And judge step "build.quality.triage" answers:
|
|
113
117
|
"""
|
|
114
118
|
[{"id":"Q1","answer":"noise","p":0.9},{"id":"Q2","answer":"noise","p":0.9},{"id":"Q3","answer":"design","p":0.9}]
|
|
@@ -122,6 +126,8 @@ Feature: every note in the review is answered with one tick
|
|
|
122
126
|
"""
|
|
123
127
|
## AC-1 ignores floating point
|
|
124
128
|
|
|
129
|
+
_critical: judge unsure_
|
|
130
|
+
|
|
125
131
|
Should subtract round? The criterion does not say.
|
|
126
132
|
|
|
127
133
|
In the code:
|
|
@@ -215,7 +221,8 @@ Feature: every note in the review is answered with one tick
|
|
|
215
221
|
gtd(agent): build.review.fix.nits.fixing → build.health.check
|
|
216
222
|
gtd(check): build.health.check → build.review.closing
|
|
217
223
|
gtd(check): build.review.closing → build.review.reviewing
|
|
218
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
224
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
225
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
219
226
|
gtd(check): build.review.carrying → build.review.await-review
|
|
220
227
|
"""
|
|
221
228
|
And ".gtd/REVIEW.md" does not contain "AC-1 ignores floating point"
|
|
@@ -69,6 +69,11 @@ Feature: a feature process ends at the human review
|
|
|
69
69
|
new subtract function
|
|
70
70
|
"""
|
|
71
71
|
And step "build.review.closing" runs its script
|
|
72
|
+
And step "build.review.carrying" runs its script
|
|
73
|
+
And judge step "build.review.ranking" answers:
|
|
74
|
+
"""
|
|
75
|
+
[]
|
|
76
|
+
"""
|
|
72
77
|
|
|
73
78
|
Scenario: a feature process ends at the review gate
|
|
74
79
|
When the process is driven
|
|
@@ -79,7 +84,9 @@ Feature: a feature process ends at the human review
|
|
|
79
84
|
"""
|
|
80
85
|
gtd(judge): implement.coverage → implement.closing
|
|
81
86
|
gtd(check): implement.closing → build.review.reviewing
|
|
82
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
87
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
88
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
89
|
+
gtd(check): build.review.carrying → build.review.await-review
|
|
83
90
|
"""
|
|
84
91
|
|
|
85
92
|
Scenario: a sign-off ends the process at idle
|
|
@@ -93,6 +100,8 @@ Feature: a feature process ends at the human review
|
|
|
93
100
|
|
|
94
101
|
## calc
|
|
95
102
|
|
|
103
|
+
_critical: judge unsure_
|
|
104
|
+
|
|
96
105
|
- [x] ./src/calc.ts#2-2
|
|
97
106
|
new subtract function
|
|
98
107
|
"""
|
|
@@ -102,7 +111,9 @@ Feature: a feature process ends at the human review
|
|
|
102
111
|
And ".gtd/REVIEW.md" does not exist
|
|
103
112
|
And the commit subjects end with:
|
|
104
113
|
"""
|
|
105
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
114
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
115
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
116
|
+
gtd(check): build.review.carrying → build.review.await-review
|
|
106
117
|
gtd(human): build.review.await-review → build.review.closing
|
|
107
118
|
gtd(check): build.review.closing → idle
|
|
108
119
|
"""
|
|
@@ -146,6 +157,8 @@ Feature: a feature process ends at the human review
|
|
|
146
157
|
|
|
147
158
|
## calc
|
|
148
159
|
|
|
160
|
+
_critical: judge unsure_
|
|
161
|
+
|
|
149
162
|
- [ ] ./src/calc.ts#2-2
|
|
150
163
|
new subtract function[^why]
|
|
151
164
|
|
|
@@ -164,6 +177,8 @@ Feature: a feature process ends at the human review
|
|
|
164
177
|
|
|
165
178
|
## calc
|
|
166
179
|
|
|
180
|
+
_critical: judge unsure_
|
|
181
|
+
|
|
167
182
|
- [ ] ./src/calc.ts#2-2
|
|
168
183
|
new subtract function
|
|
169
184
|
"""
|
|
@@ -215,7 +230,9 @@ Feature: a feature process ends at the human review
|
|
|
215
230
|
gtd(check): implement.health.check → implement.coverage
|
|
216
231
|
gtd(judge): implement.coverage → implement.closing
|
|
217
232
|
gtd(check): implement.closing → build.review.reviewing
|
|
218
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
233
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
234
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
235
|
+
gtd(check): build.review.carrying → build.review.await-review
|
|
219
236
|
"""
|
|
220
237
|
|
|
221
238
|
Scenario: a design numbering new behaviour at or below an earlier ID is refused
|
|
@@ -67,6 +67,10 @@ Feature: a reviewer walks the human through the feature's code
|
|
|
67
67
|
And step "build.quality.sorting" runs its script
|
|
68
68
|
And step "build.quality.closing" runs its script
|
|
69
69
|
And step "build.review.carrying" runs its script
|
|
70
|
+
And judge step "build.review.ranking" answers:
|
|
71
|
+
"""
|
|
72
|
+
[]
|
|
73
|
+
"""
|
|
70
74
|
|
|
71
75
|
Scenario: the finding gets its text and answer boxes under the reviewer's pointers
|
|
72
76
|
Given step "build.review.reviewing" is prompted with "- `## AC-1 ignores floating point`:"
|
|
@@ -93,12 +97,16 @@ Feature: a reviewer walks the human through the feature's code
|
|
|
93
97
|
"""
|
|
94
98
|
## Subtract
|
|
95
99
|
|
|
100
|
+
_critical: judge unsure_
|
|
101
|
+
|
|
96
102
|
The new export.
|
|
97
103
|
|
|
98
104
|
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
99
105
|
|
|
100
106
|
## AC-1 ignores floating point
|
|
101
107
|
|
|
108
|
+
_critical: judge unsure_
|
|
109
|
+
|
|
102
110
|
Should subtract round? The criterion does not say.
|
|
103
111
|
|
|
104
112
|
In the code:
|
|
@@ -114,7 +122,8 @@ Feature: a reviewer walks the human through the feature's code
|
|
|
114
122
|
And no commit body contains "build.review.composing"
|
|
115
123
|
And the commit subjects end with:
|
|
116
124
|
"""
|
|
117
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
125
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
126
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
118
127
|
gtd(check): build.review.carrying → build.review.await-review
|
|
119
128
|
"""
|
|
120
129
|
|
|
@@ -169,11 +178,13 @@ Feature: a reviewer walks the human through the feature's code
|
|
|
169
178
|
Then the process rests at "build.review.await-review"
|
|
170
179
|
And the commit subjects end with:
|
|
171
180
|
"""
|
|
172
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
181
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
182
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
173
183
|
gtd(check): build.review.carrying → build.review.fix.risks.fixing
|
|
174
184
|
gtd(agent): build.review.fix.risks.fixing → build.health.check
|
|
175
185
|
gtd(check): build.health.check → build.review.reviewing
|
|
176
|
-
gtd(agent): build.review.reviewing → build.review.
|
|
186
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
187
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
177
188
|
gtd(check): build.review.carrying → build.review.await-review
|
|
178
189
|
"""
|
|
179
190
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
@inmem
|
|
2
|
+
Feature: the review's ranking is batched to the judge's budget
|
|
3
|
+
|
|
4
|
+
`build.review.ranking` puts as many chunks in one judgment as the budget's
|
|
5
|
+
even share still holds whole; the rest go to the next. A chunk too big to
|
|
6
|
+
fit even alone is cut, and a cut chunk is critical.
|
|
7
|
+
|
|
8
|
+
Background:
|
|
9
|
+
Given the shipped workflows, as gtd install scaffolds them
|
|
10
|
+
And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
|
|
11
|
+
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
12
|
+
And an environment variable "GTD_JUDGEBUDGETBYTES" set to "150"
|
|
13
|
+
And a file "src/calc.ts" with:
|
|
14
|
+
"""
|
|
15
|
+
export const add = (a: number, b: number) => a + b
|
|
16
|
+
"""
|
|
17
|
+
And the working tree is committed as "feat: add calculator"
|
|
18
|
+
And a file "TODO.md" with:
|
|
19
|
+
"""
|
|
20
|
+
# Add subtract
|
|
21
|
+
|
|
22
|
+
subtract(a, b) returns the difference.
|
|
23
|
+
"""
|
|
24
|
+
And step "unwind" changes nothing
|
|
25
|
+
And step "start-gate.check" changes nothing
|
|
26
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
27
|
+
"""
|
|
28
|
+
# Requirements
|
|
29
|
+
|
|
30
|
+
## Acceptance Criteria
|
|
31
|
+
|
|
32
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
33
|
+
"""
|
|
34
|
+
And step "design.gate.answer" changes nothing
|
|
35
|
+
And step "seam.naive" writes "src/calc.test.ts" with:
|
|
36
|
+
"""
|
|
37
|
+
it("[AC-1] subtracts", () => {})
|
|
38
|
+
"""
|
|
39
|
+
And step "seam.naive" writes "src/calc.ts" with:
|
|
40
|
+
"""
|
|
41
|
+
export const add = (a: number, b: number) => a + b
|
|
42
|
+
export const subtract = (a: number, b: number) => a - b
|
|
43
|
+
"""
|
|
44
|
+
And step "seam.check" changes nothing
|
|
45
|
+
And step "implement.applying" runs its script
|
|
46
|
+
And step "implement.health.check" changes nothing
|
|
47
|
+
And step "implement.closing" runs its script
|
|
48
|
+
And judge step "implement.coverage" answers:
|
|
49
|
+
"""
|
|
50
|
+
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
51
|
+
"""
|
|
52
|
+
And step "build.quality.correctness.reviewing" writes ".gtd/QUALITY.md" with:
|
|
53
|
+
"""
|
|
54
|
+
# Quality
|
|
55
|
+
|
|
56
|
+
## AC-1 ignores floating point
|
|
57
|
+
|
|
58
|
+
Should subtract round? The criterion does not say.
|
|
59
|
+
"""
|
|
60
|
+
And judge step "build.quality.triage" answers:
|
|
61
|
+
"""
|
|
62
|
+
[{"id":"Q1","answer":"design","p":0.9}]
|
|
63
|
+
"""
|
|
64
|
+
And step "build.quality.sorting" runs its script
|
|
65
|
+
And step "build.quality.closing" runs its script
|
|
66
|
+
And step "build.review.carrying" runs its script
|
|
67
|
+
|
|
68
|
+
Scenario: a chunk the budget cuts is listed as critical, the others judged in batches of their own
|
|
69
|
+
Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
70
|
+
"""
|
|
71
|
+
# Review: abc1234
|
|
72
|
+
|
|
73
|
+
<!-- base: 0000000 -->
|
|
74
|
+
|
|
75
|
+
## Subtract
|
|
76
|
+
|
|
77
|
+
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
78
|
+
|
|
79
|
+
## AC-1 ignores floating point
|
|
80
|
+
|
|
81
|
+
- [ ] ./src/calc.ts#2-2 — returns the raw difference
|
|
82
|
+
"""
|
|
83
|
+
And judge step "build.review.ranking", when its questions contain "C1.security", answers:
|
|
84
|
+
"""
|
|
85
|
+
[{"id":"C1.security","answer":"no","p":0.9},{"id":"C1.reversibility","answer":"two-way","p":0.9},{"id":"C1.blastRadius","answer":"local","p":0.9},{"id":"C1.newConcept","answer":"no","p":0.9}]
|
|
86
|
+
"""
|
|
87
|
+
And judge step "build.review.ranking", when its questions contain "C2.security", answers:
|
|
88
|
+
"""
|
|
89
|
+
[{"id":"C2.security","answer":"no","p":0.9},{"id":"C2.reversibility","answer":"two-way","p":0.9},{"id":"C2.blastRadius","answer":"local","p":0.9},{"id":"C2.newConcept","answer":"no","p":0.9}]
|
|
90
|
+
"""
|
|
91
|
+
When the process is driven
|
|
92
|
+
Then the process rests at "build.review.await-review"
|
|
93
|
+
And ".gtd/REVIEW.md" contains:
|
|
94
|
+
"""
|
|
95
|
+
## AC-1 ignores floating point
|
|
96
|
+
|
|
97
|
+
_critical: judge unsure_
|
|
98
|
+
"""
|
|
99
|
+
And ".gtd/REVIEW.md" contains:
|
|
100
|
+
"""
|
|
101
|
+
## Subtract
|
|
102
|
+
|
|
103
|
+
_could_
|
|
104
|
+
"""
|
|
105
|
+
And the commit subjects end with:
|
|
106
|
+
"""
|
|
107
|
+
gtd(agent): build.review.reviewing → build.review.ranking
|
|
108
|
+
gtd(judge): build.review.ranking
|
|
109
|
+
gtd(judge): build.review.ranking → build.review.carrying
|
|
110
|
+
gtd(check): build.review.carrying → build.review.await-review
|
|
111
|
+
"""
|