@pmelab/gtd 47.0.0 → 49.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 +53 -45
- package/dist/chunks/{reserved-DF1HD6jL.mjs → reserved-CYZ1bSBy.mjs} +93 -24
- package/dist/chunks/{review-Bv40kZu9.mjs → review-BewyJflS.mjs} +58 -14
- package/dist/chunks/{steering-D7hWeMgE.mjs → steering-BXwjmocB.mjs} +7 -1
- package/dist/chunks/{tail-D89fU_wf.mjs → tail-C1Oi2YEy.mjs} +1 -1
- package/dist/gtd.bundle.mjs +203 -867
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +90 -54
- package/dist/workflow-kit.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +16 -9
- 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 +164 -39
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +0 -2
- package/src/cli/Cli.ts +0 -15
- package/src/cli/Program.test.ts +0 -1
- package/src/cli/Program.ts +0 -8
- package/src/cli/ToolCommands.ts +1 -2
- package/src/install/Install.ts +89 -35
- package/src/steering/index.ts +1 -0
- package/src/steering/review.test.ts +29 -0
- package/src/steering/review.ts +10 -0
- package/src/testkit/Layers.ts +0 -1
- package/src/wire/ContextLimit.ts +1 -1
- package/src/workflows/feature/coverage.feature +3 -3
- package/src/workflows/feature/fast-path.feature +2 -2
- package/src/workflows/feature/feature.ts +10 -3
- package/src/workflows/feature/flag-mode.feature +2 -2
- package/src/workflows/feature/lens-review.feature +116 -13
- package/src/workflows/feature/one-step-land.feature +2 -2
- package/src/workflows/feature/refactor-absorbs.feature +2 -2
- package/src/workflows/feature/refactor-first.feature +2 -2
- package/src/workflows/feature/refactor-rebuild.feature +2 -2
- package/src/workflows/feature/refactor-review.feature +6 -6
- package/src/workflows/feature/review-answers.feature +44 -5
- package/src/workflows/feature/review-gate.feature +6 -6
- package/src/workflows/feature/reviewer-walkthrough.feature +229 -0
- package/src/workflows/feature/seam-rejection.feature +2 -2
- package/src/workflows/feature/seam.feature +2 -2
- package/src/workflows/feature/slice-findings.feature +56 -5
- package/src/workflows/feature/slicing.feature +6 -6
- package/src/workflows/feature/standards.feature +25 -1
- package/src/workflows/shared/review.ts +108 -13
- package/src/workflows/shared/steps.ts +3 -2
- package/src/workflows/shared/text.test.ts +126 -0
- package/src/workflows/shared/text.ts +125 -19
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
@inmem
|
|
2
|
+
Feature: a reviewer walks the human through the feature's code
|
|
3
|
+
|
|
4
|
+
The feature's final review is written by a reviewer turn
|
|
5
|
+
(`build.review.reviewing`): chunks grouped by concern, each pointing at its
|
|
6
|
+
code, so the phone shows it. Every finding the quality laps left gets a chunk
|
|
7
|
+
of its own, headed exactly as the prompt gives it, holding the pointers to the
|
|
8
|
+
code it cites; gtd then adds the finding's text and the answer boxes
|
|
9
|
+
(`build.review.carrying`). A chunk pointing at no code, or a finding left out,
|
|
10
|
+
is refused.
|
|
11
|
+
|
|
12
|
+
Background:
|
|
13
|
+
Given the shipped workflows, as gtd install scaffolds them
|
|
14
|
+
And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
|
|
15
|
+
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
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 a file "TODO.md" with:
|
|
22
|
+
"""
|
|
23
|
+
# Add subtract
|
|
24
|
+
|
|
25
|
+
subtract(a, b) returns the difference.
|
|
26
|
+
"""
|
|
27
|
+
And step "unwind" changes nothing
|
|
28
|
+
And step "start-gate.check" changes nothing
|
|
29
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
30
|
+
"""
|
|
31
|
+
# Requirements
|
|
32
|
+
|
|
33
|
+
## Acceptance Criteria
|
|
34
|
+
|
|
35
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
36
|
+
"""
|
|
37
|
+
And step "design.gate.answer" changes nothing
|
|
38
|
+
And step "seam.naive" writes "src/calc.test.ts" with:
|
|
39
|
+
"""
|
|
40
|
+
it("[AC-1] subtracts", () => {})
|
|
41
|
+
"""
|
|
42
|
+
And step "seam.naive" writes "src/calc.ts" with:
|
|
43
|
+
"""
|
|
44
|
+
export const add = (a: number, b: number) => a + b
|
|
45
|
+
export const subtract = (a: number, b: number) => a - b
|
|
46
|
+
"""
|
|
47
|
+
And step "seam.check" changes nothing
|
|
48
|
+
And step "implement.applying" runs its script
|
|
49
|
+
And step "implement.health.check" changes nothing
|
|
50
|
+
And step "implement.closing" runs its script
|
|
51
|
+
And judge step "implement.coverage" answers:
|
|
52
|
+
"""
|
|
53
|
+
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
54
|
+
"""
|
|
55
|
+
And step "build.quality.correctness.reviewing" writes ".gtd/QUALITY.md" with:
|
|
56
|
+
"""
|
|
57
|
+
# Quality
|
|
58
|
+
|
|
59
|
+
## AC-1 ignores floating point
|
|
60
|
+
|
|
61
|
+
Should subtract round? The criterion does not say.
|
|
62
|
+
"""
|
|
63
|
+
And judge step "build.quality.triage" answers:
|
|
64
|
+
"""
|
|
65
|
+
[{"id":"Q1","answer":"design","p":0.9}]
|
|
66
|
+
"""
|
|
67
|
+
And step "build.quality.sorting" runs its script
|
|
68
|
+
And step "build.quality.closing" runs its script
|
|
69
|
+
And step "build.review.carrying" runs its script
|
|
70
|
+
|
|
71
|
+
Scenario: the finding gets its text and answer boxes under the reviewer's pointers
|
|
72
|
+
Given step "build.review.reviewing" is prompted with "- `## AC-1 ignores floating point`:"
|
|
73
|
+
And step "build.review.reviewing" is prompted with "> Should subtract round? The criterion does not say."
|
|
74
|
+
And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
75
|
+
"""
|
|
76
|
+
# Review: abc1234
|
|
77
|
+
|
|
78
|
+
<!-- base: 0000000 -->
|
|
79
|
+
|
|
80
|
+
## Subtract
|
|
81
|
+
|
|
82
|
+
The new export.
|
|
83
|
+
|
|
84
|
+
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
85
|
+
|
|
86
|
+
## AC-1 ignores floating point
|
|
87
|
+
|
|
88
|
+
- [ ] ./src/calc.ts#2-2 — returns the raw difference
|
|
89
|
+
"""
|
|
90
|
+
When the process is driven
|
|
91
|
+
Then the process rests at "build.review.await-review"
|
|
92
|
+
And ".gtd/REVIEW.md" contains:
|
|
93
|
+
"""
|
|
94
|
+
## Subtract
|
|
95
|
+
|
|
96
|
+
The new export.
|
|
97
|
+
|
|
98
|
+
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
99
|
+
|
|
100
|
+
## AC-1 ignores floating point
|
|
101
|
+
|
|
102
|
+
Should subtract round? The criterion does not say.
|
|
103
|
+
|
|
104
|
+
In the code:
|
|
105
|
+
|
|
106
|
+
- [ ] ./src/calc.ts#2-2 — returns the raw difference
|
|
107
|
+
|
|
108
|
+
Tick exactly one:
|
|
109
|
+
|
|
110
|
+
- [ ] fix as suggested
|
|
111
|
+
- [ ] won't fix
|
|
112
|
+
- [ ] _your answer_
|
|
113
|
+
"""
|
|
114
|
+
And no commit body contains "build.review.composing"
|
|
115
|
+
And the commit subjects end with:
|
|
116
|
+
"""
|
|
117
|
+
gtd(agent): build.review.reviewing → build.review.carrying
|
|
118
|
+
gtd(check): build.review.carrying → build.review.await-review
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
Scenario: a chunk pointing at no code is refused
|
|
122
|
+
Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
123
|
+
"""
|
|
124
|
+
# Review: abc1234
|
|
125
|
+
|
|
126
|
+
<!-- base: 0000000 -->
|
|
127
|
+
|
|
128
|
+
## Subtract
|
|
129
|
+
|
|
130
|
+
The new export, with no pointer.
|
|
131
|
+
|
|
132
|
+
## AC-1 ignores floating point
|
|
133
|
+
|
|
134
|
+
- [ ] ./src/calc.ts#2-2 — returns the raw difference
|
|
135
|
+
"""
|
|
136
|
+
Then driving the process is refused with "Subtract — points at no code"
|
|
137
|
+
|
|
138
|
+
Scenario: a finding the reviewer left out is refused
|
|
139
|
+
Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
140
|
+
"""
|
|
141
|
+
# Review: abc1234
|
|
142
|
+
|
|
143
|
+
<!-- base: 0000000 -->
|
|
144
|
+
|
|
145
|
+
## Subtract
|
|
146
|
+
|
|
147
|
+
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
148
|
+
"""
|
|
149
|
+
Then driving the process is refused with "AC-1 ignores floating point — no chunk of its own"
|
|
150
|
+
|
|
151
|
+
Scenario: a risk the reviewer marks is fixed before the human sees the review
|
|
152
|
+
Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
153
|
+
"""
|
|
154
|
+
# Review: abc1234
|
|
155
|
+
|
|
156
|
+
<!-- base: 0000000 -->
|
|
157
|
+
|
|
158
|
+
## Subtract
|
|
159
|
+
|
|
160
|
+
- [ ] ./src/calc.ts#2-2 — Risk: overflows on large numbers
|
|
161
|
+
|
|
162
|
+
## AC-1 ignores floating point
|
|
163
|
+
|
|
164
|
+
- [ ] ./src/calc.ts#2-2 — returns the raw difference
|
|
165
|
+
"""
|
|
166
|
+
And step "build.review.fix.risks.fixing" changes nothing
|
|
167
|
+
And step "build.health.check" changes nothing
|
|
168
|
+
When the process is driven
|
|
169
|
+
Then the process rests at "build.review.await-review"
|
|
170
|
+
And the commit subjects end with:
|
|
171
|
+
"""
|
|
172
|
+
gtd(agent): build.review.reviewing → build.review.carrying
|
|
173
|
+
gtd(check): build.review.carrying → build.review.fix.risks.fixing
|
|
174
|
+
gtd(agent): build.review.fix.risks.fixing → build.health.check
|
|
175
|
+
gtd(check): build.health.check → build.review.reviewing
|
|
176
|
+
gtd(agent): build.review.reviewing → build.review.carrying
|
|
177
|
+
gtd(check): build.review.carrying → build.review.await-review
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
Scenario: a standard conflict the reviewer writes itself is refused
|
|
181
|
+
Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
182
|
+
"""
|
|
183
|
+
# Review: abc1234
|
|
184
|
+
|
|
185
|
+
<!-- base: 0000000 -->
|
|
186
|
+
|
|
187
|
+
## Standard conflict 0123abcd
|
|
188
|
+
|
|
189
|
+
- [ ] ./src/calc.ts#2-2 — copied over
|
|
190
|
+
|
|
191
|
+
## AC-1 ignores floating point
|
|
192
|
+
|
|
193
|
+
- [ ] ./src/calc.ts#2-2 — returns the raw difference
|
|
194
|
+
"""
|
|
195
|
+
Then driving the process is refused with "Standard conflict 0123abcd — gtd writes conflicts"
|
|
196
|
+
|
|
197
|
+
Scenario: two chunks with one title are refused
|
|
198
|
+
Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
199
|
+
"""
|
|
200
|
+
# Review: abc1234
|
|
201
|
+
|
|
202
|
+
<!-- base: 0000000 -->
|
|
203
|
+
|
|
204
|
+
## AC-1 ignores floating point
|
|
205
|
+
|
|
206
|
+
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
207
|
+
|
|
208
|
+
## AC-1 ignores floating point
|
|
209
|
+
|
|
210
|
+
- [ ] ./src/calc.ts#2-2 — returns the raw difference
|
|
211
|
+
"""
|
|
212
|
+
Then driving the process is refused with "AC-1 ignores floating point — a second chunk with this title"
|
|
213
|
+
|
|
214
|
+
Scenario: a risk marked in a finding's chunk is refused, never fixed
|
|
215
|
+
Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
216
|
+
"""
|
|
217
|
+
# Review: abc1234
|
|
218
|
+
|
|
219
|
+
<!-- base: 0000000 -->
|
|
220
|
+
|
|
221
|
+
## Subtract
|
|
222
|
+
|
|
223
|
+
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
224
|
+
|
|
225
|
+
## AC-1 ignores floating point
|
|
226
|
+
|
|
227
|
+
- [ ] ./src/calc.ts#2-2 — Risk: no rounding
|
|
228
|
+
"""
|
|
229
|
+
Then driving the process is refused with "no `Risk:` in a quality finding's chunk"
|
|
@@ -100,7 +100,7 @@ Feature: rejected naive passes walk one fixed path through the steer gate to the
|
|
|
100
100
|
And step "implement.applying" runs its script
|
|
101
101
|
When gtd lands
|
|
102
102
|
And the process is driven
|
|
103
|
-
Then the process rests at "build.review.
|
|
103
|
+
Then the process rests at "build.review.reviewing"
|
|
104
104
|
And the commit subjects end with:
|
|
105
105
|
"""
|
|
106
106
|
gtd(human): idle → unwind
|
|
@@ -124,7 +124,7 @@ Feature: rejected naive passes walk one fixed path through the steer gate to the
|
|
|
124
124
|
gtd(check): implement.applying → implement.health.check
|
|
125
125
|
gtd(check): implement.health.check → implement.coverage
|
|
126
126
|
gtd(judge): implement.coverage → implement.closing
|
|
127
|
-
gtd(check): implement.closing → build.review.
|
|
127
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
128
128
|
"""
|
|
129
129
|
And the commit "gtd(human): idle → unwind" has a body containing "Gtd-Var: flag_risk_tier=0"
|
|
130
130
|
And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
|
|
@@ -152,7 +152,7 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
152
152
|
[{"id":"AC-1","answer":"yes","p":0.9}]
|
|
153
153
|
"""
|
|
154
154
|
When the process is driven
|
|
155
|
-
Then the process rests at "build.review.
|
|
155
|
+
Then the process rests at "build.review.reviewing"
|
|
156
156
|
And "src/calc.ts" contains "subtract"
|
|
157
157
|
And the commit subjects end with:
|
|
158
158
|
"""
|
|
@@ -160,7 +160,7 @@ Feature: the naive pass and seam judge decide how a feature is built
|
|
|
160
160
|
gtd(check): implement.applying → implement.health.check
|
|
161
161
|
gtd(check): implement.health.check → implement.coverage
|
|
162
162
|
gtd(judge): implement.coverage → implement.closing
|
|
163
|
-
gtd(check): implement.closing → build.review.
|
|
163
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
164
164
|
"""
|
|
165
165
|
And ".gtd/REQUIREMENTS.md" does not exist
|
|
166
166
|
|
|
@@ -70,7 +70,7 @@ Feature: every slice's quality lap is triaged, and its leftovers wait for the re
|
|
|
70
70
|
And step "build.quality.sorting" runs its script
|
|
71
71
|
And step "build.quality.closing" runs its script
|
|
72
72
|
And step "build.health.check" changes nothing
|
|
73
|
-
And step "build.review.
|
|
73
|
+
And step "build.review.carrying" runs its script
|
|
74
74
|
|
|
75
75
|
Scenario: a finding questioning a criterion reaches the final review, never the fixer
|
|
76
76
|
Given step "build.quality.correctness.reviewing" writes ".gtd/QUALITY.md" with:
|
|
@@ -85,6 +85,25 @@ Feature: every slice's quality lap is triaged, and its leftovers wait for the re
|
|
|
85
85
|
"""
|
|
86
86
|
[{"id":"Q1","answer":"design","p":0.9}]
|
|
87
87
|
"""
|
|
88
|
+
And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
89
|
+
"""
|
|
90
|
+
# Review: abc1234
|
|
91
|
+
|
|
92
|
+
<!-- base: 0000000 -->
|
|
93
|
+
|
|
94
|
+
## Subtract and multiply
|
|
95
|
+
|
|
96
|
+
- [ ] ./src/sub.test.ts#1-1 — subtract
|
|
97
|
+
- [ ] ./src/mul.test.ts#1-1 — multiply
|
|
98
|
+
|
|
99
|
+
## AC-1 ignores floating point
|
|
100
|
+
|
|
101
|
+
- [ ] ./src/sub.test.ts#1-1 — where it applies
|
|
102
|
+
|
|
103
|
+
## AC-1 ignores floating point (2)
|
|
104
|
+
|
|
105
|
+
- [ ] ./src/sub.test.ts#1-1 — where it applies
|
|
106
|
+
"""
|
|
88
107
|
When the process is driven
|
|
89
108
|
Then the process rests at "build.review.await-review"
|
|
90
109
|
And no commit body contains "build.fix.quality.fixing"
|
|
@@ -117,8 +136,9 @@ Feature: every slice's quality lap is triaged, and its leftovers wait for the re
|
|
|
117
136
|
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
118
137
|
gtd(agent): build.quality.correctness.reviewing → build.quality.triage
|
|
119
138
|
gtd(judge): build.quality.triage → build.quality.sorting
|
|
120
|
-
gtd(check): build.quality.sorting → build.review.
|
|
121
|
-
gtd(
|
|
139
|
+
gtd(check): build.quality.sorting → build.review.reviewing
|
|
140
|
+
gtd(agent): build.review.reviewing → build.review.carrying
|
|
141
|
+
gtd(check): build.review.carrying → build.review.await-review
|
|
122
142
|
"""
|
|
123
143
|
|
|
124
144
|
Scenario: a mechanical finding the fix cap leaves in an early slice is carried, never escalated
|
|
@@ -135,6 +155,25 @@ Feature: every slice's quality lap is triaged, and its leftovers wait for the re
|
|
|
135
155
|
[{"id":"Q1","answer":"mechanical","p":0.9}]
|
|
136
156
|
"""
|
|
137
157
|
And step "build.fix.quality.fixing" changes nothing
|
|
158
|
+
And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
159
|
+
"""
|
|
160
|
+
# Review: abc1234
|
|
161
|
+
|
|
162
|
+
<!-- base: 0000000 -->
|
|
163
|
+
|
|
164
|
+
## Subtract and multiply
|
|
165
|
+
|
|
166
|
+
- [ ] ./src/sub.test.ts#1-1 — subtract
|
|
167
|
+
- [ ] ./src/mul.test.ts#1-1 — multiply
|
|
168
|
+
|
|
169
|
+
## subtract has no negative test
|
|
170
|
+
|
|
171
|
+
- [ ] ./src/sub.test.ts#1-1 — where it applies
|
|
172
|
+
|
|
173
|
+
## subtract has no negative test (2)
|
|
174
|
+
|
|
175
|
+
- [ ] ./src/sub.test.ts#1-1 — where it applies
|
|
176
|
+
"""
|
|
138
177
|
When the process is driven
|
|
139
178
|
Then the process rests at "build.review.await-review"
|
|
140
179
|
And no commit body contains "build.health.describe"
|
|
@@ -168,8 +207,9 @@ Feature: every slice's quality lap is triaged, and its leftovers wait for the re
|
|
|
168
207
|
gtd(agent): build.fix.quality.fixing
|
|
169
208
|
gtd(agent): build.fix.quality.fixing → build.health.check
|
|
170
209
|
gtd(check): build.health.check → build.quality.closing
|
|
171
|
-
gtd(check): build.quality.closing → build.review.
|
|
172
|
-
gtd(
|
|
210
|
+
gtd(check): build.quality.closing → build.review.reviewing
|
|
211
|
+
gtd(agent): build.review.reviewing → build.review.carrying
|
|
212
|
+
gtd(check): build.review.carrying → build.review.await-review
|
|
173
213
|
"""
|
|
174
214
|
|
|
175
215
|
Scenario: the final review lists every slice's fixes with their commits
|
|
@@ -190,6 +230,17 @@ Feature: every slice's quality lap is triaged, and its leftovers wait for the re
|
|
|
190
230
|
it("subtracts a larger number", () => {})
|
|
191
231
|
"""
|
|
192
232
|
And step "build.fix.quality.fixing" deletes ".gtd/QUALITY.md"
|
|
233
|
+
And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
234
|
+
"""
|
|
235
|
+
# Review: abc1234
|
|
236
|
+
|
|
237
|
+
<!-- base: 0000000 -->
|
|
238
|
+
|
|
239
|
+
## Subtract and multiply
|
|
240
|
+
|
|
241
|
+
- [ ] ./src/sub.test.ts#1-1 — subtract
|
|
242
|
+
- [ ] ./src/mul.test.ts#1-1 — multiply
|
|
243
|
+
"""
|
|
193
244
|
When the process is driven
|
|
194
245
|
Then the process rests at "build.review.await-review"
|
|
195
246
|
And ".gtd/REVIEW.md" contains ", slice 1, lens correctness)"
|
|
@@ -51,7 +51,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
51
51
|
[{"id":"AC-1","answer":"yes","p":0.9},{"id":"AC-2","answer":"yes","p":0.9}]
|
|
52
52
|
"""
|
|
53
53
|
When the process is driven
|
|
54
|
-
Then the process rests at "build.review.
|
|
54
|
+
Then the process rests at "build.review.reviewing"
|
|
55
55
|
And the commit subjects end with:
|
|
56
56
|
"""
|
|
57
57
|
gtd(human): design.gate.answer → seam.naive
|
|
@@ -60,7 +60,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
60
60
|
gtd(check): implement.applying → implement.health.check
|
|
61
61
|
gtd(check): implement.health.check → implement.coverage
|
|
62
62
|
gtd(judge): implement.coverage → implement.closing
|
|
63
|
-
gtd(check): implement.closing → build.review.
|
|
63
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
64
64
|
"""
|
|
65
65
|
And the output lines of gtd "slices" start with:
|
|
66
66
|
"""
|
|
@@ -112,7 +112,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
112
112
|
[{"id":"AC-2","answer":"yes","p":0.9}]
|
|
113
113
|
"""
|
|
114
114
|
When the process is driven
|
|
115
|
-
Then the process rests at "build.review.
|
|
115
|
+
Then the process rests at "build.review.reviewing"
|
|
116
116
|
And the commit subjects end with:
|
|
117
117
|
"""
|
|
118
118
|
gtd(agent): design.triage → design.gate.answer
|
|
@@ -129,7 +129,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
129
129
|
gtd(check): implement.applying → implement.health.check
|
|
130
130
|
gtd(check): implement.health.check → implement.coverage
|
|
131
131
|
gtd(judge): implement.coverage → implement.closing
|
|
132
|
-
gtd(check): implement.closing → build.review.
|
|
132
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
133
133
|
"""
|
|
134
134
|
And the output lines of gtd "slices" start with:
|
|
135
135
|
"""
|
|
@@ -268,7 +268,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
268
268
|
"""
|
|
269
269
|
When gtd lands
|
|
270
270
|
And the process is driven
|
|
271
|
-
Then the process rests at "build.review.
|
|
271
|
+
Then the process rests at "build.review.reviewing"
|
|
272
272
|
And the commit subjects end with:
|
|
273
273
|
"""
|
|
274
274
|
gtd(human): seam.steer → seam.naive
|
|
@@ -277,7 +277,7 @@ Feature: a change with more criteria than the cap is built in coarse slices
|
|
|
277
277
|
gtd(check): implement.applying → implement.health.check
|
|
278
278
|
gtd(check): implement.health.check → implement.coverage
|
|
279
279
|
gtd(judge): implement.coverage → implement.closing
|
|
280
|
-
gtd(check): implement.closing → build.review.
|
|
280
|
+
gtd(check): implement.closing → build.review.reviewing
|
|
281
281
|
"""
|
|
282
282
|
And ".gtd/REQUIREMENTS.md" does not exist
|
|
283
283
|
And ".gtd/SLICES.md" does not exist
|
|
@@ -67,7 +67,31 @@ Feature: the review learns standards from the human's answers
|
|
|
67
67
|
Calculators read better as classes.
|
|
68
68
|
"""
|
|
69
69
|
And step "build.quality.sorting" runs its script
|
|
70
|
-
And step "build.review.
|
|
70
|
+
And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
71
|
+
"""
|
|
72
|
+
# Review: abc1234
|
|
73
|
+
|
|
74
|
+
<!-- base: 0000000 -->
|
|
75
|
+
|
|
76
|
+
## Subtract
|
|
77
|
+
|
|
78
|
+
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
79
|
+
"""
|
|
80
|
+
And step "build.review.reviewing", when prompted with "- `## subtract has no class`", writes ".gtd/REVIEW.md" with:
|
|
81
|
+
"""
|
|
82
|
+
# Review: abc1234
|
|
83
|
+
|
|
84
|
+
<!-- base: 0000000 -->
|
|
85
|
+
|
|
86
|
+
## Subtract
|
|
87
|
+
|
|
88
|
+
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
89
|
+
|
|
90
|
+
## subtract has no class
|
|
91
|
+
|
|
92
|
+
- [ ] ./src/calc.ts#2-2 — a plain function
|
|
93
|
+
"""
|
|
94
|
+
And step "build.review.carrying" runs its script
|
|
71
95
|
And judge step "build.quality.triage" answers:
|
|
72
96
|
"""
|
|
73
97
|
[{"id":"Q1","answer":"design","p":0.9}]
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
judge,
|
|
6
6
|
numeric,
|
|
7
7
|
read,
|
|
8
|
+
refuse,
|
|
8
9
|
requireRevert,
|
|
9
10
|
restoreScript,
|
|
10
11
|
run,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
type Judgment,
|
|
16
17
|
type ScopeAccess,
|
|
17
18
|
} from "../../flows/index.js"
|
|
18
|
-
import type
|
|
19
|
+
import { reviewChunks, reviewRisks, type ReviewNote } from "../../steering/index.js"
|
|
19
20
|
import * as kit from "../../workflow-kit/index.js"
|
|
20
21
|
import { guarded, healthy, type EscalationCount } from "./health.js"
|
|
21
22
|
import { NO_FINDINGS, type LensReport } from "./lenses.js"
|
|
@@ -28,10 +29,11 @@ import {
|
|
|
28
29
|
fixRisks,
|
|
29
30
|
REQUIREMENTS,
|
|
30
31
|
REVIEW,
|
|
32
|
+
reviewing,
|
|
31
33
|
reviewMissing,
|
|
32
34
|
} from "./steps.js"
|
|
33
35
|
import { learnStandards, settlesStandard, standardsAddedSince, standardsPending } from "./learn.js"
|
|
34
|
-
import { parseStandards, STANDARDS } from "./standards.js"
|
|
36
|
+
import { isConflictTitle, parseStandards, STANDARDS } from "./standards.js"
|
|
35
37
|
import { closeReview, isOptionalObject, tail, type TailOptions } from "./tail.js"
|
|
36
38
|
import * as t from "./text.js"
|
|
37
39
|
|
|
@@ -170,6 +172,21 @@ const triage =
|
|
|
170
172
|
|
|
171
173
|
export const isCode = (path: string): boolean => path !== ".gtd" && !path.startsWith(".gtd/")
|
|
172
174
|
|
|
175
|
+
const composedOf = (
|
|
176
|
+
base: string,
|
|
177
|
+
report: LensReport,
|
|
178
|
+
nitFix: string | undefined,
|
|
179
|
+
answered: ReadonlySet<string>,
|
|
180
|
+
): t.ComposedReview => ({
|
|
181
|
+
...report,
|
|
182
|
+
nitFix,
|
|
183
|
+
answered,
|
|
184
|
+
standards: {
|
|
185
|
+
added: standardsAddedSince(base),
|
|
186
|
+
undecided: parseStandards(read(STANDARDS) ?? "").undecided,
|
|
187
|
+
},
|
|
188
|
+
})
|
|
189
|
+
|
|
173
190
|
/**
|
|
174
191
|
* gtd writes `.gtd/REVIEW.md` from the quality lap's `report` — no reviewer
|
|
175
192
|
* turn. A re-review
|
|
@@ -189,20 +206,81 @@ const composing = (
|
|
|
189
206
|
? writeScript(
|
|
190
207
|
REVIEW,
|
|
191
208
|
t.composedReview(base, {
|
|
192
|
-
...report,
|
|
209
|
+
...composedOf(base, report, nitFix, answered),
|
|
193
210
|
weakened: kit.removedAssertions(changesSince(base)),
|
|
194
|
-
nitFix,
|
|
195
|
-
answered,
|
|
196
|
-
standards: {
|
|
197
|
-
added: standardsAddedSince(base),
|
|
198
|
-
undecided: parseStandards(read(STANDARDS) ?? "").undecided,
|
|
199
|
-
},
|
|
200
211
|
}),
|
|
201
212
|
)
|
|
202
213
|
: showScript(carry, REVIEW, REVIEW),
|
|
203
214
|
{ label: "Composing the review", file: REVIEW, base },
|
|
204
215
|
)
|
|
205
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Refuses a review with a chunk pointing at no code, or a lap note without a
|
|
219
|
+
* chunk of its own. A range with no code change has nothing to point at, so
|
|
220
|
+
* there only a note's chunk needs a pointer.
|
|
221
|
+
*/
|
|
222
|
+
const requirePointers = (
|
|
223
|
+
review: string,
|
|
224
|
+
notes: readonly { title: string }[],
|
|
225
|
+
codeChanged: boolean,
|
|
226
|
+
): void => {
|
|
227
|
+
const chunks = reviewChunks(review)
|
|
228
|
+
const titles = new Set(chunks.map((c) => c.title))
|
|
229
|
+
const noted = new Set(notes.map((n) => n.title))
|
|
230
|
+
const problems = [
|
|
231
|
+
...chunks
|
|
232
|
+
.filter((c) => c.pointers === 0 && (codeChanged || noted.has(c.title)))
|
|
233
|
+
.map((c) => ` - ${REVIEW}:${c.line + 1}: ${c.title} — points at no code`),
|
|
234
|
+
...chunks
|
|
235
|
+
.filter((c) => isConflictTitle(c.title))
|
|
236
|
+
.map((c) => ` - ${REVIEW}:${c.line + 1}: ${c.title} — gtd writes conflicts; leave it out`),
|
|
237
|
+
...notes.filter((n) => !titles.has(n.title)).map((n) => ` - ${n.title} — no chunk of its own`),
|
|
238
|
+
// Notes and answers are matched to their chunk by title.
|
|
239
|
+
...chunks
|
|
240
|
+
.filter((c, i) => chunks.findIndex((d) => d.title === c.title) !== i)
|
|
241
|
+
.map((c) => ` - ${REVIEW}:${c.line + 1}: ${c.title} — a second chunk with this title`),
|
|
242
|
+
// A risk is fixed before the human sees it; a note is the human's to decide.
|
|
243
|
+
...reviewRisks(review)
|
|
244
|
+
.filter((r) => [...noted].some((title) => r.anchor.startsWith(`${title} ./`)))
|
|
245
|
+
.map((r) => ` - ${r.anchor} — no \`Risk:\` in a quality finding's chunk`),
|
|
246
|
+
]
|
|
247
|
+
if (problems.length === 0) return
|
|
248
|
+
refuse(
|
|
249
|
+
`gtd land: review-pointers: ${problems.length} problem(s) in ${REVIEW} — every chunk needs a pointer to its code, and every quality finding its own chunk, headed exactly as the prompt gave it:\n${problems.join("\n")}`,
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* A reviewer walks the human through everything since `base`, every chunk
|
|
255
|
+
* pointing at its code and each of the lap's notes a chunk of its own; gtd
|
|
256
|
+
* then adds each note's text and answer boxes, and the summary chunks.
|
|
257
|
+
*/
|
|
258
|
+
const walkthrough = async (
|
|
259
|
+
base: string,
|
|
260
|
+
report: t.ComposedReview,
|
|
261
|
+
lap: t.ReviewRange,
|
|
262
|
+
carry: string | undefined,
|
|
263
|
+
): Promise<void> => {
|
|
264
|
+
const notes = t.titledNotes(report)
|
|
265
|
+
await reviewing(base, carry, lap, t.walkthroughBlock(notes, report.nitFix))
|
|
266
|
+
const review = read(REVIEW)
|
|
267
|
+
// A review the reviewer did not write is the round's own refusal or rest.
|
|
268
|
+
if (review === undefined) return
|
|
269
|
+
requirePointers(
|
|
270
|
+
review,
|
|
271
|
+
notes,
|
|
272
|
+
changesSince(base).some((c) => isCode(c.path)),
|
|
273
|
+
)
|
|
274
|
+
const full = t.withWalkthrough(review, report)
|
|
275
|
+
if (full !== review) {
|
|
276
|
+
await run("review.carrying", writeScript(REVIEW, full), {
|
|
277
|
+
label: "Adding the quality lap's findings",
|
|
278
|
+
file: REVIEW,
|
|
279
|
+
base,
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
206
284
|
/**
|
|
207
285
|
* The quality lap's notes reach a human over everything since `base`, who
|
|
208
286
|
* answers each one — fix as suggested, won't fix, or their own answer — and
|
|
@@ -234,12 +312,27 @@ export const humanReview = (
|
|
|
234
312
|
isCode,
|
|
235
313
|
reviewing: (b, carry) => {
|
|
236
314
|
const fixedAt = nitFix
|
|
237
|
-
|
|
238
|
-
return
|
|
315
|
+
const report = options.report ?? NO_FINDINGS
|
|
316
|
+
return options.walkthrough === true
|
|
317
|
+
? guarded(
|
|
318
|
+
() =>
|
|
319
|
+
walkthrough(
|
|
320
|
+
b,
|
|
321
|
+
composedOf(b, report, fixedAt, answered),
|
|
322
|
+
options.lap ?? "first",
|
|
323
|
+
carry,
|
|
324
|
+
),
|
|
325
|
+
guard,
|
|
326
|
+
)()
|
|
327
|
+
: composing(b, report, carry, fixedAt, answered)
|
|
239
328
|
},
|
|
240
329
|
fixRisks: guarded((notes) => fixRisks(notes, fixAccess, rules), guard),
|
|
241
330
|
keepGreen: () => healthy(() => fixCheck(rules), { escalations, guard, guardRests, rules }),
|
|
242
|
-
|
|
331
|
+
// Kept until the human sees a review, so a re-review after a risk fix still names it.
|
|
332
|
+
awaitReview: (b) => {
|
|
333
|
+
nitFix = undefined
|
|
334
|
+
return awaitReview(b)
|
|
335
|
+
},
|
|
243
336
|
reviewMissing,
|
|
244
337
|
collecting: feedback.collecting,
|
|
245
338
|
triage: triage(severities),
|
|
@@ -297,8 +390,10 @@ export interface BuildTailOptions extends TailOptions {
|
|
|
297
390
|
readonly feedback?: FeedbackTarget | undefined
|
|
298
391
|
/** Write access for the nit and risk fix turns; absent: the bundled `access` export decides. */
|
|
299
392
|
readonly fixAccess?: ScopeAccess | undefined
|
|
300
|
-
/**
|
|
393
|
+
/** Which round a `walkthrough` reviewer writes; absent: `first`. */
|
|
301
394
|
readonly lap?: t.ReviewRange | undefined
|
|
395
|
+
/** A reviewer turn writes the review as a walkthrough of the code, the lap's notes among its chunks; absent: gtd composes it from the notes alone. */
|
|
396
|
+
readonly walkthrough?: boolean | undefined
|
|
302
397
|
/** What the triaged quality lap left for the review; absent: nothing. */
|
|
303
398
|
readonly report?: LensReport | undefined
|
|
304
399
|
}
|
|
@@ -94,15 +94,16 @@ export const fixQuality = (rules?: string): Promise<void> =>
|
|
|
94
94
|
allowEmpty: true,
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
-
/** Write `.gtd/REVIEW.md` over everything since `base`, carrying the answers of commit `carry` when given. */
|
|
97
|
+
/** Write `.gtd/REVIEW.md` over everything since `base`, carrying the answers of commit `carry` when given; `extra` joins the prompt. */
|
|
98
98
|
export const reviewing = (
|
|
99
99
|
base: string,
|
|
100
100
|
carry?: string,
|
|
101
101
|
lap: t.ReviewRange = "first",
|
|
102
|
+
extra = "",
|
|
102
103
|
): Promise<void> =>
|
|
103
104
|
t.agentWithSkills(
|
|
104
105
|
"review.reviewing",
|
|
105
|
-
t.buildReviewReviewingPrompt(base, lap, carry, removedAssertions(changesSince(base))),
|
|
106
|
+
t.buildReviewReviewingPrompt(base, lap, carry, removedAssertions(changesSince(base)), extra),
|
|
106
107
|
{
|
|
107
108
|
label: "Reviewing",
|
|
108
109
|
file: REVIEW,
|