@pmelab/gtd 15.10.0 → 17.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 +12 -4
- package/dist/gtd.bundle.mjs +599 -165
- package/package.json +1 -1
- package/schema.json +43 -1
- package/src/flows/runtime.ts +40 -13
- package/src/workflows/health.ts +3 -2
- 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 +50 -64
- package/src/workflows/text.fixture.ts +39 -1
- package/src/workflows/text.test.ts +22 -28
- package/src/workflows/text.ts +21 -25
- package/src/workflows/unified.ts +2 -1
- package/src/workflows/vars.ts +18 -14
package/src/workflows/steps.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { env, human } from "../flows/index.js"
|
|
2
2
|
import * as t from "./text.js"
|
|
3
3
|
|
|
4
4
|
// The bundled workflow's single steps. A step's name is relative to the
|
|
@@ -14,14 +14,14 @@ export const REVIEW = ".gtd/REVIEW.md"
|
|
|
14
14
|
export const QUALITY = ".gtd/QUALITY.md"
|
|
15
15
|
export const SPEC_FEEDBACK = ".gtd/SPEC_FEEDBACK.md"
|
|
16
16
|
|
|
17
|
-
const planner = (): string =>
|
|
18
|
-
const coder = (): string =>
|
|
17
|
+
const planner = (): string => env.plannerModel ?? ""
|
|
18
|
+
const coder = (): string => env.coderModel ?? ""
|
|
19
19
|
|
|
20
20
|
// ── Planning ────────────────────────────────────────────────────────────────
|
|
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
|
+
})
|
|
@@ -4,20 +4,56 @@ import {
|
|
|
4
4
|
type FlowContext,
|
|
5
5
|
type StepRequest,
|
|
6
6
|
} from "../flows/index.js"
|
|
7
|
-
import {
|
|
7
|
+
import { skills as bundledSkills } from "./skills.js"
|
|
8
|
+
import { defaults, envDefaults } from "./vars.js"
|
|
8
9
|
|
|
9
10
|
export interface TextContext {
|
|
10
11
|
readonly vars?: Readonly<Record<string, string>>
|
|
12
|
+
readonly env?: Readonly<Record<string, string>>
|
|
11
13
|
readonly head?: string
|
|
12
14
|
readonly start?: string
|
|
13
15
|
readonly codeThreads?: readonly CodeThreadInfo[]
|
|
14
16
|
readonly read?: (path: string) => string | undefined
|
|
17
|
+
/**
|
|
18
|
+
* A PER-KEY `skillsFor` override, keyed by the LOCAL name a bundled step
|
|
19
|
+
* calls itself with (`captureStep`/`renderText` push no scopes, so a local
|
|
20
|
+
* name is also the only name here) — stands in for a `.gtdrc` entry, so
|
|
21
|
+
* only the key it names is overridden; every other step still falls back
|
|
22
|
+
* to the call's own `skills` option, then the bundled map (`./skills.ts`),
|
|
23
|
+
* so an existing step test keeps its skills without having to declare them
|
|
24
|
+
* itself.
|
|
25
|
+
*/
|
|
26
|
+
readonly skills?: Readonly<Record<string, readonly string[]>>
|
|
15
27
|
}
|
|
16
28
|
|
|
17
29
|
const unavailable = (): never => {
|
|
18
30
|
throw new Error("not available while rendering a text outside a replay")
|
|
19
31
|
}
|
|
20
32
|
|
|
33
|
+
/** The bundled map is keyed by FULL name, but a bundled step calls itself by its own LOCAL name and this fixture pushes no scope to prefix it with — so the default lookup matches on the local name alone, or as the last `.`-separated segment of a bundled key. `health.describe`'s two bundled entries (`packages.item.health.describe`, `build.health.describe`) share one list, so the ambiguity this leaves resolves to the same answer either way. */
|
|
34
|
+
const bundledSkillsFor = (localName: string): readonly string[] => {
|
|
35
|
+
const exact = bundledSkills[localName]
|
|
36
|
+
if (exact !== undefined) return exact
|
|
37
|
+
const suffix = `.${localName}`
|
|
38
|
+
const hit = Object.entries(bundledSkills).find(([key]) => key.endsWith(suffix))
|
|
39
|
+
return hit?.[1] ?? []
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Mirrors `Replay.ts`'s `resolveSkills` three-way precedence: `context.skills`
|
|
44
|
+
* (a per-key override, standing in for `.gtdrc`) beats `ownSkills` (a call's
|
|
45
|
+
* own `skills` option), which beats the bundled default — so a step test that
|
|
46
|
+
* passes its own `skills` option (like `reviewQuality`'s lens) sees it in the
|
|
47
|
+
* preamble too, the same as a real replay would.
|
|
48
|
+
*/
|
|
49
|
+
const skillsForOf =
|
|
50
|
+
(context: TextContext) =>
|
|
51
|
+
(localName: string, ownSkills?: readonly string[]): readonly string[] => {
|
|
52
|
+
const configured = context.skills?.[localName]
|
|
53
|
+
if (configured !== undefined) return configured
|
|
54
|
+
return ownSkills ?? bundledSkillsFor(localName)
|
|
55
|
+
}
|
|
56
|
+
|
|
21
57
|
/** The fixed replay context texts render against; steps, refusals and scopes are unavailable unless overridden. */
|
|
22
58
|
export const fixtureContext = (
|
|
23
59
|
context: TextContext = {},
|
|
@@ -38,8 +74,10 @@ export const fixtureContext = (
|
|
|
38
74
|
threads: () => [],
|
|
39
75
|
codeThreads: () => context.codeThreads ?? [],
|
|
40
76
|
vars: { ...defaults, ...context.vars },
|
|
77
|
+
env: { ...envDefaults, ...context.env },
|
|
41
78
|
head: () => context.head ?? "",
|
|
42
79
|
start: () => context.start ?? "",
|
|
80
|
+
skillsFor: skillsForOf(context),
|
|
43
81
|
...overrides,
|
|
44
82
|
})
|
|
45
83
|
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
buildReviewCollectingPrompt,
|
|
9
9
|
designGateAnswerMessage,
|
|
10
10
|
designTriagePrompt,
|
|
11
|
-
splitSkills,
|
|
12
11
|
summaryPrompt,
|
|
13
12
|
withSkills,
|
|
14
13
|
} from "./text.js"
|
|
@@ -30,42 +29,36 @@ describe("withSkills", () => {
|
|
|
30
29
|
})
|
|
31
30
|
})
|
|
32
31
|
|
|
33
|
-
describe("splitSkills", () => {
|
|
34
|
-
it("splits on comma, trims each name, drops empty entries", () => {
|
|
35
|
-
expect(splitSkills("a, b")).toEqual(["a", "b"])
|
|
36
|
-
expect(splitSkills(" a ,, b ")).toEqual(["a", "b"])
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it("returns [] for undefined, empty, or blank input", () => {
|
|
40
|
-
expect(splitSkills(undefined)).toEqual([])
|
|
41
|
-
expect(splitSkills("")).toEqual([])
|
|
42
|
-
expect(splitSkills(" ")).toEqual([])
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
|
|
46
32
|
describe("agentWithSkills", () => {
|
|
47
|
-
const capture = (fn: () => Promise<void
|
|
33
|
+
const capture = (fn: () => Promise<void>, skills?: Readonly<Record<string, readonly string[]>>) =>
|
|
34
|
+
captureStep(fn, skills !== undefined ? { skills } : {})
|
|
48
35
|
|
|
49
|
-
it("
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
36
|
+
it("puts skillsFor(name)'s list, joined, in the preamble", async () => {
|
|
37
|
+
const request = await capture(() => agentWithSkills("name", "do-the-work"), {
|
|
38
|
+
name: ["code-review", "testing"],
|
|
39
|
+
})
|
|
40
|
+
if (request.kind !== "agent") throw new Error("unreachable")
|
|
41
|
+
expect(request.prompt).toBe(withSkills("code-review, testing", "do-the-work"))
|
|
54
42
|
})
|
|
55
43
|
|
|
56
|
-
it("passes
|
|
57
|
-
const request = await capture(() =>
|
|
58
|
-
agentWithSkills("name", "code-review, testing", "do-the-work"),
|
|
59
|
-
)
|
|
44
|
+
it("passes no skills option itself — the replay resolver fills the wire field", async () => {
|
|
45
|
+
const request = await capture(() => agentWithSkills("name", "do-the-work"))
|
|
60
46
|
if (request.kind !== "agent") throw new Error("unreachable")
|
|
61
|
-
expect(request.options.skills).
|
|
47
|
+
expect(request.options.skills).toBeUndefined()
|
|
62
48
|
})
|
|
63
49
|
|
|
64
|
-
it("leaves the prompt bare
|
|
65
|
-
const request = await capture(() => agentWithSkills("name",
|
|
50
|
+
it("leaves the prompt bare when the name resolves to no configured skills", async () => {
|
|
51
|
+
const request = await capture(() => agentWithSkills("name", "do-the-work"), { name: [] })
|
|
66
52
|
if (request.kind !== "agent") throw new Error("unreachable")
|
|
67
53
|
expect(request.prompt).toBe("do-the-work")
|
|
68
|
-
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("still accepts an explicit options.skills of its own, untouched", async () => {
|
|
57
|
+
const request = await capture(() =>
|
|
58
|
+
agentWithSkills("name", "do-the-work", { skills: ["own-skill"] }),
|
|
59
|
+
)
|
|
60
|
+
if (request.kind !== "agent") throw new Error("unreachable")
|
|
61
|
+
expect(request.options.skills).toEqual(["own-skill"])
|
|
69
62
|
})
|
|
70
63
|
})
|
|
71
64
|
|
|
@@ -136,6 +129,7 @@ describe("summaryPrompt", () => {
|
|
|
136
129
|
{ model: "base", cost: 7 },
|
|
137
130
|
],
|
|
138
131
|
vars: {},
|
|
132
|
+
env: {},
|
|
139
133
|
})
|
|
140
134
|
expect(prompt).toContain("Token cost: 12\n- smart: 5\n- base: 7\n\nPrint the closing message")
|
|
141
135
|
})
|
package/src/workflows/text.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
agent,
|
|
3
3
|
codeThreads,
|
|
4
|
+
env,
|
|
4
5
|
head,
|
|
6
|
+
skillsFor,
|
|
5
7
|
start,
|
|
6
|
-
vars,
|
|
7
8
|
type AgentOptions,
|
|
8
9
|
type SummaryContext,
|
|
9
10
|
} from "../flows/index.js"
|
|
@@ -36,31 +37,23 @@ export const withSkills = (skills: string | undefined, prompt: string): string =
|
|
|
36
37
|
return `${skillsPreamble.replaceAll("{skills}", skills)}\n\n${prompt}`
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
/** `skills:` split on `,`, each name trimmed, empty entries dropped — `[]` for `undefined`, `""`, or blank. */
|
|
40
|
-
export const splitSkills = (raw: string | undefined): readonly string[] =>
|
|
41
|
-
(raw ?? "")
|
|
42
|
-
.split(",")
|
|
43
|
-
.map((name) => name.trim())
|
|
44
|
-
.filter((name) => name.length > 0)
|
|
45
|
-
|
|
46
40
|
/**
|
|
47
|
-
* An `agent()` step
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
41
|
+
* An `agent()` step whose preamble names its resolved skills: `skillsFor`
|
|
42
|
+
* resolves `name` (scoped from here, same as `agent()`'s own resolver) for
|
|
43
|
+
* the preamble prose, passing `options.skills` through as `ownSkills` — the
|
|
44
|
+
* same precedence tier `agent()`'s own wire resolver gives a call's own
|
|
45
|
+
* `skills` option, so a step that declares one (`reviewQuality`'s per-turn
|
|
46
|
+
* lens) gets it in the preamble too, not just on the wire, whenever no
|
|
47
|
+
* bundled or configured entry outranks it. Both reads share one resolver
|
|
48
|
+
* (`Replay.ts`'s `resolveSkills`), so the preamble and the wire can't drift
|
|
49
|
+
* apart.
|
|
51
50
|
*/
|
|
52
51
|
export const agentWithSkills = (
|
|
53
52
|
name: string,
|
|
54
|
-
skills: string | undefined,
|
|
55
53
|
prompt: string,
|
|
56
54
|
options: AgentOptions = {},
|
|
57
|
-
): Promise<void> =>
|
|
58
|
-
|
|
59
|
-
return agent(name, withSkills(list.join(", "), prompt), {
|
|
60
|
-
...options,
|
|
61
|
-
skills: list,
|
|
62
|
-
})
|
|
63
|
-
}
|
|
55
|
+
): Promise<void> =>
|
|
56
|
+
agent(name, withSkills(skillsFor(name, options.skills).join(", "), prompt), options)
|
|
64
57
|
|
|
65
58
|
export const unwindFailure = (commit: string): string =>
|
|
66
59
|
`gtd could not unwind ${commit} out of your working tree.`
|
|
@@ -202,7 +195,7 @@ ${questionBarReturn}
|
|
|
202
195
|
never re-open one. A genuinely open PRODUCT point may still
|
|
203
196
|
raise a fresh \`## Open Questions\` entry: the product gate sits
|
|
204
197
|
on this path exactly as on the first lap
|
|
205
|
-
- Before grouping anything, run \`${
|
|
198
|
+
- Before grouping anything, run \`${env.testCommand}\`
|
|
206
199
|
yourself — a loop-back runs no green-baseline gate, so the
|
|
207
200
|
tree may already be red (reverting the edit can undo a fix it
|
|
208
201
|
made). If red, make that breakage the first concern, ahead of
|
|
@@ -814,10 +807,13 @@ every commit is machine-authored.
|
|
|
814
807
|
high-level architectural changes — never which files changed; \`git
|
|
815
808
|
diff --stat\` is for that, not this message
|
|
816
809
|
- If the range removes or renames a documented config key, CLI flag, or
|
|
817
|
-
workflow state
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
810
|
+
workflow state, OR makes a previously-accepted config value now fail to
|
|
811
|
+
load (a stricter validation, an escape a value must now spell differently)
|
|
812
|
+
— a public surface, not an internal one, either way — add a literal
|
|
813
|
+
\`BREAKING CHANGE:\` footer naming what breaks and, for the second case,
|
|
814
|
+
the exact escape or rewrite a committed \`.gtdrc\` needs. A \`!\` on the
|
|
815
|
+
type prefix alone (\`feat!:\`/\`refactor!:\`) is NOT enough: this repo's
|
|
816
|
+
own commit-analyzer has missed that marker before, cutting no release
|
|
821
817
|
until a follow-up commit carried the literal footer instead
|
|
822
818
|
|
|
823
819
|
The process's entry commit is \`${it.entryCommit}\`. ${human}Inspect the range: \`git log ${it.processBase}..${it.processTip}\`
|
package/src/workflows/unified.ts
CHANGED
|
@@ -31,7 +31,8 @@ import * as t from "./text.js"
|
|
|
31
31
|
// re-exported below.
|
|
32
32
|
|
|
33
33
|
export { threads, type ThreadInfo } from "../flows/index.js"
|
|
34
|
-
export { defaults } from "./vars.js"
|
|
34
|
+
export { defaults, envDefaults } from "./vars.js"
|
|
35
|
+
export { skills } from "./skills.js"
|
|
35
36
|
export { agentWithSkills } from "./text.js"
|
|
36
37
|
export * from "./steps.js"
|
|
37
38
|
export * from "./health.js"
|
package/src/workflows/vars.ts
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
// The bundled workflow's
|
|
2
|
-
//
|
|
1
|
+
// The bundled workflow's settings, in two kinds. A process setting changes
|
|
2
|
+
// which step comes next, so it is pinned for the whole process at its start;
|
|
3
|
+
// an environment setting only changes how a step runs on this machine, so it
|
|
4
|
+
// is read live on every invocation. `.gtdrc` `vars:`/`env:`, `--var` (process
|
|
5
|
+
// settings only) and `GTD_<NAME>` override either.
|
|
6
|
+
|
|
7
|
+
/** Process settings. */
|
|
3
8
|
export const defaults: Readonly<Record<string, string>> = {
|
|
4
|
-
testCommand: "npm test",
|
|
5
|
-
plannerModel: "smart",
|
|
6
|
-
coderModel: "base",
|
|
7
9
|
reviewBase: "",
|
|
8
10
|
judgeBudgetBytes: "32768",
|
|
9
11
|
judgeIdenticalMinP: "0.7",
|
|
10
12
|
specPreJudge: "0.9",
|
|
11
13
|
reviewNoteActionable: "0.7",
|
|
12
14
|
architectureSkipMinP: "0.85",
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
fixSkills: "debugging-and-error-recovery",
|
|
18
|
-
reviewFixSkills: "incremental-implementation, code-simplification",
|
|
19
|
-
reviewSkills: "code-review-and-quality",
|
|
20
|
-
specReviewSkills: "code-review-and-quality, spec-driven-development",
|
|
21
|
-
escalateSkills: "debugging-and-error-recovery",
|
|
15
|
+
// `qualityReviews` fans out into one turn per entry (`qualityLenses`, in
|
|
16
|
+
// `./review.ts`) rather than naming one step's skill list, so it is a
|
|
17
|
+
// setting — pairing with `build.quality.reviewing`'s entry in
|
|
18
|
+
// `./skills.ts`, which replaces the lens on every one of those turns.
|
|
22
19
|
qualityReviews: "owasp-security, ponytail-review, test-audit",
|
|
23
20
|
}
|
|
21
|
+
|
|
22
|
+
/** Environment settings. */
|
|
23
|
+
export const envDefaults: Readonly<Record<string, string>> = {
|
|
24
|
+
testCommand: "npm test",
|
|
25
|
+
plannerModel: "smart",
|
|
26
|
+
coderModel: "base",
|
|
27
|
+
}
|