@pmelab/gtd 52.1.0 → 54.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 +91 -129
- package/claude/hooks/drive.ts +0 -3
- package/claude/hooks/register.tsx +3 -68
- package/claude/hooks/ship.ts +85 -1595
- package/dist/chunks/{reserved-DxB5CROj.mjs → reserved-CQ7amcKG.mjs} +45 -132
- package/dist/chunks/{review-CKMSdvgW.mjs → review-CXzUXJHL.mjs} +12 -5
- package/dist/chunks/{tail-QZc7Uz2U.mjs → tail-JZ9PAOwp.mjs} +2 -14
- package/dist/chunks/{wire-BJuZWR3C.mjs → wire-C12dahrH.mjs} +2 -130
- package/dist/flows.mjs +1 -16
- package/dist/gtd.bundle.mjs +476 -982
- package/dist/testkit.mjs +58 -417
- package/dist/wire.mjs +2 -2
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +112 -363
- 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 +321 -677
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +1 -86
- package/src/cli/Cli.ts +13 -133
- package/src/cli/Metrics.test.ts +21 -31
- package/src/cli/Metrics.ts +25 -36
- package/src/cli/Program.test.ts +1 -35
- package/src/cli/Program.ts +5 -64
- package/src/cli/ToolCommands.ts +0 -3
- package/src/edge/Edge.test.ts +20 -322
- package/src/edge/Edge.ts +10 -181
- package/src/edge/index.ts +0 -1
- package/src/flows/runtime.test.ts +0 -27
- package/src/flows/runtime.ts +1 -49
- package/src/install/Install.ts +15 -24
- package/src/replay/Replay.test.ts +0 -169
- package/src/replay/Replay.ts +4 -93
- package/src/replay/index.ts +0 -1
- package/src/testkit/Layers.ts +0 -1
- package/src/testkit/steps.ts +23 -0
- package/src/wire/BeatDocument.ts +2 -12
- package/src/wire/BeatStatus.ts +0 -3
- package/src/wire/WireSchema.test.ts +0 -18
- package/src/wire/index.ts +0 -2
- package/src/wire/types.ts +0 -7
- package/src/workflow/Workflow.ts +1 -9
- package/src/workflows/feature/criteria.test.ts +1 -14
- package/src/workflows/feature/criteria.ts +6 -38
- package/src/workflows/feature/feature.ts +34 -118
- package/src/workflows/feature/hands-on-skip.feature +2 -70
- package/src/workflows/feature/index.ts +0 -1
- package/src/workflows/feature/lens-review.feature +50 -0
- package/src/workflows/feature/one-step-land.feature +0 -4
- package/src/workflows/feature/prototype-rounds.feature +1 -1
- package/src/workflows/feature/prototype.feature +3 -3
- package/src/workflows/feature/prototype.ts +18 -29
- package/src/workflows/feature/refactor-absorbs.feature +3 -8
- package/src/workflows/feature/refactor-first.feature +0 -5
- package/src/workflows/feature/refactor-rebuild.feature +0 -6
- package/src/workflows/feature/refactor-review.feature +49 -11
- package/src/workflows/feature/refactor.ts +30 -64
- package/src/workflows/feature/review-gate.feature +1 -1
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -31
- package/src/workflows/feature/seam-rejection.feature +0 -4
- package/src/workflows/feature/seam.test.ts +7 -26
- package/src/workflows/feature/seam.ts +52 -165
- package/src/workflows/index.ts +0 -1
- package/src/workflows/shared/lenses.ts +0 -13
- package/src/workflows/shared/options.test.ts +2 -11
- package/src/workflows/shared/options.ts +4 -21
- package/src/workflows/shared/review.test.ts +3 -3
- package/src/workflows/shared/review.ts +12 -2
- package/src/workflows/shared/steps.ts +0 -2
- package/src/workflows/shared/text.test.ts +10 -80
- package/src/workflows/shared/text.ts +35 -211
- package/src/workflows/shared/vars.ts +2 -8
- package/src/workflows/shared/workflows.steps.ts +0 -13
- package/claude/hooks/context.ts +0 -66
- package/src/replay/Slices.test.ts +0 -73
- package/src/replay/Slices.ts +0 -45
- package/src/wire/ContextLimit.fixture.ts +0 -30
- package/src/wire/ContextLimit.test.ts +0 -96
- package/src/wire/ContextLimit.ts +0 -169
- package/src/wire/Slices.ts +0 -25
- package/src/wire/golden/1/slices.json +0 -11
- package/src/workflows/feature/context-limit.feature +0 -37
- package/src/workflows/feature/continuation-cap.feature +0 -69
- package/src/workflows/feature/continuation-rebuild.feature +0 -92
- package/src/workflows/feature/continuation-slices.feature +0 -68
- package/src/workflows/feature/continuation.feature +0 -209
- package/src/workflows/feature/slice-findings.feature +0 -256
- package/src/workflows/feature/slicing-cap.feature +0 -23
- package/src/workflows/feature/slicing.feature +0 -297
- package/src/workflows/feature/slicing.test.ts +0 -49
- package/src/workflows/feature/slicing.ts +0 -105
- package/src/workflows/shared/continuation.ts +0 -92
|
@@ -5,14 +5,7 @@ import * as t from "../shared/text.js"
|
|
|
5
5
|
import { defaults } from "../shared/vars.js"
|
|
6
6
|
import type { Guard } from "../shared/health.js"
|
|
7
7
|
import { green } from "./green.js"
|
|
8
|
-
import {
|
|
9
|
-
criteriaOf,
|
|
10
|
-
criterionIds,
|
|
11
|
-
handsOnIds,
|
|
12
|
-
requireCriteriaFrozen,
|
|
13
|
-
sliceIds,
|
|
14
|
-
type CriteriaSlice,
|
|
15
|
-
} from "./criteria.js"
|
|
8
|
+
import { criteriaOf, criterionIds, handsOnIds, requireCriteriaFrozen } from "./criteria.js"
|
|
16
9
|
|
|
17
10
|
/** Only a `no` at this or above skips a criterion: no verdict, or an unsure one, sends it to the hands-on test. */
|
|
18
11
|
const UNOBSERVABLE_MIN_P = 0.5
|
|
@@ -102,12 +95,12 @@ const requireReport = (
|
|
|
102
95
|
}
|
|
103
96
|
|
|
104
97
|
/**
|
|
105
|
-
* The
|
|
106
|
-
*
|
|
98
|
+
* The criteria a person can observe by using the change, by the judge: those
|
|
99
|
+
* tagged `(no hands-on)` are never asked about.
|
|
107
100
|
*/
|
|
108
|
-
const observable = async (
|
|
101
|
+
const observable = async (): Promise<string[]> => {
|
|
109
102
|
const requirements = read(REQUIREMENTS) ?? ""
|
|
110
|
-
const own = handsOnIds(requirements
|
|
103
|
+
const own = handsOnIds(requirements)
|
|
111
104
|
const criteria = (criteriaOf(requirements) ?? []).filter((c) => own.includes(c.id))
|
|
112
105
|
if (criteria.length === 0) return []
|
|
113
106
|
const { answers } = await judge("observable", {
|
|
@@ -122,13 +115,12 @@ const observable = async (slice: CriteriaSlice | undefined): Promise<string[]> =
|
|
|
122
115
|
const frozen = (): void => requireCriteriaFrozen(REQUIREMENTS)
|
|
123
116
|
|
|
124
117
|
const testing = async (
|
|
125
|
-
slice: CriteriaSlice | undefined,
|
|
126
118
|
ids: readonly string[],
|
|
127
119
|
retest: boolean,
|
|
128
120
|
): Promise<ReturnType<typeof requireReport>> => {
|
|
129
|
-
const skipped = criterionIds(read(REQUIREMENTS) ?? ""
|
|
130
|
-
await t.agentWithSkills("testing", t.prototypeTestingPrompt(
|
|
131
|
-
label: retest ? "Re-testing the
|
|
121
|
+
const skipped = criterionIds(read(REQUIREMENTS) ?? "").filter((id) => !ids.includes(id))
|
|
122
|
+
await t.agentWithSkills("testing", t.prototypeTestingPrompt(skipped, retest), {
|
|
123
|
+
label: retest ? "Re-testing the change by hand" : "Testing the change by hand",
|
|
132
124
|
file: PROTOTYPE,
|
|
133
125
|
model: coder(),
|
|
134
126
|
system: t.testerSystem(),
|
|
@@ -138,9 +130,9 @@ const testing = async (
|
|
|
138
130
|
return requireReport(ids, retest)
|
|
139
131
|
}
|
|
140
132
|
|
|
141
|
-
const fixing = async (
|
|
133
|
+
const fixing = async (guard?: Guard): Promise<void> => {
|
|
142
134
|
await scope("fix", () =>
|
|
143
|
-
t.agentWithSkills("fixing", t.prototypeFixingPrompt(
|
|
135
|
+
t.agentWithSkills("fixing", t.prototypeFixingPrompt(), {
|
|
144
136
|
label: "Fixing what the hands-on test found",
|
|
145
137
|
file: PROTOTYPE,
|
|
146
138
|
model: coder(),
|
|
@@ -166,25 +158,22 @@ const askPerson = async (name: string, message: string, label: string): Promise<
|
|
|
166
158
|
}
|
|
167
159
|
|
|
168
160
|
/**
|
|
169
|
-
* Use the
|
|
170
|
-
* can observe, a fix turn takes each failure, and the
|
|
161
|
+
* Use the change by hand: a testing turn reports on every criterion a person
|
|
162
|
+
* can observe, a fix turn takes each failure, and the change is tested again —
|
|
171
163
|
* until the report is clean, or `prototypeRounds` fixes are spent and a person
|
|
172
|
-
* decides. A
|
|
173
|
-
* every fix turn, as it holds the implementation's.
|
|
164
|
+
* decides. A change with no such criterion is not tested by hand. `guard`
|
|
165
|
+
* holds every fix turn, as it holds the implementation's.
|
|
174
166
|
*/
|
|
175
|
-
export const prototypeTest = async (
|
|
176
|
-
slice: CriteriaSlice | undefined,
|
|
177
|
-
guard?: Guard,
|
|
178
|
-
): Promise<void> => {
|
|
167
|
+
export const prototypeTest = async (guard?: Guard): Promise<void> => {
|
|
179
168
|
const cap = prototypeRounds()
|
|
180
169
|
if (cap === 0) return
|
|
181
170
|
await scope("prototype", async () => {
|
|
182
|
-
const ids = await observable(
|
|
171
|
+
const ids = await observable()
|
|
183
172
|
if (ids.length === 0) return
|
|
184
173
|
let fixes = 0
|
|
185
174
|
let retest = false
|
|
186
175
|
for (;;) {
|
|
187
|
-
const { unguided, open } = await testing(
|
|
176
|
+
const { unguided, open } = await testing(ids, retest)
|
|
188
177
|
retest = !unguided
|
|
189
178
|
if (unguided) {
|
|
190
179
|
const instructed = await askPerson(
|
|
@@ -196,7 +185,7 @@ export const prototypeTest = async (
|
|
|
196
185
|
} else if (open.length === 0) return
|
|
197
186
|
else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
|
|
198
187
|
fixes++
|
|
199
|
-
await fixing(
|
|
188
|
+
await fixing(guard)
|
|
200
189
|
} else {
|
|
201
190
|
const changed = await askPerson(
|
|
202
191
|
"open",
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
Feature: a refactor that carried the whole feature still gets a quality lap over all of it
|
|
4
4
|
|
|
5
5
|
With GTD_FLAG_RISK_TIER off, a refactor-first
|
|
6
|
-
refactor can land everything the requirements ask for. The feature
|
|
6
|
+
refactor can land everything the requirements ask for. The feature's
|
|
7
7
|
implement turn then has only the naive pass's acceptance tests to carry
|
|
8
|
-
over, and with no code but tests changed since the refactor the feature
|
|
9
|
-
|
|
8
|
+
over, and with no code but tests changed since the refactor the feature's
|
|
9
|
+
quality lap covers the whole change, refactor included.
|
|
10
10
|
|
|
11
11
|
Background:
|
|
12
12
|
Given the shipped workflows, as gtd install scaffolds them
|
|
@@ -132,8 +132,3 @@ Feature: a refactor that carried the whole feature still gets a quality lap over
|
|
|
132
132
|
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
133
133
|
gtd(agent): build.quality.correctness.reviewing → build.review.reviewing
|
|
134
134
|
"""
|
|
135
|
-
And the output lines of gtd "slices" start with:
|
|
136
|
-
"""
|
|
137
|
-
refactor
|
|
138
|
-
feature
|
|
139
|
-
"""
|
|
@@ -160,9 +160,4 @@ Feature: a refactor-first judgment walks one fixed path to the review gate
|
|
|
160
160
|
And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
|
|
161
161
|
And the commit "gtd(judge): seam.judge → seam.note" has a body containing "\"answer\":\"refactor-first\""
|
|
162
162
|
And no commit body contains "Gtd-Approved:"
|
|
163
|
-
And the output lines of gtd "slices" start with:
|
|
164
|
-
"""
|
|
165
|
-
refactor
|
|
166
|
-
feature
|
|
167
|
-
"""
|
|
168
163
|
And no commit body contains "Gtd-Workflow:"
|
|
@@ -197,7 +197,6 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
|
|
|
197
197
|
Then the process rests at "implement.building"
|
|
198
198
|
And "src/calc.ts" contains "op(a, b, 1)"
|
|
199
199
|
And "src/calc.ts" does not contain "subtract"
|
|
200
|
-
And the output of gtd "slices" contains "refactor "
|
|
201
200
|
|
|
202
201
|
Scenario: the rebuild lands only once it carries the naive pass's acceptance tests, green
|
|
203
202
|
Given step "seam.note" writes ".gtd/REFACTOR-NOTE.md" with:
|
|
@@ -285,8 +284,3 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
|
|
|
285
284
|
gtd(check): implement.health.check → implement.closing
|
|
286
285
|
gtd(check): implement.closing → build.review.reviewing
|
|
287
286
|
"""
|
|
288
|
-
And the output lines of gtd "slices" start with:
|
|
289
|
-
"""
|
|
290
|
-
refactor
|
|
291
|
-
feature
|
|
292
|
-
"""
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
@inmem
|
|
2
|
-
Feature: the refactor
|
|
2
|
+
Feature: the refactor is reviewed on its own before the feature is implemented
|
|
3
3
|
|
|
4
4
|
After a refactor-first judgment the refactor is built, quality-lapped and
|
|
5
|
-
reviewed
|
|
6
|
-
the feature
|
|
5
|
+
reviewed on its own. Review edits re-run the refactor, never the design;
|
|
6
|
+
the feature that follows is quality-lapped from the refactor's
|
|
7
7
|
sign-off, then reviewed. The flags are pinned so the per-flag test runs never move it off
|
|
8
8
|
its path.
|
|
9
9
|
|
|
@@ -614,7 +614,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
614
614
|
And ".gtd/REFACTOR.md" does not exist
|
|
615
615
|
And ".gtd/REVIEW.md" contains "A: add and subtract differ only by sign."
|
|
616
616
|
|
|
617
|
-
Scenario: a no-change landing after the in-gate reply signs off and the feature
|
|
617
|
+
Scenario: a no-change landing after the in-gate reply signs off and the feature follows
|
|
618
618
|
Given step "implement.building" writes "src/calc.ts" with:
|
|
619
619
|
"""
|
|
620
620
|
export const op = (a: number, b: number, sign: number) => a + sign * b
|
|
@@ -697,7 +697,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
697
697
|
Then the process rests at "build.review.reviewing"
|
|
698
698
|
And "src/calc.ts" contains "subtract"
|
|
699
699
|
|
|
700
|
-
Scenario: a praise-only one-shot footnote signs off and the feature
|
|
700
|
+
Scenario: a praise-only one-shot footnote signs off and the feature follows
|
|
701
701
|
Given step "implement.building" writes "src/calc.ts" with:
|
|
702
702
|
"""
|
|
703
703
|
export const op = (a: number, b: number, sign: number) => a + sign * b
|
|
@@ -979,7 +979,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
979
979
|
Then driving the process is refused with "refactor-feedback"
|
|
980
980
|
And driving the process is refused with "hand-edited src/calc.ts"
|
|
981
981
|
|
|
982
|
-
Scenario: a praise note and a concluded thread on a settled round sign off and the feature
|
|
982
|
+
Scenario: a praise note and a concluded thread on a settled round sign off and the feature follows
|
|
983
983
|
Given step "implement.building" writes "src/calc.ts" with:
|
|
984
984
|
"""
|
|
985
985
|
export const op = (a: number, b: number, sign: number) => a + sign * b
|
|
@@ -1066,7 +1066,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1066
1066
|
Then the process rests at "build.review.reviewing"
|
|
1067
1067
|
And "src/calc.ts" contains "subtract"
|
|
1068
1068
|
|
|
1069
|
-
Scenario: a praise-only refactor review still signs off and the feature
|
|
1069
|
+
Scenario: a praise-only refactor review still signs off and the feature follows
|
|
1070
1070
|
Given step "implement.building" writes "src/calc.ts" with:
|
|
1071
1071
|
"""
|
|
1072
1072
|
export const op = (a: number, b: number, sign: number) => a + sign * b
|
|
@@ -1099,7 +1099,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1099
1099
|
Then the process rests at "build.review.reviewing"
|
|
1100
1100
|
And "src/calc.ts" contains "subtract"
|
|
1101
1101
|
|
|
1102
|
-
Scenario: a nit fix in the refactor
|
|
1102
|
+
Scenario: a nit fix in the refactor that changes a non-stray is refused, though a glob stray widens the write access
|
|
1103
1103
|
Given step "seam.naive" writes "src/*.ts" with:
|
|
1104
1104
|
"""
|
|
1105
1105
|
export const literalGlobName = 2
|
|
@@ -1130,7 +1130,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1130
1130
|
When gtd lands
|
|
1131
1131
|
Then driving the process is refused with "refactor-scope"
|
|
1132
1132
|
|
|
1133
|
-
Scenario: the nit-fix turn in the refactor
|
|
1133
|
+
Scenario: the nit-fix turn in the refactor may write the strays and .gtd/** only
|
|
1134
1134
|
Given step "seam.naive" writes "src/*.ts" with:
|
|
1135
1135
|
"""
|
|
1136
1136
|
export const literalGlobName = 2
|
|
@@ -1185,7 +1185,45 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1185
1185
|
"""
|
|
1186
1186
|
And no commit body contains "refactor.build.review.composing"
|
|
1187
1187
|
|
|
1188
|
-
|
|
1188
|
+
Scenario: a risk the refactor's re-review marks again reaches the human unfixed
|
|
1189
|
+
Given step "refactor.build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
1190
|
+
"""
|
|
1191
|
+
# Review: abc1234
|
|
1192
|
+
|
|
1193
|
+
<!-- base: 0000000 -->
|
|
1194
|
+
|
|
1195
|
+
## calc
|
|
1196
|
+
|
|
1197
|
+
- [ ] ./src/calc.ts#1-2 — Risk: drops the sign
|
|
1198
|
+
"""
|
|
1199
|
+
When the process is driven
|
|
1200
|
+
Then the process rests at "refactor.build.review.fix.risks.fixing"
|
|
1201
|
+
Given step "refactor.build.review.reviewing" is no longer scripted
|
|
1202
|
+
And step "refactor.build.review.reviewing" replaces the text "Risk: drops the sign" in ".gtd/REVIEW.md" with "Risk: still drops the sign"
|
|
1203
|
+
And step "refactor.build.review.fix.risks.fixing" changes nothing
|
|
1204
|
+
And step "refactor.build.health.check" changes nothing
|
|
1205
|
+
When the process is driven
|
|
1206
|
+
Then the process rests at "refactor.build.review.await-review"
|
|
1207
|
+
And ".gtd/REVIEW.md" contains:
|
|
1208
|
+
"""
|
|
1209
|
+
## Walkthrough
|
|
1210
|
+
|
|
1211
|
+
### calc
|
|
1212
|
+
|
|
1213
|
+
- [ ] ./src/calc.ts#1-2 — Risk: still drops the sign
|
|
1214
|
+
"""
|
|
1215
|
+
And ".gtd/REVIEW.md" contains "## Walkthrough" exactly once
|
|
1216
|
+
And the commit subjects end with:
|
|
1217
|
+
"""
|
|
1218
|
+
gtd(agent): refactor.build.review.reviewing → refactor.build.review.fix.risks.fixing
|
|
1219
|
+
gtd(agent): refactor.build.review.fix.risks.fixing → refactor.build.health.check
|
|
1220
|
+
gtd(check): refactor.build.health.check → refactor.build.review.reviewing
|
|
1221
|
+
gtd(agent): refactor.build.review.reviewing → refactor.build.review.ranking
|
|
1222
|
+
gtd(judge): refactor.build.review.ranking → refactor.build.review.carrying
|
|
1223
|
+
gtd(check): refactor.build.review.carrying → refactor.build.review.await-review
|
|
1224
|
+
"""
|
|
1225
|
+
|
|
1226
|
+
Rule: with a quality lens on both the refactor and the feature
|
|
1189
1227
|
|
|
1190
1228
|
Background:
|
|
1191
1229
|
Given an environment variable "GTD_REFACTORQUALITYREVIEWS" set to "correctness"
|
|
@@ -1275,7 +1313,7 @@ Feature: the refactor slice is reviewed on its own before the feature is impleme
|
|
|
1275
1313
|
"""
|
|
1276
1314
|
And step "refactor.build.review.closing" runs its script
|
|
1277
1315
|
|
|
1278
|
-
Scenario:
|
|
1316
|
+
Scenario: the refactor's and the feature's quality laps each review from their own base
|
|
1279
1317
|
When the process is driven
|
|
1280
1318
|
Then the process rests at "refactor.build.quality.correctness.reviewing"
|
|
1281
1319
|
And the resting step's content contains the hash of the commit "gtd(agent): seam.note → refactor.deriving"
|
|
@@ -15,9 +15,8 @@ import {
|
|
|
15
15
|
} from "../../flows/index.js"
|
|
16
16
|
import { lensEntriesOf } from "../../workflow-kit/index.js"
|
|
17
17
|
import { markFenced } from "../shared/fences.js"
|
|
18
|
-
import { continuing, phase, type Phase } from "../shared/continuation.js"
|
|
19
18
|
import { healthy, type Guard } from "../shared/health.js"
|
|
20
|
-
import { coder, FEEDBACK,
|
|
19
|
+
import { coder, FEEDBACK, REFACTOR } from "../shared/steps.js"
|
|
21
20
|
import * as t from "../shared/text.js"
|
|
22
21
|
import { defaults, testFileGlobs } from "../shared/vars.js"
|
|
23
22
|
import { buildTail, reUnwind, type FeedbackTarget } from "../shared/review.js"
|
|
@@ -264,12 +263,7 @@ const guardOf =
|
|
|
264
263
|
}
|
|
265
264
|
|
|
266
265
|
/** Derive, build, check and close one refactor lap; `feedback` is the collected review text of the lap before. */
|
|
267
|
-
const refactorLap = async (
|
|
268
|
-
p: Plan,
|
|
269
|
-
guard: Guard,
|
|
270
|
-
refactor: Phase,
|
|
271
|
-
feedback?: string,
|
|
272
|
-
): Promise<void> => {
|
|
266
|
+
const refactorLap = async (p: Plan, guard: Guard, feedback?: string): Promise<void> => {
|
|
273
267
|
const { pattern, strays, reference, note } = p
|
|
274
268
|
await run(
|
|
275
269
|
"deriving",
|
|
@@ -282,59 +276,32 @@ const refactorLap = async (
|
|
|
282
276
|
]),
|
|
283
277
|
{ label: "Deriving the refactor package", file: REFACTOR_PACKAGE },
|
|
284
278
|
)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// The building turns' own changes only: a fix turn reaching a path is not the refactor reaching it.
|
|
312
|
-
for (const c of changes()) reached.add(c.path)
|
|
313
|
-
await healthy(
|
|
314
|
-
async () => {
|
|
315
|
-
await t.agentWithSkills("fix.suite.fixing", t.refactorFixPrompt(strays, pattern), {
|
|
316
|
-
label: "Fixing the check",
|
|
317
|
-
file: FEEDBACK,
|
|
318
|
-
model: coder(),
|
|
319
|
-
system: t.builderSystem(),
|
|
320
|
-
})
|
|
321
|
-
guard()
|
|
322
|
-
},
|
|
323
|
-
{ describeGuard: guard, rules: t.refactorRules(strays, pattern) },
|
|
324
|
-
)
|
|
325
|
-
if (left === undefined) return { result: undefined }
|
|
326
|
-
return {
|
|
327
|
-
result: undefined,
|
|
328
|
-
handoff: {
|
|
329
|
-
snapshot,
|
|
330
|
-
note: left.trim(),
|
|
331
|
-
open: strays.filter((path) => !reached.has(path)),
|
|
332
|
-
},
|
|
333
|
-
}
|
|
334
|
-
})
|
|
335
|
-
await run("closing", removeScript([REFACTOR_PACKAGE, HANDOFF]), {
|
|
336
|
-
label: "Closing out the refactor",
|
|
337
|
-
})
|
|
279
|
+
await t.agentWithSkills(
|
|
280
|
+
"building",
|
|
281
|
+
t.refactorBuildingPrompt({
|
|
282
|
+
pkg: REFACTOR_PACKAGE,
|
|
283
|
+
strays,
|
|
284
|
+
pattern,
|
|
285
|
+
reference,
|
|
286
|
+
feedback: feedback !== undefined,
|
|
287
|
+
note: note !== undefined,
|
|
288
|
+
}),
|
|
289
|
+
{ label: "Refactoring", model: coder(), system: t.builderSystem() },
|
|
290
|
+
)
|
|
291
|
+
guard()
|
|
292
|
+
await healthy(
|
|
293
|
+
async () => {
|
|
294
|
+
await t.agentWithSkills("fix.suite.fixing", t.refactorFixPrompt(strays, pattern), {
|
|
295
|
+
label: "Fixing the check",
|
|
296
|
+
file: FEEDBACK,
|
|
297
|
+
model: coder(),
|
|
298
|
+
system: t.builderSystem(),
|
|
299
|
+
})
|
|
300
|
+
guard()
|
|
301
|
+
},
|
|
302
|
+
{ describeGuard: guard, rules: t.refactorRules(strays, pattern) },
|
|
303
|
+
)
|
|
304
|
+
await run("closing", removeScript([REFACTOR_PACKAGE]), { label: "Closing out the refactor" })
|
|
338
305
|
}
|
|
339
306
|
|
|
340
307
|
const strayAccess = (strays: readonly string[]) => ({
|
|
@@ -348,7 +315,7 @@ export const refactorFirst = async (
|
|
|
348
315
|
const p = plan(shape, options)
|
|
349
316
|
if (p === undefined) return false
|
|
350
317
|
return scope({ name: "refactor", access: strayAccess(p.strays) }, async () => {
|
|
351
|
-
await refactorLap(p, guardOf(p, head())
|
|
318
|
+
await refactorLap(p, guardOf(p, head()))
|
|
352
319
|
return true
|
|
353
320
|
})
|
|
354
321
|
}
|
|
@@ -375,10 +342,9 @@ export const reviewedRefactor = async (
|
|
|
375
342
|
"gtd land: refactor-feedback: the review feedback changed files but wrote no .gtd/REFACTOR.md — a refactor cannot act on it; revert the change, or `gtd abandon` and restart",
|
|
376
343
|
dropped: t.refactorDroppedRefusal,
|
|
377
344
|
}
|
|
378
|
-
const refactor = phase("refactor")
|
|
379
345
|
let collected: string | undefined
|
|
380
346
|
for (;;) {
|
|
381
|
-
await refactorLap(p, guard,
|
|
347
|
+
await refactorLap(p, guard, collected)
|
|
382
348
|
// A person's edit at an escalation rest is never refused, as in the lap.
|
|
383
349
|
const outcome = await buildTail(entry, {
|
|
384
350
|
green: true,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
@inmem
|
|
2
2
|
Feature: a feature process ends at the human review
|
|
3
3
|
|
|
4
|
-
After the
|
|
4
|
+
After the quality lap the process rests at the review gate,
|
|
5
5
|
`.gtd/REVIEW.md`. A sign-off ends the process. A note asking for a change in
|
|
6
6
|
behaviour starts a feedback lap — design, then implement with no new naive
|
|
7
7
|
pass or seam judge, the hands-on test, the quality lap — and the review
|
|
@@ -179,37 +179,6 @@ Feature: a reviewer walks the human through the feature's code
|
|
|
179
179
|
"""
|
|
180
180
|
Then driving the process is refused with "AC-1 ignores floating point — no chunk of its own"
|
|
181
181
|
|
|
182
|
-
Scenario: a risk the reviewer marks is fixed before the human sees the review
|
|
183
|
-
Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
184
|
-
"""
|
|
185
|
-
# Review: abc1234
|
|
186
|
-
|
|
187
|
-
<!-- base: 0000000 -->
|
|
188
|
-
|
|
189
|
-
## Subtract
|
|
190
|
-
|
|
191
|
-
- [ ] ./src/calc.ts#2-2 — Risk: overflows on large numbers
|
|
192
|
-
|
|
193
|
-
## AC-1 ignores floating point
|
|
194
|
-
|
|
195
|
-
- [ ] ./src/calc.ts#2-2 — returns the raw difference
|
|
196
|
-
"""
|
|
197
|
-
And step "build.review.fix.risks.fixing" changes nothing
|
|
198
|
-
And step "build.health.check" changes nothing
|
|
199
|
-
When the process is driven
|
|
200
|
-
Then the process rests at "build.review.await-review"
|
|
201
|
-
And the commit subjects end with:
|
|
202
|
-
"""
|
|
203
|
-
gtd(agent): build.review.reviewing → build.review.ranking
|
|
204
|
-
gtd(judge): build.review.ranking → build.review.carrying
|
|
205
|
-
gtd(check): build.review.carrying → build.review.fix.risks.fixing
|
|
206
|
-
gtd(agent): build.review.fix.risks.fixing → build.health.check
|
|
207
|
-
gtd(check): build.health.check → build.review.reviewing
|
|
208
|
-
gtd(agent): build.review.reviewing → build.review.ranking
|
|
209
|
-
gtd(judge): build.review.ranking → build.review.carrying
|
|
210
|
-
gtd(check): build.review.carrying → build.review.await-review
|
|
211
|
-
"""
|
|
212
|
-
|
|
213
182
|
Scenario: a standard conflict the reviewer writes itself is refused
|
|
214
183
|
Given step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
215
184
|
"""
|
|
@@ -136,8 +136,4 @@ Feature: rejected naive passes walk one fixed path through the steer gate to the
|
|
|
136
136
|
And the commit "gtd(judge): seam.judge → seam.steer" has a body containing "\"answer\":\"reject\""
|
|
137
137
|
And the commit "gtd(judge): seam.judge → implement.applying" has a body containing "\"answer\":\"land\""
|
|
138
138
|
And no commit body contains "Gtd-Approved:"
|
|
139
|
-
And the output lines of gtd "slices" start with:
|
|
140
|
-
"""
|
|
141
|
-
feature
|
|
142
|
-
"""
|
|
143
139
|
And no commit body contains "Gtd-Workflow:"
|
|
@@ -311,9 +311,6 @@ const reviewDoc = [
|
|
|
311
311
|
|
|
312
312
|
interface Seamed {
|
|
313
313
|
readonly names: string[]
|
|
314
|
-
readonly slices: string[]
|
|
315
|
-
/** Slice names entered, with the steps seen while inside each. */
|
|
316
|
-
readonly inside: Record<string, string[]>
|
|
317
314
|
readonly outcome: SeamOutcome | Error
|
|
318
315
|
}
|
|
319
316
|
|
|
@@ -327,10 +324,7 @@ const driveSeam = async (
|
|
|
327
324
|
let red = false
|
|
328
325
|
let redsLeft = reds
|
|
329
326
|
const names: string[] = []
|
|
330
|
-
const slices: string[] = []
|
|
331
|
-
const inside: Record<string, string[]> = {}
|
|
332
327
|
const stack: string[] = []
|
|
333
|
-
const open: string[] = []
|
|
334
328
|
const files = new Map<string, string>([["src/a.ts", "a"]])
|
|
335
329
|
let current = "c0"
|
|
336
330
|
const effects: Record<string, () => void> = {
|
|
@@ -351,12 +345,6 @@ const driveSeam = async (
|
|
|
351
345
|
},
|
|
352
346
|
pushScope: (scope: { name?: string }) => void stack.push(scope.name ?? ""),
|
|
353
347
|
popScope: () => void stack.pop(),
|
|
354
|
-
enterSlice: (name: string) => {
|
|
355
|
-
slices.push(name)
|
|
356
|
-
open.push(name)
|
|
357
|
-
inside[name] = []
|
|
358
|
-
},
|
|
359
|
-
leaveSlice: () => void open.pop(),
|
|
360
348
|
read: (path: string) => files.get(path),
|
|
361
349
|
head: () => current,
|
|
362
350
|
kept: () => tip(stray),
|
|
@@ -372,7 +360,6 @@ const driveSeam = async (
|
|
|
372
360
|
}
|
|
373
361
|
if (full === "seam.clear") files.delete(".gtd/FEEDBACK.md")
|
|
374
362
|
names.push(full)
|
|
375
|
-
for (const name of open) inside[name]!.push(full)
|
|
376
363
|
effects[request.name]?.()
|
|
377
364
|
if (request.kind === "judge")
|
|
378
365
|
return Promise.resolve({ answers: { shape: { answer, p: 0.9 } }, truncated: [] })
|
|
@@ -385,14 +372,13 @@ const driveSeam = async (
|
|
|
385
372
|
} catch (error) {
|
|
386
373
|
outcome = error as Error
|
|
387
374
|
}
|
|
388
|
-
return { names,
|
|
375
|
+
return { names, outcome }
|
|
389
376
|
}
|
|
390
377
|
|
|
391
378
|
describe("seamBuild", () => {
|
|
392
|
-
it("land resolves the kept tip to land, with the caller's base
|
|
379
|
+
it("land resolves the kept tip to land, with the caller's base", async () => {
|
|
393
380
|
const h = await driveSeam("land")
|
|
394
381
|
expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
|
|
395
|
-
expect(h.slices).toEqual([])
|
|
396
382
|
expect(h.names).toEqual(["seam.naive", "seam.check", "seam.judge"])
|
|
397
383
|
})
|
|
398
384
|
|
|
@@ -432,7 +418,7 @@ describe("seamBuild", () => {
|
|
|
432
418
|
expect(h.outcome).toEqual({ reference: "abc", base: "c-base", land: true })
|
|
433
419
|
})
|
|
434
420
|
|
|
435
|
-
it("refactor-first runs the reviewed refactor
|
|
421
|
+
it("refactor-first runs the reviewed refactor right after the note and resolves HEAD after it", async () => {
|
|
436
422
|
const h = await driveSeam("refactor-first")
|
|
437
423
|
expect(h.outcome).toEqual({
|
|
438
424
|
reference: "abc",
|
|
@@ -440,16 +426,14 @@ describe("seamBuild", () => {
|
|
|
440
426
|
refactored: true,
|
|
441
427
|
rebuild: { note: "Seam: a shared op", acceptance: { hash: "abc", tests: [], ids: [] } },
|
|
442
428
|
})
|
|
443
|
-
expect(h.slices).toEqual(["refactor"])
|
|
444
429
|
expect(h.names.indexOf("seam.note")).toBe(h.names.indexOf("seam.judge") + 1)
|
|
445
|
-
expect(h.
|
|
446
|
-
expect(h.
|
|
447
|
-
expect(h.inside.refactor).toContain("refactor.build.review.closing")
|
|
430
|
+
expect(h.names.indexOf("refactor.deriving")).toBe(h.names.indexOf("seam.note") + 1)
|
|
431
|
+
expect(h.names).toContain("refactor.build.review.closing")
|
|
448
432
|
expect(h.names.some((n) => n.includes("approve"))).toBe(false)
|
|
449
433
|
expect(h.names.some((n) => n.startsWith("implement"))).toBe(false)
|
|
450
434
|
})
|
|
451
435
|
|
|
452
|
-
it("refactor-first with only test-file strays resolves the caller's base with no
|
|
436
|
+
it("refactor-first with only test-file strays resolves the caller's base with no refactor and no by-hand", async () => {
|
|
453
437
|
const h = await driveSeam("refactor-first", change("src/a.test.ts", "modified"), {
|
|
454
438
|
seamAllow: ".gtd/**",
|
|
455
439
|
})
|
|
@@ -458,7 +442,6 @@ describe("seamBuild", () => {
|
|
|
458
442
|
base: "c-base",
|
|
459
443
|
rebuild: { acceptance: { hash: "abc", tests: [], ids: [] } },
|
|
460
444
|
})
|
|
461
|
-
expect(h.slices).toEqual([])
|
|
462
445
|
expect(h.names).not.toContain("seam.by-hand")
|
|
463
446
|
expect(h.names.some((n) => n.startsWith("refactor"))).toBe(false)
|
|
464
447
|
})
|
|
@@ -469,18 +452,16 @@ describe("seamBuild", () => {
|
|
|
469
452
|
{ path: "src/a.test.ts", status: "modified", before: undefined, after: "b" },
|
|
470
453
|
{ seamAllow: ".gtd/**" },
|
|
471
454
|
)
|
|
472
|
-
expect(h.slices).toEqual([])
|
|
473
455
|
expect(h.names).toContain("seam.by-hand")
|
|
474
456
|
})
|
|
475
457
|
|
|
476
|
-
it("rests unreadable strays at seam.by-hand and
|
|
458
|
+
it("rests unreadable strays at seam.by-hand and runs no refactor", async () => {
|
|
477
459
|
const h = await driveSeam("refactor-first", {
|
|
478
460
|
path: "src/a.ts",
|
|
479
461
|
status: "modified",
|
|
480
462
|
before: undefined,
|
|
481
463
|
after: "b",
|
|
482
464
|
})
|
|
483
|
-
expect(h.slices).toEqual([])
|
|
484
465
|
expect(h.names).toContain("seam.by-hand")
|
|
485
466
|
expect(h.outcome).toEqual({
|
|
486
467
|
reference: "abc",
|