@pmelab/gtd 43.0.0 → 44.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 +17 -13
- package/dist/chunks/{reserved-COEMFEpy.mjs → reserved-DJk_6-ju.mjs} +41 -1
- package/dist/chunks/review-X7Jfz0dY.mjs +249 -0
- package/dist/chunks/{tail-C_xn8U7U.mjs → tail-5w5mxGTq.mjs} +3 -3
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +6 -278
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +27 -7
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +74 -44
- package/package.json +1 -1
- package/src/steering/FORMATS.md +4 -4
- package/src/workflows/feature/coverage.ts +1 -1
- package/src/workflows/feature/feature.test.ts +8 -0
- package/src/workflows/feature/feature.ts +2 -2
- package/src/workflows/feature/index.ts +1 -1
- package/src/workflows/feature/refactor.ts +1 -1
- package/src/workflows/feature/seam.ts +1 -1
- package/src/workflows/feature/steps.test.ts +2 -64
- package/src/workflows/feature/steps.ts +5 -41
- package/src/workflows/fix/fix.feature +20 -21
- package/src/workflows/index.ts +1 -1
- package/src/workflows/review/review-door.feature +149 -0
- package/src/workflows/review/review.test.ts +1 -73
- package/src/workflows/review/review.ts +34 -6
- package/src/workflows/{feature → shared}/review.test.ts +4 -8
- package/src/workflows/{feature → shared}/review.ts +13 -10
- package/src/workflows/shared/skills.test.ts +2 -0
- package/src/workflows/shared/skills.ts +2 -0
- package/src/workflows/shared/steps.test.ts +64 -2
- package/src/workflows/shared/steps.ts +40 -1
- package/src/workflows/shared/tail.ts +2 -2
- package/src/workflows/shared/text.ts +11 -0
|
@@ -21,6 +21,7 @@ describe("the bundled workflow's skills export", () => {
|
|
|
21
21
|
"build.review.fix.nits",
|
|
22
22
|
"build.review.fix.risks",
|
|
23
23
|
"notes",
|
|
24
|
+
"feedback",
|
|
24
25
|
"refactor",
|
|
25
26
|
"refactor.fix.suite",
|
|
26
27
|
"refactor.health",
|
|
@@ -71,6 +72,7 @@ describe("the bundled workflow's skills export", () => {
|
|
|
71
72
|
"build.review.fix.nits": ["incremental-implementation", "code-simplification"],
|
|
72
73
|
"build.review.fix.risks": ["debugging-and-error-recovery", "incremental-implementation"],
|
|
73
74
|
notes: ["incremental-implementation", "code-simplification"],
|
|
75
|
+
feedback: ["test-driven-development", "incremental-implementation"],
|
|
74
76
|
refactor: ["ponytail"],
|
|
75
77
|
"refactor.fix.suite": ["ponytail", "debugging-and-error-recovery"],
|
|
76
78
|
"refactor.health": ["ponytail", "debugging-and-error-recovery"],
|
|
@@ -22,6 +22,8 @@ export const skills = (
|
|
|
22
22
|
design: ["spec-driven-development", "planning-and-task-breakdown"],
|
|
23
23
|
// `fix`'s turn over the notes a review handed on.
|
|
24
24
|
notes: ["incremental-implementation", "code-simplification"],
|
|
25
|
+
// `review`'s turn building what a review round collected.
|
|
26
|
+
feedback: ["test-driven-development", "incremental-implementation"],
|
|
25
27
|
// The refactor's own turns load `ponytail`; `refactor.build.*` carry the `build.*` lists.
|
|
26
28
|
...build,
|
|
27
29
|
...Object.fromEntries(Object.entries(build).map(([name, list]) => [`refactor.${name}`, list])),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import type
|
|
3
|
-
import { captureStep } from "./text.fixture.js"
|
|
2
|
+
import { installContext, type ScopeOptions, type StepRequest } from "../../flows/index.js"
|
|
3
|
+
import { captureStep, fixtureContext } from "./text.fixture.js"
|
|
4
4
|
import * as steps from "./steps.js"
|
|
5
5
|
|
|
6
6
|
const capture = (
|
|
@@ -151,4 +151,66 @@ describe("the bundled workflow's model hints", () => {
|
|
|
151
151
|
expect(await modelOf(() => steps.fixQuality())).toBe("base")
|
|
152
152
|
expect(await modelOf(() => steps.fixCheck(), { env: { coderModel: "sonnet" } })).toBe("sonnet")
|
|
153
153
|
})
|
|
154
|
+
|
|
155
|
+
it("answerReviewQuestions answers inline in .gtd/REVIEW.md as the reviewer, with its bundled skills", async () => {
|
|
156
|
+
const request = await capture(
|
|
157
|
+
() =>
|
|
158
|
+
steps.answerReviewQuestions([{ id: "note-1", anchor: "calc ./a.ts#1-1", text: "why?" }]),
|
|
159
|
+
"build",
|
|
160
|
+
)
|
|
161
|
+
if (request.kind !== "agent") throw new Error("unreachable")
|
|
162
|
+
expect(request.name).toBe("review.answer-review-questions")
|
|
163
|
+
expect(request.options).toMatchObject({
|
|
164
|
+
file: ".gtd/REVIEW.md",
|
|
165
|
+
mode: "review",
|
|
166
|
+
})
|
|
167
|
+
expect(request.prompt).toContain("code-review-and-quality")
|
|
168
|
+
expect(request.prompt).toContain("note-1 — calc ./a.ts#1-1")
|
|
169
|
+
expect(request.prompt).toContain("`A: ` line")
|
|
170
|
+
expect(request.prompt).toContain("gtd check review .gtd/REVIEW.md")
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("fixNits fixes every nit in one turn and leaves .gtd/REVIEW.md alone", async () => {
|
|
174
|
+
const request = await capture(
|
|
175
|
+
() =>
|
|
176
|
+
steps.fixNits([
|
|
177
|
+
{ id: "note-1", anchor: "calc ./a.ts#1-1", text: "typo" },
|
|
178
|
+
{ id: "note-2", anchor: "calc ./a.ts#2-2", text: "semicolon" },
|
|
179
|
+
]),
|
|
180
|
+
"build",
|
|
181
|
+
)
|
|
182
|
+
if (request.kind !== "agent") throw new Error("unreachable")
|
|
183
|
+
expect(request.name).toBe("review.fix.nits.fixing")
|
|
184
|
+
expect(request.prompt).toContain("incremental-implementation, code-simplification")
|
|
185
|
+
expect(request.prompt).toContain("typo")
|
|
186
|
+
expect(request.prompt).toContain("semicolon")
|
|
187
|
+
expect(request.prompt).toContain("Leave `.gtd/REVIEW.md` untouched")
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it("fixNits with access runs in a review.fix.nits scope, the step named fixing inside it", async () => {
|
|
191
|
+
const scopes: ScopeOptions[] = []
|
|
192
|
+
const steps_: StepRequest[] = []
|
|
193
|
+
const access = { write: ["src/a.ts", ".gtd/**"] }
|
|
194
|
+
installContext(
|
|
195
|
+
fixtureContext(
|
|
196
|
+
{ scope: "refactor.build" },
|
|
197
|
+
{
|
|
198
|
+
step: (request) => {
|
|
199
|
+
steps_.push(request)
|
|
200
|
+
return Promise.resolve()
|
|
201
|
+
},
|
|
202
|
+
pushScope: (s) => void scopes.push(s),
|
|
203
|
+
popScope: () => undefined,
|
|
204
|
+
changesSince: () => [],
|
|
205
|
+
},
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
try {
|
|
209
|
+
await steps.fixNits([], access)
|
|
210
|
+
} finally {
|
|
211
|
+
installContext(undefined)
|
|
212
|
+
}
|
|
213
|
+
expect(scopes).toEqual([{ name: "review.fix.nits", access }])
|
|
214
|
+
expect(steps_.map((r) => ("name" in r ? r.name : ""))).toEqual(["fixing"])
|
|
215
|
+
})
|
|
154
216
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { changesSince, env, human, scope, type ScopeAccess } from "../../flows/index.js"
|
|
2
|
-
import { removedAssertions } from "../../workflow-kit/index.js"
|
|
2
|
+
import { removedAssertions, type CollectRound } from "../../workflow-kit/index.js"
|
|
3
3
|
import * as t from "./text.js"
|
|
4
4
|
|
|
5
5
|
// The bundled workflow's single steps. A step's name is relative to the
|
|
@@ -145,6 +145,45 @@ export const awaitReview = (
|
|
|
145
145
|
acceptClean: true,
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
+
/** Answer every `question` note inline in `.gtd/REVIEW.md`. */
|
|
149
|
+
export const answerReviewQuestions = (notes: readonly t.NoteInput[]): Promise<void> =>
|
|
150
|
+
t.agentWithSkills("review.answer-review-questions", t.buildReviewAnswerQuestionsPrompt(notes), {
|
|
151
|
+
label: "Answering your questions",
|
|
152
|
+
file: REVIEW,
|
|
153
|
+
mode: "review",
|
|
154
|
+
model: planner(),
|
|
155
|
+
system: t.reviewerSystem(),
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
/** Fix every `nit` note in one turn. */
|
|
159
|
+
export const fixNits = (
|
|
160
|
+
notes: readonly t.NoteInput[],
|
|
161
|
+
access?: ScopeAccess,
|
|
162
|
+
rules?: string,
|
|
163
|
+
): Promise<void> => {
|
|
164
|
+
const fixing = (name: string): Promise<void> =>
|
|
165
|
+
t.agentWithSkills(name, t.buildReviewFixNitsPrompt(notes, rules), {
|
|
166
|
+
label: "Fixing your nits",
|
|
167
|
+
file: REVIEW,
|
|
168
|
+
model: planner(),
|
|
169
|
+
system: t.reviewerSystem(),
|
|
170
|
+
})
|
|
171
|
+
return access === undefined
|
|
172
|
+
? fixing("review.fix.nits.fixing")
|
|
173
|
+
: scope({ name: "review.fix.nits", access }, () => fixing("fixing"))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
|
|
177
|
+
export const collecting = (capture: string, round?: CollectRound): Promise<void> =>
|
|
178
|
+
t.agentWithSkills("review.collecting", t.buildReviewCollectingPrompt(capture, undefined, round), {
|
|
179
|
+
label: "Collecting your feedback",
|
|
180
|
+
file: REQUIREMENTS,
|
|
181
|
+
mode: "qa",
|
|
182
|
+
model: planner(),
|
|
183
|
+
system: t.reviewerSystem(),
|
|
184
|
+
allowEmpty: true,
|
|
185
|
+
})
|
|
186
|
+
|
|
148
187
|
/** The round at commit `round` left no `.gtd/REVIEW.md`. */
|
|
149
188
|
export const reviewMissing = (round: string): Promise<void> =>
|
|
150
189
|
human("review.review-missing", {
|
|
@@ -190,7 +190,7 @@ const runTail = <T>(finish: Finish<T>, options: TailOptions): Promise<T> =>
|
|
|
190
190
|
})
|
|
191
191
|
|
|
192
192
|
/**
|
|
193
|
-
* The
|
|
193
|
+
* The fix workflow's human review since `base`: a sign-off closes
|
|
194
194
|
* `.gtd/REVIEW.md`; a note leaves it, with the reviewer's own version as
|
|
195
195
|
* `REVIEW_ORIG`, for the next `fix` to read — never a re-plan.
|
|
196
196
|
*/
|
|
@@ -220,6 +220,6 @@ export const handOffReview = (
|
|
|
220
220
|
"gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note to hand to fix.",
|
|
221
221
|
})
|
|
222
222
|
|
|
223
|
-
/** The tail the
|
|
223
|
+
/** The tail the fix workflow runs: ends at the hand-off review since `base`. */
|
|
224
224
|
export const handOffTail = (fixFirst: boolean, base: string, guard?: Guard): Promise<void> =>
|
|
225
225
|
tail((escalations) => handOffReview(base, escalations, guard), { fixFirst, guard })
|
|
@@ -875,6 +875,17 @@ The reviewer's risks are:
|
|
|
875
875
|
|
|
876
876
|
${risks.length === 0 ? "(none)\n" : notesCapture(risks)}`
|
|
877
877
|
|
|
878
|
+
export const reviewFeedbackFixPrompt = (): string =>
|
|
879
|
+
`${stateFileRules}
|
|
880
|
+
- Read \`.gtd/REQUIREMENTS.md\` — the concerns the human's review asked for,
|
|
881
|
+
one \`## \` heading each, in build order
|
|
882
|
+
- Build every concern in this one turn; an answered question is context,
|
|
883
|
+
not a concern
|
|
884
|
+
- Where a change is behavioural, add a test that fails without it
|
|
885
|
+
- Leave \`.gtd/REQUIREMENTS.md\` untouched — the next step removes it
|
|
886
|
+
- Leave everything uncommitted and finish your turn
|
|
887
|
+
`
|
|
888
|
+
|
|
878
889
|
export const buildReviewReviewMissingMessage = (commit: string): string =>
|
|
879
890
|
`The review round committed no \`.gtd/REVIEW.md\` (at ${commit}), so there is
|
|
880
891
|
nothing to sign off on.
|