@pmelab/gtd 17.3.0 → 18.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 +3 -2
- package/claude/hooks/drive.ts +5 -0
- package/claude/hooks/persona.ts +28 -0
- package/claude/hooks/register.tsx +23 -19
- package/claude/hooks/wording.ts +3 -0
- package/claude/types/index.d.ts +5 -1
- package/dist/gtd.bundle.mjs +248 -263
- package/package.json +1 -1
- package/schema.json +1 -1
- package/skills/authoring/SKILL.md +6 -4
- package/src/flows/runtime.ts +6 -14
- package/src/workflows/review.test.ts +13 -13
- package/src/workflows/review.ts +4 -4
- package/src/workflows/skills.test.ts +67 -81
- package/src/workflows/skills.ts +33 -37
- package/src/workflows/steps.test.ts +56 -78
- package/src/workflows/steps.ts +10 -19
- package/src/workflows/text.fixture.ts +14 -29
- package/src/workflows/text.test.ts +6 -14
- package/src/workflows/text.ts +3 -14
- package/src/workflows/vars.ts +2 -4
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
},
|
|
98
98
|
"skills": {
|
|
99
99
|
"type": "object",
|
|
100
|
-
"description": "Flat
|
|
100
|
+
"description": "Flat scope full-name -> skill-name array map. Each entry REPLACES the named scope's skill list wholesale (never merges into it) and reaches every nested scope that sets no list of its own; [] means no skills at all for that scope, and no preamble. A key that is not a scope running a turn is a load error listing the known scopes — the schema itself cannot validate a key, only a value's shape.",
|
|
101
101
|
"additionalProperties": {
|
|
102
102
|
"type": "array",
|
|
103
103
|
"items": {
|
|
@@ -115,10 +115,12 @@ needs across steps — a counter, the previous report, a review round's base —
|
|
|
115
115
|
lives in local variables; replay rebuilds them.
|
|
116
116
|
|
|
117
117
|
Composition: `scope(name, fn)` prefixes step names (`build.fix`) and sets their
|
|
118
|
-
**memory scope** (one scope = one agent conversation = one model/system —
|
|
119
|
-
them inside a scope fails the process);
|
|
120
|
-
also sets defaults for agent steps
|
|
121
|
-
|
|
118
|
+
**memory scope** (one scope = one agent conversation = one model/system/skills —
|
|
119
|
+
mixing them inside a scope fails the process);
|
|
120
|
+
`scope({ name?, model, system, skills }, fn)` also sets defaults for agent steps
|
|
121
|
+
inside. Skills are declared per scope, never on `agent()`, and a driver
|
|
122
|
+
restricts each turn to its scope's list where it can. `refuse(message)` refuses
|
|
123
|
+
the pending landing — call it right after the step whose turn you reject.
|
|
122
124
|
`@pmelab/gtd/flows` exports `requireProgress(file)`, `requireAnswers(file)` and
|
|
123
125
|
`requireRevert(edited, base)`, three such checks ready-made.
|
|
124
126
|
|
package/src/flows/runtime.ts
CHANGED
|
@@ -24,8 +24,6 @@ export interface AgentOptions extends SteeringOptions {
|
|
|
24
24
|
* is an attempt: recorded, but the process stays at the step (a stall).
|
|
25
25
|
*/
|
|
26
26
|
readonly allowEmpty?: boolean | undefined
|
|
27
|
-
/** The skill names this turn declares — what `gtd next --json`'s `skills` key carries. */
|
|
28
|
-
readonly skills?: readonly string[] | undefined
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
export interface HumanOptions extends SteeringOptions {
|
|
@@ -92,6 +90,8 @@ export interface ScopeOptions {
|
|
|
92
90
|
readonly model?: string | undefined
|
|
93
91
|
/** The system prompt every agent step inside runs with, unless it sets its own. */
|
|
94
92
|
readonly system?: string | undefined
|
|
93
|
+
/** The skill names every agent step inside declares — `gtd next --json`'s `skills`. A nested scope inherits it unless it sets its own. */
|
|
94
|
+
readonly skills?: readonly string[] | undefined
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
export type StepRequest =
|
|
@@ -139,15 +139,8 @@ export interface FlowContext {
|
|
|
139
139
|
readonly env: Readonly<Record<string, string>>
|
|
140
140
|
readonly head: () => string
|
|
141
141
|
readonly start: () => string
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
* to — for a prompt preamble. `ownSkills`, when given, is the same
|
|
145
|
-
* precedence tier as a call's own `skills` option: it stands in for the
|
|
146
|
-
* bundled default when there isn't one, but a `.gtdrc` entry still beats
|
|
147
|
-
* it. Shares its resolution with `agent()`'s own wire resolver (both read
|
|
148
|
-
* `Workflow.ts`'s `configuredSkills`/`skills`), so the two can never drift.
|
|
149
|
-
*/
|
|
150
|
-
readonly skillsFor: (localName: string, ownSkills?: readonly string[]) => readonly string[]
|
|
142
|
+
/** The skill list the memory scope `localName` lands in resolves to — the same resolver as the wire's `skills`. */
|
|
143
|
+
readonly skillsFor: (localName: string) => readonly string[]
|
|
151
144
|
}
|
|
152
145
|
|
|
153
146
|
const CONTEXT_KEY = Symbol.for("@pmelab/gtd/flow-context")
|
|
@@ -253,9 +246,8 @@ export const head = (): string => ctx().head()
|
|
|
253
246
|
/** The process's diff base: the commit before it began, or the base `gtd --entry` fixed. */
|
|
254
247
|
export const start = (): string => ctx().start()
|
|
255
248
|
|
|
256
|
-
/** The skill list `localName` (scoped from here, same as `agent()`) resolves to — for a prompt preamble.
|
|
257
|
-
export const skillsFor = (localName: string
|
|
258
|
-
ctx().skillsFor(localName, ownSkills)
|
|
249
|
+
/** The skill list `localName` (scoped from here, same as `agent()`) resolves to — for a prompt preamble. */
|
|
250
|
+
export const skillsFor = (localName: string): readonly string[] => ctx().skillsFor(localName)
|
|
259
251
|
|
|
260
252
|
const settingsProxy = (
|
|
261
253
|
read: () => Readonly<Record<string, string>>,
|
|
@@ -64,7 +64,7 @@ const drive = async (d: Drive): Promise<Run> => {
|
|
|
64
64
|
},
|
|
65
65
|
"review.closing": () => void files.delete(REVIEW),
|
|
66
66
|
"review.collecting": () => void files.set(REQUIREMENTS, "## Concern"),
|
|
67
|
-
"review.fix
|
|
67
|
+
"review.fix.nits.fixing": () => {
|
|
68
68
|
nitsFixed = true
|
|
69
69
|
},
|
|
70
70
|
}
|
|
@@ -118,22 +118,22 @@ describe("the risk-fix pass", () => {
|
|
|
118
118
|
const run = await drive({ notes: [], reviewDocs: [risky, clean] })
|
|
119
119
|
expect(run.log.slice(0, 5)).toEqual([
|
|
120
120
|
"review.reviewing",
|
|
121
|
-
"review.fix
|
|
121
|
+
"review.fix.risks.fixing",
|
|
122
122
|
"health.check",
|
|
123
123
|
"review.reviewing",
|
|
124
124
|
"review.await-review",
|
|
125
125
|
])
|
|
126
|
-
expect(run.prompts.get("review.fix
|
|
127
|
-
expect(run.prompts.get("review.fix
|
|
128
|
-
expect(run.prompts.get("review.fix
|
|
126
|
+
expect(run.prompts.get("review.fix.risks.fixing")).toContain("risk-1")
|
|
127
|
+
expect(run.prompts.get("review.fix.risks.fixing")).toContain("Risk: drops the carry")
|
|
128
|
+
expect(run.prompts.get("review.fix.risks.fixing")).toContain("Leave `.gtd/REVIEW.md` untouched")
|
|
129
129
|
})
|
|
130
130
|
|
|
131
131
|
it("a risk the re-review still marks goes to the gate with no second fix", async () => {
|
|
132
132
|
const run = await drive({ notes: [], reviewDocs: [risky, risky] })
|
|
133
|
-
expect(run.log.filter((n) => n === "review.fix
|
|
133
|
+
expect(run.log.filter((n) => n === "review.fix.risks.fixing")).toHaveLength(1)
|
|
134
134
|
expect(run.log.slice(0, 5)).toEqual([
|
|
135
135
|
"review.reviewing",
|
|
136
|
-
"review.fix
|
|
136
|
+
"review.fix.risks.fixing",
|
|
137
137
|
"health.check",
|
|
138
138
|
"review.reviewing",
|
|
139
139
|
"review.await-review",
|
|
@@ -142,7 +142,7 @@ describe("the risk-fix pass", () => {
|
|
|
142
142
|
|
|
143
143
|
it("no marker means no fix-risks step", async () => {
|
|
144
144
|
const run = await drive({ notes: [], reviewDocs: [clean] })
|
|
145
|
-
expect(run.log).not.toContain("review.fix
|
|
145
|
+
expect(run.log).not.toContain("review.fix.risks.fixing")
|
|
146
146
|
expect(run.log[0]).toBe("review.reviewing")
|
|
147
147
|
expect(run.log[1]).toBe("review.await-review")
|
|
148
148
|
})
|
|
@@ -157,11 +157,11 @@ describe("the risk-fix pass", () => {
|
|
|
157
157
|
"review.reviewing",
|
|
158
158
|
"review.await-review",
|
|
159
159
|
"review.triage",
|
|
160
|
-
"review.fix
|
|
160
|
+
"review.fix.nits.fixing",
|
|
161
161
|
"health.check",
|
|
162
162
|
"review.closing",
|
|
163
163
|
"review.reviewing",
|
|
164
|
-
"review.fix
|
|
164
|
+
"review.fix.risks.fixing",
|
|
165
165
|
"health.check",
|
|
166
166
|
"review.reviewing",
|
|
167
167
|
"review.await-review",
|
|
@@ -250,12 +250,12 @@ describe("review verdict routing", () => {
|
|
|
250
250
|
"review.await-review",
|
|
251
251
|
"review.triage",
|
|
252
252
|
"review.answer-review-questions",
|
|
253
|
-
"review.fix
|
|
253
|
+
"review.fix.nits.fixing",
|
|
254
254
|
"health.check",
|
|
255
255
|
"review.closing",
|
|
256
256
|
"review.reviewing",
|
|
257
257
|
])
|
|
258
|
-
expect(run.prompts.get("review.fix
|
|
258
|
+
expect(run.prompts.get("review.fix.nits.fixing")).not.toContain("why?")
|
|
259
259
|
expect(run.prompts.get("review.reviewing")).toContain("Carry-over: commit `c4`")
|
|
260
260
|
expect(run.result).toBe("stopped")
|
|
261
261
|
})
|
|
@@ -278,7 +278,7 @@ describe("review verdict routing", () => {
|
|
|
278
278
|
"review.await-review",
|
|
279
279
|
"review.triage",
|
|
280
280
|
"review.answer-review-questions",
|
|
281
|
-
"review.fix
|
|
281
|
+
"review.fix.nits.fixing",
|
|
282
282
|
"health.check",
|
|
283
283
|
"review.closing",
|
|
284
284
|
"review.collecting",
|
package/src/workflows/review.ts
CHANGED
|
@@ -36,9 +36,9 @@ import {
|
|
|
36
36
|
} from "./steps.js"
|
|
37
37
|
import * as t from "./text.js"
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
export const
|
|
41
|
-
|
|
39
|
+
/** Split a `qualityReviews` value into its lenses: on `,`, trimmed, blanks dropped. */
|
|
40
|
+
export const lensesOf = (value: string): readonly string[] =>
|
|
41
|
+
value
|
|
42
42
|
.split(",")
|
|
43
43
|
.map((lens) => lens.trim())
|
|
44
44
|
.filter((lens) => lens.length > 0)
|
|
@@ -49,7 +49,7 @@ export const qualityLenses = (): readonly string[] =>
|
|
|
49
49
|
* has any.
|
|
50
50
|
*/
|
|
51
51
|
export const qualityLap = async (): Promise<"clean" | "findings"> => {
|
|
52
|
-
for (const lens of
|
|
52
|
+
for (const lens of lensesOf(vars.qualityReviews ?? "")) await reviewQuality(lens)
|
|
53
53
|
return (read(QUALITY) ?? "").length > 0 ? "findings" : "clean"
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -1,94 +1,80 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
2
|
import { skills } from "./skills.js"
|
|
3
|
+
import { unified } from "./index.js"
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 seventeen bundled agent steps, by full name", () => {
|
|
30
|
-
expect(Object.keys(skills).sort()).toEqual(
|
|
5
|
+
const { defaults } = unified
|
|
6
|
+
|
|
7
|
+
const bundled = skills(defaults)
|
|
8
|
+
|
|
9
|
+
// Pins the lists by scope full name. It never runs the real flow, so a
|
|
10
|
+
// scope rename is caught by the e2e features that rest on these names
|
|
11
|
+
// (default-workflow, state-skills, scope-skills, quality-review-lap), not here.
|
|
12
|
+
describe("the bundled workflow's skills export", () => {
|
|
13
|
+
it("declares exactly the bundled scopes under the default qualityReviews", () => {
|
|
14
|
+
expect(Object.keys(bundled).sort()).toEqual(
|
|
31
15
|
[
|
|
32
|
-
"design
|
|
33
|
-
"architecture
|
|
16
|
+
"design",
|
|
17
|
+
"architecture",
|
|
34
18
|
"architecture.decompose",
|
|
35
|
-
"packages.item
|
|
36
|
-
"packages.item.fix
|
|
37
|
-
"packages.item.fix
|
|
38
|
-
"packages.item.spec
|
|
39
|
-
"packages.item.health
|
|
40
|
-
"build
|
|
41
|
-
"build.health
|
|
42
|
-
"build.quality
|
|
43
|
-
"build.
|
|
44
|
-
"build.review.
|
|
45
|
-
"build.review.
|
|
46
|
-
"build.
|
|
47
|
-
"build.
|
|
48
|
-
"build.review
|
|
19
|
+
"packages.item",
|
|
20
|
+
"packages.item.fix.suite",
|
|
21
|
+
"packages.item.fix.spec",
|
|
22
|
+
"packages.item.spec",
|
|
23
|
+
"packages.item.health",
|
|
24
|
+
"build",
|
|
25
|
+
"build.health",
|
|
26
|
+
"build.fix.quality",
|
|
27
|
+
"build.review",
|
|
28
|
+
"build.review.fix.nits",
|
|
29
|
+
"build.review.fix.risks",
|
|
30
|
+
"build.quality.correctness",
|
|
31
|
+
"build.quality.owasp-security",
|
|
32
|
+
"build.quality.ponytail-review",
|
|
33
|
+
"build.quality.test-audit",
|
|
34
|
+
"build.quality.conventions",
|
|
35
|
+
"build.quality.spec-challenge",
|
|
49
36
|
].sort(),
|
|
50
37
|
)
|
|
51
38
|
})
|
|
52
39
|
|
|
53
|
-
it("
|
|
54
|
-
|
|
55
|
-
|
|
40
|
+
it("keys no group scope", () => {
|
|
41
|
+
for (const group of ["packages.item.fix", "build.fix", "build.review.fix", "build.quality"]) {
|
|
42
|
+
expect(bundled).not.toHaveProperty(group)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("pins each scope's list", () => {
|
|
47
|
+
expect(bundled).toMatchObject({
|
|
48
|
+
design: ["spec-driven-development", "planning-and-task-breakdown"],
|
|
49
|
+
architecture: ["api-and-interface-design", "documentation-and-adrs", "ponytail"],
|
|
50
|
+
"architecture.decompose": ["incremental-implementation", "planning-and-task-breakdown"],
|
|
51
|
+
"packages.item": ["test-driven-development", "incremental-implementation"],
|
|
52
|
+
"packages.item.fix.suite": ["debugging-and-error-recovery"],
|
|
53
|
+
"packages.item.fix.spec": ["incremental-implementation", "code-simplification"],
|
|
54
|
+
"packages.item.spec": ["code-review-and-quality", "spec-driven-development"],
|
|
55
|
+
"packages.item.health": ["debugging-and-error-recovery"],
|
|
56
|
+
build: ["debugging-and-error-recovery"],
|
|
57
|
+
"build.health": ["debugging-and-error-recovery"],
|
|
58
|
+
"build.fix.quality": ["incremental-implementation", "code-simplification"],
|
|
59
|
+
"build.review": ["code-review-and-quality"],
|
|
60
|
+
"build.review.fix.nits": ["incremental-implementation", "code-simplification"],
|
|
61
|
+
"build.review.fix.risks": ["debugging-and-error-recovery", "incremental-implementation"],
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("loads one lens skill per quality lens: correctness its review skill, conventions and spec-challenge none, any other itself", () => {
|
|
66
|
+
expect(bundled["build.quality.correctness"]).toEqual(["code-review-and-quality"])
|
|
67
|
+
expect(bundled["build.quality.conventions"]).toEqual([])
|
|
68
|
+
expect(bundled["build.quality.spec-challenge"]).toEqual([])
|
|
69
|
+
expect(bundled["build.quality.owasp-security"]).toEqual(["owasp-security"])
|
|
56
70
|
})
|
|
57
71
|
|
|
58
|
-
it("
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"documentation-and-adrs",
|
|
66
|
-
"ponytail",
|
|
67
|
-
])
|
|
68
|
-
expect(skills["architecture.decompose"]).toEqual([
|
|
69
|
-
"incremental-implementation",
|
|
70
|
-
"planning-and-task-breakdown",
|
|
71
|
-
])
|
|
72
|
-
expect(skills["packages.item.building"]).toEqual([
|
|
73
|
-
"test-driven-development",
|
|
74
|
-
"incremental-implementation",
|
|
75
|
-
])
|
|
76
|
-
expect(skills["packages.item.fix-suite"]).toEqual(["debugging-and-error-recovery"])
|
|
77
|
-
expect(skills["build.fix"]).toEqual(["debugging-and-error-recovery"])
|
|
78
|
-
expect(skills["packages.item.fix-spec"]).toEqual([
|
|
79
|
-
"incremental-implementation",
|
|
80
|
-
"code-simplification",
|
|
81
|
-
])
|
|
82
|
-
expect(skills["build.fix-quality"]).toEqual([
|
|
83
|
-
"incremental-implementation",
|
|
84
|
-
"code-simplification",
|
|
85
|
-
])
|
|
86
|
-
expect(skills["packages.item.spec.review"]).toEqual([
|
|
87
|
-
"code-review-and-quality",
|
|
88
|
-
"spec-driven-development",
|
|
89
|
-
])
|
|
90
|
-
expect(skills["build.review.reviewing"]).toEqual(["code-review-and-quality"])
|
|
91
|
-
expect(skills["build.review.collecting"]).toEqual(["code-review-and-quality"])
|
|
92
|
-
expect(skills["build.quality.reviewing"]).toEqual([])
|
|
72
|
+
it("keys one lens scope per qualityReviews entry", () => {
|
|
73
|
+
const custom = skills({ ...defaults, qualityReviews: " my-lens ,, correctness" })
|
|
74
|
+
expect(
|
|
75
|
+
Object.keys(custom)
|
|
76
|
+
.filter((k) => k.startsWith("build.quality."))
|
|
77
|
+
.sort(),
|
|
78
|
+
).toEqual(["build.quality.correctness", "build.quality.my-lens"])
|
|
93
79
|
})
|
|
94
80
|
})
|
package/src/workflows/skills.ts
CHANGED
|
@@ -1,38 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"architecture.author": ["api-and-interface-design", "documentation-and-adrs", "ponytail"],
|
|
1
|
+
import { builtInLenses } from "./steps.js"
|
|
2
|
+
import { lensesOf } from "./review.js"
|
|
3
|
+
|
|
4
|
+
// The bundled workflow's default skills, one list per SCOPE, keyed by the
|
|
5
|
+
// scope's full name — the same name a `.gtdrc` `skills:` entry addresses. A
|
|
6
|
+
// nested scope with no entry inherits its parent's; group scopes
|
|
7
|
+
// (`packages.item.fix`, `build.fix`, `build.review.fix`, `build.quality`) run
|
|
8
|
+
// no turn and carry no key. `build.quality.<lens>` is one key per
|
|
9
|
+
// `qualityReviews` entry, so the export is a function of the vars.
|
|
10
|
+
export const skills = (
|
|
11
|
+
vars: Readonly<Record<string, string>>,
|
|
12
|
+
): Readonly<Record<string, readonly string[]>> => ({
|
|
13
|
+
design: ["spec-driven-development", "planning-and-task-breakdown"],
|
|
14
|
+
architecture: ["api-and-interface-design", "documentation-and-adrs", "ponytail"],
|
|
16
15
|
"architecture.decompose": ["incremental-implementation", "planning-and-task-breakdown"],
|
|
17
|
-
"packages.item
|
|
18
|
-
"packages.item.fix
|
|
19
|
-
"packages.item.fix
|
|
20
|
-
"packages.item.spec
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"build.review.fix-risks": ["debugging-and-error-recovery", "incremental-implementation"],
|
|
37
|
-
"build.review.collecting": ["code-review-and-quality"],
|
|
38
|
-
}
|
|
16
|
+
"packages.item": ["test-driven-development", "incremental-implementation"],
|
|
17
|
+
"packages.item.fix.suite": ["debugging-and-error-recovery"],
|
|
18
|
+
"packages.item.fix.spec": ["incremental-implementation", "code-simplification"],
|
|
19
|
+
"packages.item.spec": ["code-review-and-quality", "spec-driven-development"],
|
|
20
|
+
// `health` is shared code reached from both `packages.item` and `build`.
|
|
21
|
+
"packages.item.health": ["debugging-and-error-recovery"],
|
|
22
|
+
build: ["debugging-and-error-recovery"],
|
|
23
|
+
"build.health": ["debugging-and-error-recovery"],
|
|
24
|
+
"build.fix.quality": ["incremental-implementation", "code-simplification"],
|
|
25
|
+
"build.review": ["code-review-and-quality"],
|
|
26
|
+
"build.review.fix.nits": ["incremental-implementation", "code-simplification"],
|
|
27
|
+
"build.review.fix.risks": ["debugging-and-error-recovery", "incremental-implementation"],
|
|
28
|
+
...Object.fromEntries(
|
|
29
|
+
lensesOf(vars.qualityReviews ?? "").map((lens) => [
|
|
30
|
+
`build.quality.${lens}`,
|
|
31
|
+
builtInLenses[lens]?.skills ?? [lens],
|
|
32
|
+
]),
|
|
33
|
+
),
|
|
34
|
+
})
|