@pmelab/gtd 15.10.0 → 16.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/README.md +4 -1
- package/dist/gtd.bundle.mjs +271 -62
- package/package.json +1 -1
- package/schema.json +10 -0
- package/src/flows/runtime.ts +13 -0
- package/src/workflows/review.ts +6 -2
- package/src/workflows/skills.test.ts +93 -0
- package/src/workflows/skills.ts +37 -0
- package/src/workflows/steps.test.ts +75 -68
- package/src/workflows/steps.ts +47 -61
- package/src/workflows/text.fixture.ts +36 -0
- package/src/workflows/text.test.ts +21 -28
- package/src/workflows/text.ts +19 -23
- package/src/workflows/unified.ts +1 -0
- package/src/workflows/vars.ts +7 -9
package/schema.json
CHANGED
|
@@ -63,6 +63,16 @@
|
|
|
63
63
|
"additionalProperties": false,
|
|
64
64
|
"description": "Settings for `gtd ui`: where it listens, and optional TLS."
|
|
65
65
|
},
|
|
66
|
+
"skills": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"description": "Flat step full-name -> skill-name array map. Each entry REPLACES the named step's bundled skill list wholesale (never merges into it); [] means no skills at all for that step, and no preamble. A key naming a step the workflow in play does not declare is a load error listing the known names — the schema itself cannot validate a key, only a value's shape.",
|
|
69
|
+
"additionalProperties": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": {
|
|
72
|
+
"type": "string"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
66
76
|
"$schema": {
|
|
67
77
|
"type": "string",
|
|
68
78
|
"description": "Editor-only pointer at this schema. Stripped by gtd before validation."
|
package/src/flows/runtime.ts
CHANGED
|
@@ -138,6 +138,15 @@ export interface FlowContext {
|
|
|
138
138
|
readonly vars: Readonly<Record<string, string>>
|
|
139
139
|
readonly head: () => string
|
|
140
140
|
readonly start: () => string
|
|
141
|
+
/**
|
|
142
|
+
* The skill list `localName` (scoped from here, same as `agent()`) resolves
|
|
143
|
+
* to — for a prompt preamble. `ownSkills`, when given, is the same
|
|
144
|
+
* precedence tier as a call's own `skills` option: it stands in for the
|
|
145
|
+
* bundled default when there isn't one, but a `.gtdrc` entry still beats
|
|
146
|
+
* it. Shares its resolution with `agent()`'s own wire resolver (both read
|
|
147
|
+
* `Workflow.ts`'s `configuredSkills`/`skills`), so the two can never drift.
|
|
148
|
+
*/
|
|
149
|
+
readonly skillsFor: (localName: string, ownSkills?: readonly string[]) => readonly string[]
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
const CONTEXT_KEY = Symbol.for("@pmelab/gtd/flow-context")
|
|
@@ -243,6 +252,10 @@ export const head = (): string => ctx().head()
|
|
|
243
252
|
/** The process's diff base: the commit before it began, or the base `gtd --entry` fixed. */
|
|
244
253
|
export const start = (): string => ctx().start()
|
|
245
254
|
|
|
255
|
+
/** The skill list `localName` (scoped from here, same as `agent()`) resolves to — for a prompt preamble. See `FlowContext.skillsFor` for `ownSkills`. */
|
|
256
|
+
export const skillsFor = (localName: string, ownSkills?: readonly string[]): readonly string[] =>
|
|
257
|
+
ctx().skillsFor(localName, ownSkills)
|
|
258
|
+
|
|
246
259
|
/** The merged workflow variables. */
|
|
247
260
|
export const vars: Readonly<Record<string, string>> = new Proxy(
|
|
248
261
|
{},
|
package/src/workflows/review.ts
CHANGED
|
@@ -35,8 +35,12 @@ import {
|
|
|
35
35
|
} from "./steps.js"
|
|
36
36
|
import * as t from "./text.js"
|
|
37
37
|
|
|
38
|
-
/** The
|
|
39
|
-
export const qualityLenses = (): readonly string[] =>
|
|
38
|
+
/** The lenses the quality lap reviews with, one turn each: the `qualityReviews` var, split on `,` and trimmed. Unlike a `skills:` entry, this fans out into one whole turn per entry rather than naming one step's skill list — see `build.quality.reviewing` in `./skills.ts` for the (separate) skills a lens turn itself loads. */
|
|
39
|
+
export const qualityLenses = (): readonly string[] =>
|
|
40
|
+
(vars.qualityReviews ?? "")
|
|
41
|
+
.split(",")
|
|
42
|
+
.map((lens) => lens.trim())
|
|
43
|
+
.filter((lens) => lens.length > 0)
|
|
40
44
|
|
|
41
45
|
/**
|
|
42
46
|
* One review turn per lens over the whole change, each appending what it
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { skills } from "./skills.js"
|
|
3
|
+
|
|
4
|
+
// Pins every bundled step's own declared list, keyed by its full name. This
|
|
5
|
+
// file alone only catches a LOCAL-name rename (the list literally changes);
|
|
6
|
+
// it cannot catch a SCOPE rename (e.g. `scope("architecture")` renamed to
|
|
7
|
+
// `scope("design.architecture")`), because it never runs the real flow — it
|
|
8
|
+
// compares the map against itself. Every one of the sixteen keys has its
|
|
9
|
+
// own e2e grounding closing that gap instead — each runs the actual scoped
|
|
10
|
+
// name through replay, so a scope rename desyncs a preamble assertion there,
|
|
11
|
+
// not just silently drops a step's skills (see AGENTS.md's "workflow is
|
|
12
|
+
// code" rule: a step's full name is public API):
|
|
13
|
+
// - design.triage, architecture.author, architecture.decompose,
|
|
14
|
+
// packages.item.building — default-workflow.feature's main scenario
|
|
15
|
+
// - packages.item.spec.review, packages.item.fix-spec — the same
|
|
16
|
+
// scenario, continued
|
|
17
|
+
// - packages.item.fix-suite, packages.item.health.describe — the
|
|
18
|
+
// "escalation loop ... under packages.item" scenario
|
|
19
|
+
// - build.fix — state-skills.feature
|
|
20
|
+
// - build.health.describe — the "repeated check failures escalate"
|
|
21
|
+
// scenario
|
|
22
|
+
// - build.quality.reviewing — per-step-skills.feature
|
|
23
|
+
// - build.fix-quality — quality-review-lap.feature
|
|
24
|
+
// - build.review.reviewing, build.review.collecting —
|
|
25
|
+
// default-workflow.feature's review-loop scenarios
|
|
26
|
+
// - build.review.answer-review-questions, build.review.fix-nits — NOT yet
|
|
27
|
+
// e2e-grounded; only steps.test.ts's preamble checks, which use local names
|
|
28
|
+
describe("the bundled workflow's skills map", () => {
|
|
29
|
+
it("declares exactly the sixteen bundled agent steps, by full name", () => {
|
|
30
|
+
expect(Object.keys(skills).sort()).toEqual(
|
|
31
|
+
[
|
|
32
|
+
"design.triage",
|
|
33
|
+
"architecture.author",
|
|
34
|
+
"architecture.decompose",
|
|
35
|
+
"packages.item.building",
|
|
36
|
+
"packages.item.fix-suite",
|
|
37
|
+
"packages.item.fix-spec",
|
|
38
|
+
"packages.item.spec.review",
|
|
39
|
+
"packages.item.health.describe",
|
|
40
|
+
"build.fix",
|
|
41
|
+
"build.health.describe",
|
|
42
|
+
"build.quality.reviewing",
|
|
43
|
+
"build.fix-quality",
|
|
44
|
+
"build.review.reviewing",
|
|
45
|
+
"build.review.answer-review-questions",
|
|
46
|
+
"build.review.fix-nits",
|
|
47
|
+
"build.review.collecting",
|
|
48
|
+
].sort(),
|
|
49
|
+
)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("packages.item.health.describe and build.health.describe are both present, with the same list — the shared escalation loop resolves under both scopes", () => {
|
|
53
|
+
expect(skills["packages.item.health.describe"]).toEqual(skills["build.health.describe"])
|
|
54
|
+
expect(skills["packages.item.health.describe"]).toEqual(["debugging-and-error-recovery"])
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("pins each step's own declared list", () => {
|
|
58
|
+
expect(skills["design.triage"]).toEqual([
|
|
59
|
+
"spec-driven-development",
|
|
60
|
+
"planning-and-task-breakdown",
|
|
61
|
+
])
|
|
62
|
+
expect(skills["architecture.author"]).toEqual([
|
|
63
|
+
"api-and-interface-design",
|
|
64
|
+
"documentation-and-adrs",
|
|
65
|
+
"ponytail",
|
|
66
|
+
])
|
|
67
|
+
expect(skills["architecture.decompose"]).toEqual([
|
|
68
|
+
"incremental-implementation",
|
|
69
|
+
"planning-and-task-breakdown",
|
|
70
|
+
])
|
|
71
|
+
expect(skills["packages.item.building"]).toEqual([
|
|
72
|
+
"test-driven-development",
|
|
73
|
+
"incremental-implementation",
|
|
74
|
+
])
|
|
75
|
+
expect(skills["packages.item.fix-suite"]).toEqual(["debugging-and-error-recovery"])
|
|
76
|
+
expect(skills["build.fix"]).toEqual(["debugging-and-error-recovery"])
|
|
77
|
+
expect(skills["packages.item.fix-spec"]).toEqual([
|
|
78
|
+
"incremental-implementation",
|
|
79
|
+
"code-simplification",
|
|
80
|
+
])
|
|
81
|
+
expect(skills["build.fix-quality"]).toEqual([
|
|
82
|
+
"incremental-implementation",
|
|
83
|
+
"code-simplification",
|
|
84
|
+
])
|
|
85
|
+
expect(skills["packages.item.spec.review"]).toEqual([
|
|
86
|
+
"code-review-and-quality",
|
|
87
|
+
"spec-driven-development",
|
|
88
|
+
])
|
|
89
|
+
expect(skills["build.review.reviewing"]).toEqual(["code-review-and-quality"])
|
|
90
|
+
expect(skills["build.review.collecting"]).toEqual(["code-review-and-quality"])
|
|
91
|
+
expect(skills["build.quality.reviewing"]).toEqual([])
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The bundled workflow's default skills, one list per agent step, keyed by
|
|
2
|
+
// the step's FULL name (its scope prefixes included) — the same name a
|
|
3
|
+
// `.gtdrc` `skills:` entry addresses. A step's name is not derivable from
|
|
4
|
+
// flow code alone (loops, branches, shared calls), so this map is the only
|
|
5
|
+
// place every addressable name is declared up front; `steps.test.ts` pins
|
|
6
|
+
// each step's declared list against a renamed map key.
|
|
7
|
+
//
|
|
8
|
+
// `packages.item.health.describe` and `build.health.describe` are not a
|
|
9
|
+
// duplicate: the escalation loop (`healthy()`, in `./health.ts`) is shared
|
|
10
|
+
// code reached from both `scope("packages") -> scope("item")` and
|
|
11
|
+
// `scope("build")`, so the one `health.describe` step name resolves to two
|
|
12
|
+
// different full names depending on which caller is looping.
|
|
13
|
+
export const skills: Readonly<Record<string, readonly string[]>> = {
|
|
14
|
+
"design.triage": ["spec-driven-development", "planning-and-task-breakdown"],
|
|
15
|
+
"architecture.author": ["api-and-interface-design", "documentation-and-adrs", "ponytail"],
|
|
16
|
+
"architecture.decompose": ["incremental-implementation", "planning-and-task-breakdown"],
|
|
17
|
+
"packages.item.building": ["test-driven-development", "incremental-implementation"],
|
|
18
|
+
"packages.item.fix-suite": ["debugging-and-error-recovery"],
|
|
19
|
+
"packages.item.fix-spec": ["incremental-implementation", "code-simplification"],
|
|
20
|
+
"packages.item.spec.review": ["code-review-and-quality", "spec-driven-development"],
|
|
21
|
+
"packages.item.health.describe": ["debugging-and-error-recovery"],
|
|
22
|
+
"build.fix": ["debugging-and-error-recovery"],
|
|
23
|
+
"build.health.describe": ["debugging-and-error-recovery"],
|
|
24
|
+
// No bundled default: unlike every other entry, the lens named in the
|
|
25
|
+
// prompt BODY already varies per turn (see `reviewQuality` in
|
|
26
|
+
// `./steps.ts`), which is what the preamble falls back to naming instead —
|
|
27
|
+
// a fixed bundled list here would repeat one entry on every turn. A
|
|
28
|
+
// `.gtdrc` `skills:` entry still overrides that per-turn fallback, on the
|
|
29
|
+
// wire and in the preamble alike (`resolveSkills` in `../replay/Replay.ts`
|
|
30
|
+
// is what both share).
|
|
31
|
+
"build.quality.reviewing": [],
|
|
32
|
+
"build.fix-quality": ["incremental-implementation", "code-simplification"],
|
|
33
|
+
"build.review.reviewing": ["code-review-and-quality"],
|
|
34
|
+
"build.review.answer-review-questions": ["code-review-and-quality"],
|
|
35
|
+
"build.review.fix-nits": ["incremental-implementation", "code-simplification"],
|
|
36
|
+
"build.review.collecting": ["code-review-and-quality"],
|
|
37
|
+
}
|
|
@@ -5,107 +5,114 @@ import * as steps from "./steps.js"
|
|
|
5
5
|
|
|
6
6
|
const capture = (
|
|
7
7
|
fn: () => Promise<void>,
|
|
8
|
-
|
|
9
|
-
): Promise<StepRequest> => captureStep(fn, {
|
|
8
|
+
skills?: Readonly<Record<string, readonly string[]>>,
|
|
9
|
+
): Promise<StepRequest> => captureStep(fn, skills !== undefined ? { skills } : {})
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const agentPrompt = (request: StepRequest): string => {
|
|
12
|
+
if (request.kind !== "agent") throw new Error(`expected an agent step, got ${request.kind}`)
|
|
13
|
+
return request.prompt
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// The preamble (`agentPrompt`) reflects `skillsFor`'s resolved map — but the
|
|
17
|
+
// WIRE `skills` field a request actually carries is whatever `options.skills`
|
|
18
|
+
// the step itself passed; `Replay.ts`'s `resolve()` only overrides it from
|
|
19
|
+
// `.gtdrc` (never reachable here — this fixture skips replay entirely). Most
|
|
20
|
+
// bundled steps pass no `skills` option of their own, so this is normally
|
|
21
|
+
// `undefined`; `reviewQuality` is the one step that does.
|
|
22
|
+
const agentSkillsOption = (request: StepRequest): readonly string[] | undefined => {
|
|
12
23
|
if (request.kind !== "agent") throw new Error(`expected an agent step, got ${request.kind}`)
|
|
13
24
|
return request.options.skills
|
|
14
25
|
}
|
|
15
26
|
|
|
16
|
-
describe("the bundled workflow's steps declare skills", () => {
|
|
17
|
-
it("triage
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
describe("the bundled workflow's steps declare skills — a bundled step's rendered prompt carries its bundled skills.ts preamble", () => {
|
|
28
|
+
it("triage carries design.triage's bundled skills", async () => {
|
|
29
|
+
const prompt = agentPrompt(await capture(() => steps.triage("base")))
|
|
30
|
+
expect(prompt).toContain("spec-driven-development, planning-and-task-breakdown")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("author carries architecture.author's bundled skills", async () => {
|
|
34
|
+
const prompt = agentPrompt(await capture(() => steps.author()))
|
|
35
|
+
expect(prompt).toContain("api-and-interface-design, documentation-and-adrs, ponytail")
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("decompose carries architecture.decompose's bundled skills", async () => {
|
|
39
|
+
const prompt = agentPrompt(await capture(() => steps.decompose()))
|
|
40
|
+
expect(prompt).toContain("incremental-implementation, planning-and-task-breakdown")
|
|
22
41
|
})
|
|
23
42
|
|
|
24
|
-
it("
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"documentation-and-adrs",
|
|
28
|
-
"ponytail",
|
|
29
|
-
])
|
|
43
|
+
it("build carries packages.item.building's bundled skills", async () => {
|
|
44
|
+
const prompt = agentPrompt(await capture(() => steps.build("pkg")))
|
|
45
|
+
expect(prompt).toContain("test-driven-development, incremental-implementation")
|
|
30
46
|
})
|
|
31
47
|
|
|
32
|
-
it("
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"planning-and-task-breakdown",
|
|
36
|
-
])
|
|
48
|
+
it("fixSuite carries packages.item.fix-suite's bundled skills", async () => {
|
|
49
|
+
const prompt = agentPrompt(await capture(() => steps.fixSuite()))
|
|
50
|
+
expect(prompt).toContain("debugging-and-error-recovery")
|
|
37
51
|
})
|
|
38
52
|
|
|
39
|
-
it("
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"incremental-implementation",
|
|
43
|
-
])
|
|
53
|
+
it("fixSpec carries packages.item.fix-spec's bundled skills", async () => {
|
|
54
|
+
const prompt = agentPrompt(await capture(() => steps.fixSpec("pkg")))
|
|
55
|
+
expect(prompt).toContain("incremental-implementation, code-simplification")
|
|
44
56
|
})
|
|
45
57
|
|
|
46
|
-
it("
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
])
|
|
58
|
+
it("reviewPackage carries packages.item.spec.review's bundled skills", async () => {
|
|
59
|
+
const prompt = agentPrompt(await capture(() => steps.reviewPackage("pkg")))
|
|
60
|
+
expect(prompt).toContain("code-review-and-quality, spec-driven-development")
|
|
50
61
|
})
|
|
51
62
|
|
|
52
|
-
it("
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"code-simplification",
|
|
56
|
-
])
|
|
63
|
+
it("fix carries build.fix's bundled skills", async () => {
|
|
64
|
+
const prompt = agentPrompt(await capture(() => steps.fix()))
|
|
65
|
+
expect(prompt).toContain("debugging-and-error-recovery")
|
|
57
66
|
})
|
|
58
67
|
|
|
59
|
-
it("
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"spec-driven-development",
|
|
63
|
-
])
|
|
68
|
+
it("describeEscalation carries health.describe's bundled skills", async () => {
|
|
69
|
+
const prompt = agentPrompt(await capture(() => steps.describeEscalation()))
|
|
70
|
+
expect(prompt).toContain("debugging-and-error-recovery")
|
|
64
71
|
})
|
|
65
72
|
|
|
66
|
-
it("
|
|
67
|
-
|
|
73
|
+
it("reviewQuality's preamble falls back to the lens itself by default — build.quality.reviewing bundles no fixed skills of its own", async () => {
|
|
74
|
+
const prompt = agentPrompt(await capture(() => steps.reviewQuality("owasp-security")))
|
|
75
|
+
expect(prompt).toContain("missing one: owasp-security")
|
|
68
76
|
})
|
|
69
77
|
|
|
70
|
-
it("
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
])
|
|
78
|
+
it("reviewQuality's own wire skills option IS the lens by default — the same list its preamble now names, never one without the other", async () => {
|
|
79
|
+
const request = await capture(() => steps.reviewQuality("owasp-security"))
|
|
80
|
+
expect(agentSkillsOption(request)).toEqual(["owasp-security"])
|
|
74
81
|
})
|
|
75
82
|
|
|
76
|
-
it("reviewQuality
|
|
77
|
-
|
|
78
|
-
"owasp-security",
|
|
79
|
-
|
|
83
|
+
it("reviewQuality's preamble reflects a configured build.quality.reviewing entry, every turn, regardless of lens", async () => {
|
|
84
|
+
const prompt = agentPrompt(
|
|
85
|
+
await capture(() => steps.reviewQuality("owasp-security"), {
|
|
86
|
+
"quality.reviewing": ["my-org-checklist"],
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
expect(prompt).toContain("my-org-checklist")
|
|
90
|
+
// The body still names the lens — what makes N otherwise identical prompts distinguishable.
|
|
91
|
+
expect(prompt).toContain("owasp-security")
|
|
80
92
|
})
|
|
81
93
|
|
|
82
|
-
it("fixQuality
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"code-simplification",
|
|
86
|
-
])
|
|
94
|
+
it("fixQuality carries build.fix-quality's bundled skills", async () => {
|
|
95
|
+
const prompt = agentPrompt(await capture(() => steps.fixQuality()))
|
|
96
|
+
expect(prompt).toContain("incremental-implementation, code-simplification")
|
|
87
97
|
})
|
|
88
98
|
|
|
89
|
-
it("reviewing
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
])
|
|
99
|
+
it("reviewing carries build.review.reviewing's bundled skills", async () => {
|
|
100
|
+
const prompt = agentPrompt(await capture(() => steps.reviewing("base")))
|
|
101
|
+
expect(prompt).toContain("code-review-and-quality")
|
|
93
102
|
})
|
|
94
103
|
|
|
95
|
-
it("collecting
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
])
|
|
104
|
+
it("collecting carries build.review.collecting's bundled skills", async () => {
|
|
105
|
+
const prompt = agentPrompt(await capture(() => steps.collecting("capture")))
|
|
106
|
+
expect(prompt).toContain("code-review-and-quality")
|
|
99
107
|
})
|
|
100
108
|
|
|
101
|
-
it("
|
|
102
|
-
const request = await capture(() => steps.fix(), {
|
|
109
|
+
it("an empty configured list leaves that step's skills empty, and its rendered prompt bare", async () => {
|
|
110
|
+
const request = await capture(() => steps.fix(), { fix: [] })
|
|
103
111
|
if (request.kind !== "agent") throw new Error("unreachable")
|
|
104
|
-
expect(request.options.skills).toEqual([])
|
|
105
112
|
expect(request.prompt).not.toContain("Load whatever's listed here")
|
|
106
113
|
})
|
|
107
114
|
|
|
108
|
-
it("answerReviewQuestions answers inline in .gtd/REVIEW.md as the reviewer, with
|
|
115
|
+
it("answerReviewQuestions answers inline in .gtd/REVIEW.md as the reviewer, with its bundled skills", async () => {
|
|
109
116
|
const request = await capture(() =>
|
|
110
117
|
steps.answerReviewQuestions([{ id: "note-1", anchor: "calc ./a.ts#1-1", text: "why?" }]),
|
|
111
118
|
)
|
|
@@ -114,8 +121,8 @@ describe("the bundled workflow's steps declare skills", () => {
|
|
|
114
121
|
expect(request.options).toMatchObject({
|
|
115
122
|
file: ".gtd/REVIEW.md",
|
|
116
123
|
mode: "review",
|
|
117
|
-
skills: ["code-review-and-quality"],
|
|
118
124
|
})
|
|
125
|
+
expect(request.prompt).toContain("code-review-and-quality")
|
|
119
126
|
expect(request.prompt).toContain("note-1 — calc ./a.ts#1-1")
|
|
120
127
|
expect(request.prompt).toContain("`A: ` line")
|
|
121
128
|
expect(request.prompt).toContain("gtd check review .gtd/REVIEW.md")
|
|
@@ -130,7 +137,7 @@ describe("the bundled workflow's steps declare skills", () => {
|
|
|
130
137
|
)
|
|
131
138
|
if (request.kind !== "agent") throw new Error("unreachable")
|
|
132
139
|
expect(request.name).toBe("review.fix-nits")
|
|
133
|
-
expect(request.
|
|
140
|
+
expect(request.prompt).toContain("incremental-implementation, code-simplification")
|
|
134
141
|
expect(request.prompt).toContain("typo")
|
|
135
142
|
expect(request.prompt).toContain("semicolon")
|
|
136
143
|
expect(request.prompt).toContain("Leave `.gtd/REVIEW.md` untouched")
|
package/src/workflows/steps.ts
CHANGED
|
@@ -21,7 +21,7 @@ const coder = (): string => vars.coderModel ?? ""
|
|
|
21
21
|
|
|
22
22
|
/** Turn the sketch since `base` into `.gtd/REQUIREMENTS.md`. */
|
|
23
23
|
export const triage = (base: string): Promise<void> =>
|
|
24
|
-
t.agentWithSkills("triage",
|
|
24
|
+
t.agentWithSkills("triage", t.designTriagePrompt(base), {
|
|
25
25
|
label: "Triaging the change",
|
|
26
26
|
file: REQUIREMENTS,
|
|
27
27
|
mode: "qa",
|
|
@@ -30,7 +30,7 @@ export const triage = (base: string): Promise<void> =>
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
export const author = (): Promise<void> =>
|
|
33
|
-
t.agentWithSkills("author",
|
|
33
|
+
t.agentWithSkills("author", t.architectureAuthorPrompt(), {
|
|
34
34
|
label: "Refining the technical plan",
|
|
35
35
|
file: ARCHITECTURE,
|
|
36
36
|
mode: "qa",
|
|
@@ -39,7 +39,7 @@ export const author = (): Promise<void> =>
|
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
export const decompose = (): Promise<void> =>
|
|
42
|
-
t.agentWithSkills("decompose",
|
|
42
|
+
t.agentWithSkills("decompose", t.architectureDecomposePrompt(), {
|
|
43
43
|
label: "Decomposing into packages",
|
|
44
44
|
model: planner(),
|
|
45
45
|
system: t.architectSystem(),
|
|
@@ -66,14 +66,14 @@ export const answerTechnicalQuestions = (): Promise<void> =>
|
|
|
66
66
|
// ── Packages ────────────────────────────────────────────────────────────────
|
|
67
67
|
|
|
68
68
|
export const build = (pkg: string): Promise<void> =>
|
|
69
|
-
t.agentWithSkills("building",
|
|
69
|
+
t.agentWithSkills("building", t.packagesItemBuildingPrompt(pkg), {
|
|
70
70
|
label: "Building",
|
|
71
71
|
model: coder(),
|
|
72
72
|
system: t.builderSystem(),
|
|
73
73
|
})
|
|
74
74
|
|
|
75
75
|
export const fixSuite = (): Promise<void> =>
|
|
76
|
-
t.agentWithSkills("fix-suite",
|
|
76
|
+
t.agentWithSkills("fix-suite", t.packagesItemFixSuitePrompt(), {
|
|
77
77
|
label: "Fixing the check",
|
|
78
78
|
file: FEEDBACK,
|
|
79
79
|
model: coder(),
|
|
@@ -81,7 +81,7 @@ export const fixSuite = (): Promise<void> =>
|
|
|
81
81
|
})
|
|
82
82
|
|
|
83
83
|
export const fixSpec = (pkg: string): Promise<void> =>
|
|
84
|
-
t.agentWithSkills("fix-spec",
|
|
84
|
+
t.agentWithSkills("fix-spec", t.packagesItemFixSpecPrompt(pkg), {
|
|
85
85
|
label: "Fixing review feedback",
|
|
86
86
|
file: SPEC_FEEDBACK,
|
|
87
87
|
model: coder(),
|
|
@@ -90,22 +90,17 @@ export const fixSpec = (pkg: string): Promise<void> =>
|
|
|
90
90
|
|
|
91
91
|
/** Review `pkg` against its spec, focused on the `failing` sections the pre-judge could not clear. */
|
|
92
92
|
export const reviewPackage = (pkg: string, failing: readonly string[] = []): Promise<void> =>
|
|
93
|
-
t.agentWithSkills(
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
t.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
model: planner(),
|
|
100
|
-
system: t.specReviewerSystem(),
|
|
101
|
-
allowEmpty: true,
|
|
102
|
-
},
|
|
103
|
-
)
|
|
93
|
+
t.agentWithSkills("spec.review", t.packagesItemSpecReviewPrompt(pkg, failing), {
|
|
94
|
+
label: "Reviewing the package",
|
|
95
|
+
model: planner(),
|
|
96
|
+
system: t.specReviewerSystem(),
|
|
97
|
+
allowEmpty: true,
|
|
98
|
+
})
|
|
104
99
|
|
|
105
100
|
// ── Keeping the suite green ─────────────────────────────────────────────────
|
|
106
101
|
|
|
107
102
|
export const fix = (): Promise<void> =>
|
|
108
|
-
t.agentWithSkills("fix",
|
|
103
|
+
t.agentWithSkills("fix", t.buildFixPrompt(), {
|
|
109
104
|
label: "Fixing the check",
|
|
110
105
|
file: FEEDBACK,
|
|
111
106
|
model: coder(),
|
|
@@ -113,7 +108,7 @@ export const fix = (): Promise<void> =>
|
|
|
113
108
|
})
|
|
114
109
|
|
|
115
110
|
export const describeEscalation = (): Promise<void> =>
|
|
116
|
-
t.agentWithSkills("health.describe",
|
|
111
|
+
t.agentWithSkills("health.describe", t.healthDescribePrompt(), {
|
|
117
112
|
label: "Describing the escalation",
|
|
118
113
|
file: FEEDBACK,
|
|
119
114
|
model: coder(),
|
|
@@ -139,18 +134,24 @@ export const escalationExhausted = (): Promise<void> =>
|
|
|
139
134
|
|
|
140
135
|
// ── Quality and review ──────────────────────────────────────────────────────
|
|
141
136
|
|
|
142
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* One quality review, through the skill `lens`. `lens` rides as this turn's
|
|
139
|
+
* own `skills` option — a `.gtdrc` `build.quality.reviewing` entry still
|
|
140
|
+
* overrides it (config beats a flow-supplied list same as any other step),
|
|
141
|
+
* but absent one the lens itself is what the turn loads by default.
|
|
142
|
+
*/
|
|
143
143
|
export const reviewQuality = (lens: string): Promise<void> =>
|
|
144
|
-
t.agentWithSkills("quality.reviewing",
|
|
144
|
+
t.agentWithSkills("quality.reviewing", t.buildQualityReviewingPrompt(lens), {
|
|
145
145
|
label: "Reviewing (one quality lens)",
|
|
146
146
|
file: QUALITY,
|
|
147
147
|
model: planner(),
|
|
148
148
|
system: t.reviewerSystem(),
|
|
149
149
|
allowEmpty: true,
|
|
150
|
+
skills: [lens],
|
|
150
151
|
})
|
|
151
152
|
|
|
152
153
|
export const fixQuality = (): Promise<void> =>
|
|
153
|
-
t.agentWithSkills("fix-quality",
|
|
154
|
+
t.agentWithSkills("fix-quality", t.buildFixQualityPrompt(), {
|
|
154
155
|
label: "Fixing quality findings",
|
|
155
156
|
file: QUALITY,
|
|
156
157
|
model: coder(),
|
|
@@ -160,41 +161,31 @@ export const fixQuality = (): Promise<void> =>
|
|
|
160
161
|
|
|
161
162
|
/** Write `.gtd/REVIEW.md` over everything since `base`, carrying the answers of commit `carry` when given. */
|
|
162
163
|
export const reviewing = (base: string, carry?: string): Promise<void> =>
|
|
163
|
-
t.agentWithSkills(
|
|
164
|
-
"
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
model: planner(),
|
|
172
|
-
system: t.reviewerSystem(),
|
|
173
|
-
base,
|
|
174
|
-
},
|
|
175
|
-
)
|
|
164
|
+
t.agentWithSkills("review.reviewing", t.buildReviewReviewingPrompt(base, carry), {
|
|
165
|
+
label: "Reviewing",
|
|
166
|
+
file: REVIEW,
|
|
167
|
+
mode: "review",
|
|
168
|
+
model: planner(),
|
|
169
|
+
system: t.reviewerSystem(),
|
|
170
|
+
base,
|
|
171
|
+
})
|
|
176
172
|
|
|
177
173
|
/** Answer every `question` note inline in `.gtd/REVIEW.md`. */
|
|
178
174
|
export const answerReviewQuestions = (notes: readonly t.NoteInput[]): Promise<void> =>
|
|
179
|
-
t.agentWithSkills(
|
|
180
|
-
"
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
mode: "review",
|
|
187
|
-
model: planner(),
|
|
188
|
-
system: t.reviewerSystem(),
|
|
189
|
-
},
|
|
190
|
-
)
|
|
175
|
+
t.agentWithSkills("review.answer-review-questions", t.buildReviewAnswerQuestionsPrompt(notes), {
|
|
176
|
+
label: "Answering your questions",
|
|
177
|
+
file: REVIEW,
|
|
178
|
+
mode: "review",
|
|
179
|
+
model: planner(),
|
|
180
|
+
system: t.reviewerSystem(),
|
|
181
|
+
})
|
|
191
182
|
|
|
192
183
|
/**
|
|
193
184
|
* Fix every `nit` note in one turn. Its name puts it in the `build.review`
|
|
194
185
|
* conversation, which has one identity — so it runs as the reviewer, not the coder.
|
|
195
186
|
*/
|
|
196
187
|
export const fixNits = (notes: readonly t.NoteInput[]): Promise<void> =>
|
|
197
|
-
t.agentWithSkills("review.fix-nits",
|
|
188
|
+
t.agentWithSkills("review.fix-nits", t.buildReviewFixNitsPrompt(notes), {
|
|
198
189
|
label: "Fixing your nits",
|
|
199
190
|
file: REVIEW,
|
|
200
191
|
model: planner(),
|
|
@@ -219,16 +210,11 @@ export const reviewMissing = (round: string): Promise<void> =>
|
|
|
219
210
|
|
|
220
211
|
/** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
|
|
221
212
|
export const collecting = (capture: string): Promise<void> =>
|
|
222
|
-
t.agentWithSkills(
|
|
223
|
-
"
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
model: planner(),
|
|
231
|
-
system: t.reviewerSystem(),
|
|
232
|
-
allowEmpty: true,
|
|
233
|
-
},
|
|
234
|
-
)
|
|
213
|
+
t.agentWithSkills("review.collecting", t.buildReviewCollectingPrompt(capture), {
|
|
214
|
+
label: "Collecting your feedback",
|
|
215
|
+
file: REQUIREMENTS,
|
|
216
|
+
mode: "qa",
|
|
217
|
+
model: planner(),
|
|
218
|
+
system: t.reviewerSystem(),
|
|
219
|
+
allowEmpty: true,
|
|
220
|
+
})
|