@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.
Files changed (34) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +17 -13
  3. package/dist/chunks/{reserved-COEMFEpy.mjs → reserved-DJk_6-ju.mjs} +41 -1
  4. package/dist/chunks/review-X7Jfz0dY.mjs +249 -0
  5. package/dist/chunks/{tail-C_xn8U7U.mjs → tail-5w5mxGTq.mjs} +3 -3
  6. package/dist/workflows/cleanup.mjs +2 -2
  7. package/dist/workflows/feature.mjs +6 -278
  8. package/dist/workflows/fix.mjs +2 -2
  9. package/dist/workflows/review.mjs +27 -7
  10. package/dist/workflows/rollout.mjs +1 -1
  11. package/dist/workflows.mjs +74 -44
  12. package/package.json +1 -1
  13. package/src/steering/FORMATS.md +4 -4
  14. package/src/workflows/feature/coverage.ts +1 -1
  15. package/src/workflows/feature/feature.test.ts +8 -0
  16. package/src/workflows/feature/feature.ts +2 -2
  17. package/src/workflows/feature/index.ts +1 -1
  18. package/src/workflows/feature/refactor.ts +1 -1
  19. package/src/workflows/feature/seam.ts +1 -1
  20. package/src/workflows/feature/steps.test.ts +2 -64
  21. package/src/workflows/feature/steps.ts +5 -41
  22. package/src/workflows/fix/fix.feature +20 -21
  23. package/src/workflows/index.ts +1 -1
  24. package/src/workflows/review/review-door.feature +149 -0
  25. package/src/workflows/review/review.test.ts +1 -73
  26. package/src/workflows/review/review.ts +34 -6
  27. package/src/workflows/{feature → shared}/review.test.ts +4 -8
  28. package/src/workflows/{feature → shared}/review.ts +13 -10
  29. package/src/workflows/shared/skills.test.ts +2 -0
  30. package/src/workflows/shared/skills.ts +2 -0
  31. package/src/workflows/shared/steps.test.ts +64 -2
  32. package/src/workflows/shared/steps.ts +40 -1
  33. package/src/workflows/shared/tail.ts +2 -2
  34. 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 { StepRequest } from "../../flows/index.js"
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 review and fix workflows' human review since `base`: a sign-off closes
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 review and fix workflows run: ends at the hand-off review since `base`. */
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.