@pmelab/gtd 12.5.0 → 14.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.
@@ -0,0 +1,99 @@
1
+ import {
2
+ answered,
3
+ changes,
4
+ judge,
5
+ moveScript,
6
+ numeric,
7
+ openQuestions,
8
+ read,
9
+ refuse,
10
+ requireAnswers,
11
+ requireProgress,
12
+ run,
13
+ scope,
14
+ sections,
15
+ vars,
16
+ } from "../flows/index.js"
17
+ import {
18
+ answerProductQuestions,
19
+ answerTechnicalQuestions,
20
+ ARCHITECTURE,
21
+ author,
22
+ decompose,
23
+ REQUIREMENTS,
24
+ triage,
25
+ } from "./steps.js"
26
+ import * as t from "./text.js"
27
+
28
+ /**
29
+ * Stop for a human only while `file` is missing or still has open questions.
30
+ * Resolves `true` when the human answered (the author revises), `false` when
31
+ * no question was left.
32
+ */
33
+ const questionGate = async (file: string, answer: () => Promise<void>): Promise<boolean> => {
34
+ const content = read(file)
35
+ if (content !== undefined && openQuestions(content).length === 0) return false
36
+ await answer()
37
+ requireAnswers(file)
38
+ return true
39
+ }
40
+
41
+ /** Triage the sketch since `base` into requirements, until no product question is open. */
42
+ export const design = (base: string): Promise<void> =>
43
+ scope("design", async () => {
44
+ do {
45
+ await triage(base)
46
+ requireProgress(REQUIREMENTS)
47
+ } while (await questionGate(REQUIREMENTS, answerProductQuestions))
48
+ })
49
+
50
+ /** Work out the technical plan, then decompose it into package files. */
51
+ export const architecture = (): Promise<void> =>
52
+ scope("architecture", async () => {
53
+ do await author()
54
+ while (await questionGate(ARCHITECTURE, answerTechnicalQuestions))
55
+ await decompose()
56
+ if (changes(".gtd/packages/**").length === 0) {
57
+ refuse("gtd land: decompose: write at least one package under .gtd/packages/")
58
+ }
59
+ })
60
+
61
+ /** A package file name from a plan's first heading. */
62
+ const slug = (title: string): string =>
63
+ title
64
+ .toLowerCase()
65
+ .replace(/[^a-z0-9]+/g, "-")
66
+ .replace(/^-+|-+$/g, "") || "package"
67
+
68
+ /** Whether the settled plan needs its own architecture pass, or goes straight to one package. */
69
+ export const architecturePass = async (): Promise<void> => {
70
+ const { answers, truncated } = await judge("architecture-pre", {
71
+ questions: [
72
+ {
73
+ id: "architectureWarranted",
74
+ primitive: "noul",
75
+ instructions:
76
+ "Given the settled concerns in `.gtd/REQUIREMENTS.md` (in state), does this plan warrant a dedicated architecture pass — real structural decisions, multiple integration points, or a non-obvious tradeoff — before packages are written?",
77
+ criteria:
78
+ "Answer yes if uncertain; a trivial, single-concern, mechanical plan with no real design decision answers no.",
79
+ },
80
+ ],
81
+ evidence: { requirements: read(REQUIREMENTS) ?? "" },
82
+ message: t.architecturePreMessage(),
83
+ label: "Judging whether this plan warrants an architecture pass",
84
+ })
85
+ // A plan the budget cut can hide its structural concerns from the judge:
86
+ // never skip the architecture pass on it, however confident the "no".
87
+ const skip =
88
+ truncated.length === 0 &&
89
+ answered(answers.architectureWarranted, "no", numeric(vars.architectureSkipMinP, Infinity))
90
+ const plan = read(REQUIREMENTS)
91
+ if (skip && plan !== undefined) {
92
+ const target = `.gtd/packages/01-${slug(sections(plan)[0] ?? "")}.md`
93
+ await run("architecture-promote", moveScript(REQUIREMENTS, target), {
94
+ label: "Promoting the plan straight to a package",
95
+ })
96
+ return
97
+ }
98
+ await architecture()
99
+ }
@@ -0,0 +1,200 @@
1
+ // Prose the bundled workflow's prompts share: its voice, its personas and
2
+ // the rules several steps repeat. A workflow that wants different words
3
+ // writes its own prompts.
4
+
5
+ export const skillsPreamble = `- Load whatever's listed here that your harness actually has installed,
6
+ skip anything it doesn't — silently, never stopping or asking about a
7
+ missing one: {skills}
8
+ - A loaded skill offers technique, never authority: this state's own file
9
+ format and its own completion condition are the final word over
10
+ anything a skill's instructions say, regardless of which one this
11
+ prompt states first
12
+ - Never let a loaded skill turn this turn interactive — answer nothing,
13
+ ask nothing; this runs unattended, with no one at a keyboard`
14
+
15
+ export const styleBlock = `- A deliverable, not a chat reply — size follows the work; cut padding
16
+ - Lead with the answer; never circle back to restate it
17
+ - Flat, commanding sentences — commit to the claim, never hedge
18
+ - Everyday words; define an unavoidable term in five words or fewer, on
19
+ first use
20
+ - Bold carries the load: bold the claim, not the sentence around it —
21
+ the bold text alone must yield the full point and every risk
22
+ - **Never trim a risk, a number, a threshold, or a scoped condition to
23
+ save space — this outranks every other rule here**
24
+ - Ship the artifact bare — no lead-in, no sign-off
25
+ - Compressing is not dropping: three load-bearing parts ship as three,
26
+ each shorter, never as two
27
+ - One idea per block; break when the idea shifts
28
+ - Flag risk in one blunt line, never hedged prose; never narrate — do it`
29
+
30
+ export const styleFormatContract = `- Machine-read: its format contract outranks every style rule above —
31
+ keep every \`##\`/\`###\` heading, checkbox row, and marker line exactly
32
+ as specified. Never renumber or rename a heading; a parser reads
33
+ these literally and a violation refuses the turn
34
+ - Voice rules above govern only the prose between these elements`
35
+
36
+ export const agentConduct = `- You have shell and file tools — bash, read, write, edit — use them
37
+ without asking first; this runs unattended, no one grants permission
38
+ - Investigate with real commands rather than assuming a file's, a
39
+ commit's, or a decision's status — then act on what you find
40
+ - No injected status block — no cwd, no branch, no history, no tree
41
+ state — orient yourself first with \`git status\`, \`git log\`, \`ls\`
42
+ - The turn's message names a commit, a range, or a file to go inspect
43
+ yourself — never a diff or summary inlined into the conversation
44
+ - Across turns one conversation may span, work from what you already
45
+ read and settled, unless told this is a fresh, cold pass`
46
+
47
+ export const designPersona = `You are the product-facing planning voice in gtd's build pipeline: turn
48
+ a raw sketch — a hand-edit, a scratch note, or both — into an ordered,
49
+ classified list of concerns, and hold the running product conversation
50
+ with the human it depends on. You are read by that human — write
51
+ plainly — not by a parser, except where a state says otherwise.`
52
+
53
+ export const architectPersona = `You are the technical planning voice in gtd's build pipeline: take over
54
+ once product concerns are settled, reading them cold, no carried
55
+ conversation. Work out the *how* per concern — structure, data models,
56
+ tech-stack choices, error handling — raise the technical open
57
+ questions, then write one package spec per concern with no further
58
+ judgement call: the grouping is already decided.`
59
+
60
+ export const reviewerPersona = `You are the independent reviewing mind in gtd's build pipeline —
61
+ deliberately separate from whoever wrote the code, with no attachment
62
+ to it. Two turns: write a structured review document grouping a diff
63
+ into chunks; later, classify a round of the human's feedback as
64
+ actionable or just approving, never fixing anything yourself. You judge
65
+ and classify; you never build.`
66
+
67
+ export const specReviewerPersona = `You are the adversarial spec-conformance checker in gtd's build
68
+ pipeline, checking one freshly-built package against its spec. Verify
69
+ only: tasks done, criteria met, code sound and consistent with the
70
+ codebase. Write feedback only when something is genuinely wrong —
71
+ otherwise write nothing; a clean turn IS the approval. Never fix what
72
+ you find — naming it precisely enough for a fix turn is the whole job.`
73
+
74
+ export const builderPersona = `You are the TDD implementer in gtd's build pipeline: build one
75
+ package's declared scope end to end, tests first, and come back to fix
76
+ things when redirected — a failing check, or reviewer feedback. Stay
77
+ strictly inside the package in front of you; never touch another
78
+ package's files or code outside the task. Treat feedback as the
79
+ diagnosis to act on; discard it, with reason, only when simply wrong.`
80
+
81
+ export const finisherPersona = `You are the closing identity in gtd's build pipeline — the last coder
82
+ before it closes. Fix a late-breaking failing check after sign-off,
83
+ focused and minimal. Every turn lands its own commit — nothing here
84
+ gets squashed away.`
85
+
86
+ export const escalationPersona = `You are the diagnosing voice in gtd's build pipeline, called in once a
87
+ check has stayed red past repeated fix attempts. Read the failing
88
+ output, the previous round's, and the code those attempts touched, then
89
+ write down what is failing, why the earlier attempts didn't resolve it,
90
+ and concrete approaches worth trying next. Never fix the code yourself —
91
+ naming the problem precisely enough for the next fix turn is the whole
92
+ job.`
93
+
94
+ export const stateFileRules = `- You are an autonomous coding agent
95
+ - This workflow's own state files are its private scratchpad, never
96
+ project code or documentation`
97
+
98
+ export const questionBar = `- The goal is shared understanding, not a quota or an empty section: a
99
+ question exists to close a gap between what the human wants and
100
+ what the agent is about to build. Ask whenever that gap is open —
101
+ even when the point looks cheap to undo, settled-looking, or
102
+ narrow. This goal outranks what follows; the conditions below are
103
+ signals a gap is real, strong evidence to ask, never permission
104
+ withheld
105
+ - Before writing \`## Open Questions\`, walk every concern you grouped and
106
+ collect every point above the bar into that ONE section — a question
107
+ held back for a later lap is a bug. One lap is the target; a second is
108
+ the exception
109
+ - Raise first, narrow second: only once every concern above the bar is
110
+ raised into \`## Open Questions\`, walk that same set once more and
111
+ answer the ones a confident default settles, moving each into
112
+ \`## Answered Questions\` with its answer. This pass narrows what's
113
+ already raised — it is never license to raise less, and skipping the
114
+ raise to answer straight through is the same bug as holding a
115
+ question back for a later lap
116
+ - Treat each of these as strong evidence a gap is real: it's a genuine
117
+ fork with divergent outcomes (user-visible for product, materially
118
+ different builds for technical); the diff and history don't already
119
+ settle it (treat an explicit \`.gtd/TODO.md\` directive, a committed
120
+ hand-edit, or — on a loop-back lap — the human's own review-round
121
+ edit/note as settled); or it would be expensive to undo once
122
+ packages are written
123
+ - Where no gap in shared understanding exists, decide it yourself:
124
+ record it under \`## Answered Questions\` as
125
+ \`### <the point phrased as a question>\`
126
+ plus a one-line rationale in prose, no checkboxes. That heading
127
+ always comes last, after every other \`##\` section
128
+ - Above the bar, write it under \`## Open Questions\` — always the first
129
+ \`##\` section — as \`### <question>\` plus a checkbox list: two
130
+ concrete options and a free-text slot, all unticked:
131
+
132
+ ### <the question>
133
+
134
+ - [ ] <first option — a concrete answer, a few words of rationale>
135
+ - [ ] <second option>
136
+ - [ ] _your answer_
137
+
138
+ - Never tick a box yourself — the human ticks exactly one per question`
139
+
140
+ export const questionBarReturn = `- This lap continues the same goal as the first: close the gap
141
+ between what the human wants and what gets built. Folding in
142
+ answers and deciding what's left is that same goal continued, not
143
+ a different job
144
+ - The human answered — a ticked box, a free-text answer in place of
145
+ \`_your answer_\`, a deleted question, or a deleted \`## Open Questions\`
146
+ section are all answers. Fold each resolved answer into its concern's
147
+ prose, then move the question into \`## Answered Questions\` as
148
+ \`### <question>\` with the resolved answer in plain prose — no
149
+ checkboxes
150
+ - \`## Answered Questions\` is always the last \`##\` section — a moved
151
+ question lands there, never wherever \`## Open Questions\` used to sit
152
+ - Never re-raise a deleted question, and never re-open a settled
153
+ \`## Answered Questions\` entry
154
+ - An answer may earn a follow-up: if it opens a genuinely new fork above
155
+ the bar — one the answer itself created — raise it as a fresh \`##
156
+ Open Questions\` entry on this same lap. Never restate a question
157
+ already asked, and never treat this as licence to re-open a question
158
+ already settled under \`## Answered Questions\`
159
+ - Recognise a silent lap from \`## Open Questions\` still present with
160
+ nothing ticked and nothing else changed — the human's way of saying
161
+ the gap is already closed. That lap ends the questions, whatever the
162
+ goal says: decide every remaining question yourself, move each to
163
+ \`## Answered Questions\` with a one-line rationale, raise nothing new,
164
+ and leave no \`## Open Questions\` section behind`
165
+
166
+ export const fixFeedbackPrompt = `- The only state file this turn writes is \`.gtd/FEEDBACK.md\` — no other
167
+ files for notes or output
168
+ - Read \`.gtd/FEEDBACK.md\` (the failing test output) and fix the code so
169
+ the suite passes
170
+ - When \`.gtd/ESCALATION.md\` is present, it is a human-reviewed analysis
171
+ of why earlier attempts failed — read it and treat it as the primary
172
+ instruction for this turn. Never edit or delete it yourself, even once
173
+ you believe you've resolved it: only a genuinely green check retires
174
+ it, so an attempt that turns out to be wrong still leaves the next
175
+ turn's instruction in place. Its absence is the ordinary case: an
176
+ unremarkable red round with nothing to escalate yet
177
+ - Delete \`.gtd/FEEDBACK.md\` either way — once the suite passes, or once
178
+ you have established the feedback was wrong; leaving it in place is
179
+ never the right end state, the next check writes its own`
180
+
181
+ export const footnoteRules = `- Leave a footnote anywhere: mark the exact spot with \`[^name]\` (any
182
+ name, no whitespace or \`]\`), then define it below as \`[^name]:
183
+ explain what you mean\` — indent a longer comment's continuation
184
+ lines. (The literal words "your comment" are this format's seeded
185
+ placeholder body — a definition still holding them exactly is
186
+ flagged as unfilled, so write your own words there)
187
+ - A footnote is a comment on that exact spot — the hunk, line, or
188
+ paragraph it marks — never a whole-file remark
189
+ - \`name\` is yours to pick; only a definition's name must be unique in
190
+ this document — the same name may mark more than one spot`
191
+
192
+ export const footnoteFoldIn = `- A footnote (\`[^name]\` plus its \`[^name]:\` definition) is a comment on
193
+ its exact anchor — fold it in as a mandatory concern described
194
+ against that anchor's hunk or paragraph, never flattened into a
195
+ whole-file remark
196
+ - DELETE it in this same turn — marker and definition together — the
197
+ way a transient hand-written code comment is already treated. Never
198
+ re-read a footnote already acted on
199
+ - A footnote is human input only — you reply in prose; never write one
200
+ yourself`
@@ -0,0 +1,169 @@
1
+ import {
2
+ answered,
3
+ changes,
4
+ codeChanges,
5
+ head,
6
+ judge,
7
+ numeric,
8
+ read,
9
+ refuse,
10
+ removeScript,
11
+ run,
12
+ scope,
13
+ sectionBodies,
14
+ vars,
15
+ wrote,
16
+ type Change,
17
+ type JudgeQuestion,
18
+ } from "../flows/index.js"
19
+ import { escalation, FIX_CAP, healthy, type EscalationCount } from "./health.js"
20
+ import {
21
+ awaitReview,
22
+ collecting,
23
+ fix,
24
+ fixQuality,
25
+ QUALITY,
26
+ REQUIREMENTS,
27
+ REVIEW,
28
+ reviewing,
29
+ reviewMissing,
30
+ reviewQuality,
31
+ } from "./steps.js"
32
+ import * as t from "./text.js"
33
+
34
+ /** The skills the quality lap reviews with, one turn each: the `qualityReviews` var. */
35
+ export const qualityLenses = (): readonly string[] =>
36
+ (vars.qualityReviews ?? "")
37
+ .split(",")
38
+ .map((lens) => lens.trim())
39
+ .filter((lens) => lens !== "")
40
+
41
+ /**
42
+ * One review turn per lens over the whole change, each appending what it
43
+ * finds blocking to `.gtd/QUALITY.md`. Resolves `"findings"` when that file
44
+ * has any.
45
+ */
46
+ export const qualityLap = async (): Promise<"clean" | "findings"> => {
47
+ for (const lens of qualityLenses()) await reviewQuality(lens)
48
+ return (read(QUALITY) ?? "").length > 0 ? "findings" : "clean"
49
+ }
50
+
51
+ /** Resolves `true` once `.gtd/QUALITY.md` is resolved, `false` when the fix cap escalated instead. */
52
+ export const fixQualityFindings = async (escalations: EscalationCount): Promise<boolean> => {
53
+ for (let turns = 0; turns < FIX_CAP; turns++) {
54
+ await fixQuality()
55
+ if (changes(QUALITY).some((c) => c.status === "deleted")) return true
56
+ }
57
+ await escalation(escalations)
58
+ return false
59
+ }
60
+
61
+ /** How a review round ended. `feedback` carries what the human edited, for the re-unwind to undo. */
62
+ export type ReviewOutcome =
63
+ | { readonly verdict: "signoff" }
64
+ | {
65
+ readonly verdict: "feedback"
66
+ /** The commit the human's review edit landed as. */
67
+ readonly base: string
68
+ /** The code paths that edit changed, with their content before it. */
69
+ readonly edited: readonly Change[]
70
+ }
71
+
72
+ /** One noul per `## ` chunk of `.gtd/REVIEW.md`: is its note actionable? */
73
+ const triageQuestions = (chunks: readonly string[]): JudgeQuestion[] =>
74
+ chunks.map((title, i) => ({
75
+ id: `chunk-${i + 1}`,
76
+ primitive: "noul",
77
+ instructions: `Is the note under review chunk "${title}" (in .gtd/REVIEW.md) actionable — anything beyond an approving remark with no code edit?`,
78
+ criteria:
79
+ "A concrete request, a question, a code comment, or a hand-edit under this chunk answers yes. No note, or a purely approving remark, answers no.",
80
+ }))
81
+
82
+ /** A note-only round: judge whether any chunk of `review` asks for something. */
83
+ const actionable = async (review: string): Promise<boolean> => {
84
+ const found = sectionBodies(review)
85
+ const chunks = found.map((section) => section.title)
86
+ const evidence = Object.fromEntries(found.map(({ body }, i) => [`chunk-${i + 1}`, body]))
87
+ const { answers, truncated } = await judge("review.triage", {
88
+ questions: triageQuestions(chunks),
89
+ evidence,
90
+ message: t.buildReviewTriageMessage(),
91
+ label: "Judging feedback actionability",
92
+ })
93
+ // Dismissing a note is the risky direction, so only a confident "no" on
94
+ // uncut evidence dismisses a chunk; a blank floor dismisses nothing.
95
+ const minP = numeric(vars.reviewNoteActionable, Infinity)
96
+ return (
97
+ chunks.length === 0 ||
98
+ chunks.some((_, i) => {
99
+ const id = `chunk-${i + 1}`
100
+ return truncated.includes(id) || !answered(answers[id], "no", minP)
101
+ })
102
+ )
103
+ }
104
+
105
+ const collect = async (capture: string): Promise<"signoff" | "feedback"> => {
106
+ await collecting(capture)
107
+ if (wrote(REQUIREMENTS)) return "feedback"
108
+ if (changes().length === 0) return "signoff"
109
+ return refuse(
110
+ "gtd land: no declared pattern matches the pending changes — write .gtd/REQUIREMENTS.md from the feedback, or change nothing when it asks for nothing",
111
+ )
112
+ }
113
+
114
+ /**
115
+ * A reviewer writes `.gtd/REVIEW.md` over everything since `base`, a human
116
+ * reviews and signs off or comments, and a comment is classified into
117
+ * requirements for another lap.
118
+ */
119
+ export const review = async (base: string): Promise<ReviewOutcome> => {
120
+ for (;;) {
121
+ await reviewing(base)
122
+ await awaitReview(base)
123
+ if (changes(REVIEW).some((c) => c.status === "deleted")) {
124
+ refuse(
125
+ "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note (or edit code) to request changes.",
126
+ )
127
+ }
128
+ const edited = codeChanges()
129
+ const round = head()
130
+ const record = read(REVIEW)
131
+ if (record === undefined) {
132
+ await reviewMissing(round)
133
+ continue
134
+ }
135
+ // The review gate clears every tick before its commit, so a changed
136
+ // REVIEW.md is a note, never a tick.
137
+ const noted = changes(REVIEW).length > 0
138
+ const outcome = (verdict: "signoff" | "feedback"): ReviewOutcome =>
139
+ verdict === "signoff" ? { verdict } : { verdict, base: round, edited }
140
+ await run("review.closing", removeScript([REVIEW]), {
141
+ label: "Closing the review",
142
+ base: round,
143
+ })
144
+ if (edited.length > 0) return outcome(await collect(t.reviewEditsCapture(round)))
145
+ if (!noted || !(await actionable(record))) return { verdict: "signoff" }
146
+ return outcome(await collect(t.reviewNotesCapture(round)))
147
+ }
148
+ }
149
+
150
+ /** The build tail: fix (when entered red), keep green, the quality lap, then human review since `base`. */
151
+ export const buildTail = (fixFirst: boolean, base: string): Promise<ReviewOutcome> =>
152
+ scope("build", async () => {
153
+ const escalations: EscalationCount = { rounds: 0 }
154
+ let redFirst = fixFirst
155
+ // The lap runs once a tail: after its findings are fixed, review follows.
156
+ let lapped = false
157
+ for (;;) {
158
+ if (redFirst) {
159
+ await fix()
160
+ await healthy(fix, { fixesSoFar: 1, escalations })
161
+ redFirst = false
162
+ }
163
+ const lap = lapped ? "clean" : await qualityLap()
164
+ lapped = true
165
+ if (lap === "clean") return review(base)
166
+ if (await fixQualityFindings(escalations)) await healthy(fix, { escalations })
167
+ else redFirst = true
168
+ }
169
+ })
@@ -0,0 +1,200 @@
1
+ import { agent, human, vars } from "../flows/index.js"
2
+ import * as t from "./text.js"
3
+
4
+ // The bundled workflow's single steps. A step's name is relative to the
5
+ // scope its caller runs it in — `triage` inside `scope("design")` is
6
+ // `design.triage` — and the full name is public API: renaming it strands
7
+ // every process resting on it.
8
+
9
+ export const FEEDBACK = ".gtd/FEEDBACK.md"
10
+ export const ESCALATION = ".gtd/ESCALATION.md"
11
+ export const REQUIREMENTS = ".gtd/REQUIREMENTS.md"
12
+ export const ARCHITECTURE = ".gtd/ARCHITECTURE.md"
13
+ export const REVIEW = ".gtd/REVIEW.md"
14
+ export const QUALITY = ".gtd/QUALITY.md"
15
+ export const SPEC_FEEDBACK = ".gtd/SPEC_FEEDBACK.md"
16
+
17
+ const planner = (): string => vars.plannerModel ?? ""
18
+ const coder = (): string => vars.coderModel ?? ""
19
+
20
+ // ── Planning ────────────────────────────────────────────────────────────────
21
+
22
+ /** Turn the sketch since `base` into `.gtd/REQUIREMENTS.md`. */
23
+ export const triage = (base: string): Promise<void> =>
24
+ agent("triage", t.withSkills(vars.triageSkills, t.designTriagePrompt(base)), {
25
+ label: "Triaging the change",
26
+ file: REQUIREMENTS,
27
+ mode: "qa",
28
+ model: planner(),
29
+ system: t.designSystem(),
30
+ })
31
+
32
+ export const author = (): Promise<void> =>
33
+ agent("author", t.withSkills(vars.architectureSkills, t.architectureAuthorPrompt()), {
34
+ label: "Refining the technical plan",
35
+ file: ARCHITECTURE,
36
+ mode: "qa",
37
+ model: planner(),
38
+ system: t.architectSystem(),
39
+ })
40
+
41
+ export const decompose = (): Promise<void> =>
42
+ agent("decompose", t.withSkills(vars.decomposeSkills, t.architectureDecomposePrompt()), {
43
+ label: "Decomposing into packages",
44
+ model: planner(),
45
+ system: t.architectSystem(),
46
+ })
47
+
48
+ export const answerProductQuestions = (): Promise<void> =>
49
+ human("gate.answer", {
50
+ message: t.designGateAnswerMessage(),
51
+ label: "Awaiting your product answers",
52
+ file: REQUIREMENTS,
53
+ mode: "qa",
54
+ acceptClean: true,
55
+ })
56
+
57
+ export const answerTechnicalQuestions = (): Promise<void> =>
58
+ human("gate.answer", {
59
+ message: t.architectureGateAnswerMessage(),
60
+ label: "Awaiting your technical answers",
61
+ file: ARCHITECTURE,
62
+ mode: "qa",
63
+ acceptClean: true,
64
+ })
65
+
66
+ // ── Packages ────────────────────────────────────────────────────────────────
67
+
68
+ export const build = (pkg: string): Promise<void> =>
69
+ agent("building", t.withSkills(vars.buildSkills, t.packagesItemBuildingPrompt(pkg)), {
70
+ label: "Building",
71
+ model: coder(),
72
+ system: t.builderSystem(),
73
+ })
74
+
75
+ export const fixSuite = (): Promise<void> =>
76
+ agent("fix-suite", t.withSkills(vars.fixSkills, t.packagesItemFixSuitePrompt()), {
77
+ label: "Fixing the check",
78
+ file: FEEDBACK,
79
+ model: coder(),
80
+ system: t.builderSystem(),
81
+ })
82
+
83
+ export const fixSpec = (pkg: string): Promise<void> =>
84
+ agent("fix-spec", t.withSkills(vars.reviewFixSkills, t.packagesItemFixSpecPrompt(pkg)), {
85
+ label: "Fixing review feedback",
86
+ file: SPEC_FEEDBACK,
87
+ model: coder(),
88
+ system: t.builderSystem(),
89
+ })
90
+
91
+ /** Review `pkg` against its spec, focused on the `failing` sections the pre-judge could not clear. */
92
+ export const reviewPackage = (pkg: string, failing: readonly string[] = []): Promise<void> =>
93
+ agent(
94
+ "spec.review",
95
+ t.withSkills(vars.specReviewSkills, t.packagesItemSpecReviewPrompt(pkg, failing)),
96
+ {
97
+ label: "Reviewing the package",
98
+ model: planner(),
99
+ system: t.specReviewerSystem(),
100
+ allowEmpty: true,
101
+ },
102
+ )
103
+
104
+ // ── Keeping the suite green ─────────────────────────────────────────────────
105
+
106
+ export const fix = (): Promise<void> =>
107
+ agent("fix", t.withSkills(vars.fixSkills, t.buildFixPrompt()), {
108
+ label: "Fixing the check",
109
+ file: FEEDBACK,
110
+ model: coder(),
111
+ system: t.finisherSystem(),
112
+ })
113
+
114
+ export const describeEscalation = (): Promise<void> =>
115
+ agent("health.describe", t.withSkills(vars.escalateSkills, t.healthDescribePrompt()), {
116
+ label: "Describing the escalation",
117
+ file: FEEDBACK,
118
+ model: coder(),
119
+ system: t.escalationSystem(),
120
+ allowEmpty: true,
121
+ })
122
+
123
+ export const escalate = (): Promise<void> =>
124
+ human("health.stop", {
125
+ message: t.healthStopMessage(),
126
+ label: "Escalating to a human",
127
+ file: ESCALATION,
128
+ acceptClean: true,
129
+ })
130
+
131
+ export const escalationExhausted = (): Promise<void> =>
132
+ human("health.exhausted", {
133
+ message: t.healthExhaustedMessage(),
134
+ label: "Escalation exhausted",
135
+ file: ESCALATION,
136
+ acceptClean: true,
137
+ })
138
+
139
+ // ── Quality and review ──────────────────────────────────────────────────────
140
+
141
+ /** One quality review, through the skill `lens`. */
142
+ export const reviewQuality = (lens: string): Promise<void> =>
143
+ agent("quality.reviewing", t.withSkills(lens, t.buildQualityReviewingPrompt(lens)), {
144
+ label: "Reviewing (one quality lens)",
145
+ file: QUALITY,
146
+ model: planner(),
147
+ system: t.reviewerSystem(),
148
+ allowEmpty: true,
149
+ })
150
+
151
+ export const fixQuality = (): Promise<void> =>
152
+ agent("fix-quality", t.withSkills(vars.reviewFixSkills, t.buildFixQualityPrompt()), {
153
+ label: "Fixing quality findings",
154
+ file: QUALITY,
155
+ model: coder(),
156
+ system: t.finisherSystem(),
157
+ allowEmpty: true,
158
+ })
159
+
160
+ /** Write `.gtd/REVIEW.md` over everything since `base`. */
161
+ export const reviewing = (base: string): Promise<void> =>
162
+ agent("review.reviewing", t.withSkills(vars.reviewSkills, t.buildReviewReviewingPrompt(base)), {
163
+ label: "Reviewing",
164
+ file: REVIEW,
165
+ mode: "review",
166
+ model: planner(),
167
+ system: t.reviewerSystem(),
168
+ base,
169
+ })
170
+
171
+ export const awaitReview = (base: string): Promise<void> =>
172
+ human("review.await-review", {
173
+ message: t.buildReviewAwaitReviewMessage(base),
174
+ label: "Awaiting your review",
175
+ file: REVIEW,
176
+ mode: "review",
177
+ base,
178
+ })
179
+
180
+ /** The round at commit `round` left no `.gtd/REVIEW.md`. */
181
+ export const reviewMissing = (round: string): Promise<void> =>
182
+ human("review.review-missing", {
183
+ message: t.buildReviewReviewMissingMessage(round),
184
+ label: "Nothing to review",
185
+ })
186
+
187
+ /** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
188
+ export const collecting = (capture: string): Promise<void> =>
189
+ agent(
190
+ "review.collecting",
191
+ t.withSkills(vars.reviewSkills, t.buildReviewCollectingPrompt(capture)),
192
+ {
193
+ label: "Collecting your feedback",
194
+ file: REQUIREMENTS,
195
+ mode: "qa",
196
+ model: planner(),
197
+ system: t.reviewerSystem(),
198
+ allowEmpty: true,
199
+ },
200
+ )