@pmelab/gtd 27.5.0 → 27.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +9 -0
  3. package/dist/chunks/{reserved-DM5W_LAt.mjs → reserved-Df8GeSHw.mjs} +37 -9
  4. package/dist/chunks/{tail-6gvLXXcg.mjs → tail-DmIfEAI4.mjs} +1 -1
  5. package/dist/flows.mjs +17 -1
  6. package/dist/gtd.bundle.mjs +53 -8
  7. package/dist/testkit.mjs +37 -8
  8. package/dist/workflows/cleanup.mjs +2 -2
  9. package/dist/workflows/feature.mjs +61 -10
  10. package/dist/workflows/fix.mjs +2 -2
  11. package/dist/workflows/review.mjs +2 -2
  12. package/dist/workflows/rollout.mjs +1 -1
  13. package/dist/workflows.mjs +110 -16
  14. package/package.json +1 -1
  15. package/src/edge/Edge.test.ts +111 -0
  16. package/src/edge/Edge.ts +15 -3
  17. package/src/flags/index.test.ts +13 -0
  18. package/src/flags/index.ts +1 -0
  19. package/src/flows/runtime.test.ts +42 -0
  20. package/src/flows/runtime.ts +36 -0
  21. package/src/replay/Replay.test.ts +43 -0
  22. package/src/replay/Replay.ts +10 -2
  23. package/src/replay/Trailers.test.ts +54 -0
  24. package/src/replay/Trailers.ts +15 -1
  25. package/src/workflows/feature/feature.ts +3 -1
  26. package/src/workflows/feature/risk-tier.feature +301 -0
  27. package/src/workflows/feature/seam.ts +5 -4
  28. package/src/workflows/feature/steps.ts +3 -2
  29. package/src/workflows/feature/tier.test.ts +164 -0
  30. package/src/workflows/feature/tier.ts +74 -0
  31. package/src/workflows/shared/options.test.ts +6 -1
  32. package/src/workflows/shared/text.test.ts +129 -0
  33. package/src/workflows/shared/text.ts +57 -9
  34. package/src/workflows/shared/vars.ts +2 -0
  35. package/src/workflows/shared/workflows.steps.ts +8 -0
@@ -10,6 +10,7 @@ import {
10
10
  type ScopeOptions,
11
11
  type StepRequest,
12
12
  type Flow,
13
+ type Mark,
13
14
  } from "../flows/index.js"
14
15
  import {
15
16
  headingSectionBodies,
@@ -114,12 +115,15 @@ export type ReplayOutcome =
114
115
  readonly trace: readonly ReachedStep[]
115
116
  /** The step the pending turn completed, when one was supplied. */
116
117
  readonly landed: ReachedStep | undefined
118
+ /** Marks the flow made after the pending turn's step resolved; [] without a pending turn. */
119
+ readonly marks: readonly Mark[]
117
120
  }
118
121
  | {
119
122
  readonly kind: "ended"
120
123
  readonly via: "return" | "restart"
121
124
  readonly trace: readonly ReachedStep[]
122
125
  readonly landed: ReachedStep | undefined
126
+ readonly marks: readonly Mark[]
123
127
  }
124
128
  | { readonly kind: "divergence"; readonly message: string }
125
129
  | { readonly kind: "refused"; readonly message: string }
@@ -292,6 +296,7 @@ export const replay = async (input: ReplayInput): Promise<ReplayOutcome> => {
292
296
  { readonly hash: string; readonly before: TreeView; readonly after: TreeView }
293
297
  >()
294
298
  let landed: ReachedStep | undefined
299
+ const marks: Mark[] = []
295
300
  let outcome: ReplayOutcome | undefined
296
301
  let signal: (() => void) | undefined
297
302
  const settled = new Promise<void>((resolve) => {
@@ -553,7 +558,7 @@ export const replay = async (input: ReplayInput): Promise<ReplayOutcome> => {
553
558
  const commit = commits[cursor]
554
559
  if (commit !== undefined) return consumeCommit(step, commit)
555
560
  if (input.pending === undefined || pendingUsed) {
556
- return finish({ kind: "rest", rest: step, trace, landed })
561
+ return finish({ kind: "rest", rest: step, trace, landed, marks })
557
562
  }
558
563
  return landPending(step, input.pending)
559
564
  }
@@ -598,7 +603,7 @@ export const replay = async (input: ReplayInput): Promise<ReplayOutcome> => {
598
603
  kind: "divergence",
599
604
  message: `gtd: the flow ended, but commit ${short(commits[cursor]!.hash)} continues the episode — ${DIVERGED}`,
600
605
  }
601
- : { kind: "ended", via, trace, landed }
606
+ : { kind: "ended", via, trace, landed, marks }
602
607
 
603
608
  const changesSince = (hash: string): readonly Change[] => {
604
609
  const tree = treeAt(hash)
@@ -670,6 +675,9 @@ export const replay = async (input: ReplayInput): Promise<ReplayOutcome> => {
670
675
  .map((f) => f.message),
671
676
  }))
672
677
  }),
678
+ mark: (name, value) => {
679
+ if (pendingUsed) marks.push({ name, value })
680
+ },
673
681
  kept: (name) => {
674
682
  const tip = keptTips.get(scoped(name))
675
683
  return tip === undefined
@@ -282,3 +282,57 @@ describe("isRecordableApproval", () => {
282
282
  expect(isRecordableApproval(of("Ada", "a@x.io", 0))).toBe(false)
283
283
  })
284
284
  })
285
+
286
+ describe("Gtd-Mark", () => {
287
+ const body = (...lines: string[]) => `gtd(human): a → b\n\n${lines.join("\n")}`
288
+
289
+ it("round-trips in order, names repeating", () => {
290
+ const message = formatCommitMessage({
291
+ actor: "human",
292
+ from: "a",
293
+ to: "b",
294
+ marks: [
295
+ { name: "risk-tier", value: "high" },
296
+ { name: "production-signal", value: "error rate = 0" },
297
+ { name: "production-signal", value: "p95" },
298
+ ],
299
+ })
300
+ expect(parseCommitMessage(message).marks).toEqual([
301
+ { name: "risk-tier", value: "high" },
302
+ { name: "production-signal", value: "error rate = 0" },
303
+ { name: "production-signal", value: "p95" },
304
+ ])
305
+ })
306
+
307
+ it("is written after Gtd-Judge and before Gtd-Approved, with no Gtd-Format bump", () => {
308
+ const lines = formatCommitMessage({
309
+ actor: "human",
310
+ to: "b",
311
+ judge: [{ id: "v", answer: "x", p: 1 }],
312
+ marks: [{ name: "risk-tier", value: "high" }],
313
+ approved: { step: { name: "s", occurrence: 1 }, name: "Ada", email: "a@x.io" },
314
+ }).split("\n")
315
+ const at = lines.findIndex((l) => l.startsWith("Gtd-Mark:"))
316
+ expect(lines[at - 1]).toMatch(/^Gtd-Judge:/)
317
+ expect(lines[at + 1]).toMatch(/^Gtd-Approved:/)
318
+ expect(lines).toContain("Gtd-Format: 1")
319
+ })
320
+
321
+ it("writes nothing without marks", () => {
322
+ expect(formatCommitMessage({ actor: "human", to: "b" })).not.toContain("Gtd-Mark")
323
+ expect(parseCommitMessage("gtd(human): a → b").marks).toEqual([])
324
+ })
325
+
326
+ it("skips a malformed line, a missing =, an empty value or a control character", () => {
327
+ const parsed = parseCommitMessage(
328
+ body(
329
+ "Gtd-Mark: no-equals",
330
+ "Gtd-Mark: Bad_Name=x",
331
+ "Gtd-Mark: empty=",
332
+ "Gtd-Mark: ctl=a\u001bb",
333
+ "Gtd-Mark: ok=fine",
334
+ ),
335
+ )
336
+ expect(parsed.marks).toEqual([{ name: "ok", value: "fine" }])
337
+ })
338
+ })
@@ -3,6 +3,7 @@
3
3
  // written by gtd) is skipped, not fatal — a corrupt line must never make every
4
4
  // command unusable.
5
5
 
6
+ import type { Mark } from "../flows/index.js"
6
7
  import { HISTORY_FORMAT } from "./Format.js"
7
8
 
8
9
  const TRANSITION_SEP = " → "
@@ -73,6 +74,8 @@ export interface CommitMessage {
73
74
  /** Written only on a process's opening commit. */
74
75
  readonly workflow: string | undefined
75
76
  readonly judge: readonly JudgeVerdict[]
77
+ /** `Gtd-Mark`, in order; a malformed or control-character line is skipped. */
78
+ readonly marks: readonly Mark[]
76
79
  readonly vars: Readonly<Record<string, string>>
77
80
  /** Written only on a process's first commit. */
78
81
  readonly config: Readonly<Record<string, string>>
@@ -141,6 +144,7 @@ export const isRecordableApproval = (approval: Approval): boolean => {
141
144
  const back = decodeApproval(encodeApproval(approval))
142
145
  return back !== undefined && encodeApproval(back) === encodeApproval(approval)
143
146
  }
147
+ const MARK_RE = /^([a-z][a-z0-9-]*)=(.+)$/
144
148
  const FORMAT_RE = /^[1-9][0-9]*$/
145
149
  const HASH_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/
146
150
 
@@ -153,6 +157,7 @@ interface Trailers {
153
157
  kept: string | undefined
154
158
  approved: Approval | undefined
155
159
  readonly judge: JudgeVerdict[]
160
+ readonly marks: Mark[]
156
161
  readonly vars: Record<string, string>
157
162
  readonly config: Record<string, string>
158
163
  readonly cost: CostEntry[]
@@ -170,6 +175,11 @@ const TRAILER_READERS: Readonly<Record<string, (value: string, into: Trailers) =
170
175
  const parsed = parseJson(value)
171
176
  if (isVerdict(parsed)) into.judge.push(parsed)
172
177
  },
178
+ "Gtd-Mark": (value, into) => {
179
+ const kv = MARK_RE.exec(value)
180
+ if (kv !== null && !CONTROL_CHARACTER.test(value))
181
+ into.marks.push({ name: kv[1]!, value: kv[2]! })
182
+ },
173
183
  "Gtd-Var": (value, into) => {
174
184
  const kv = VAR_RE.exec(value)
175
185
  if (kv !== null) into.vars[kv[1]!] = kv[2]!
@@ -215,6 +225,7 @@ export const parseCommitMessage = (message: string): CommitMessage => {
215
225
  kept: undefined,
216
226
  approved: undefined,
217
227
  judge: [],
228
+ marks: [],
218
229
  vars: {},
219
230
  config: {},
220
231
  cost: [],
@@ -239,7 +250,9 @@ export interface CommitSpec {
239
250
  readonly config?: Readonly<Record<string, string>>
240
251
  readonly cost?: { readonly cost: number; readonly model?: string }
241
252
  readonly judge?: readonly JudgeVerdict[]
242
- /** Written after `Gtd-Judge`, before `Gtd-Branch`. */
253
+ /** Written after `Gtd-Judge`, before `Gtd-Approved`. */
254
+ readonly marks?: readonly Mark[]
255
+ /** Written after `Gtd-Mark`, before `Gtd-Branch`. */
243
256
  readonly approved?: Approval
244
257
  readonly branch?: string
245
258
  readonly version?: string
@@ -263,6 +276,7 @@ export const formatCommitMessage = (spec: CommitSpec): string => {
263
276
  )
264
277
  }
265
278
  for (const verdict of spec.judge ?? []) lines.push(`Gtd-Judge: ${JSON.stringify(verdict)}`)
279
+ for (const m of spec.marks ?? []) lines.push(`Gtd-Mark: ${m.name}=${m.value}`)
266
280
  if (spec.approved !== undefined) {
267
281
  lines.push(`Gtd-Approved: ${encodeApproval(spec.approved)}`)
268
282
  }
@@ -18,6 +18,7 @@ import { packages } from "./packages.js"
18
18
  import { architecture, design } from "./planning.js"
19
19
  import { buildTail, type ReviewOutcome } from "./review.js"
20
20
  import { seamBuild } from "./seam.js"
21
+ import { recordRisk, riskTierOn } from "./tier.js"
21
22
 
22
23
  // The ordinary start. Any change to the tree starts a process: `idle` →
23
24
  // `unwind` reverts the sketch (its intent survives in history) → a
@@ -56,9 +57,10 @@ export const planAndBuild = async (firstBase: string): Promise<void> => {
56
57
  let base = firstBase
57
58
  for (;;) {
58
59
  await design(base)
60
+ const tier = riskTierOn() ? recordRisk() : undefined
59
61
  // GTD_FLAG_NAIVE_PASS: the record is pinned at the process start, so a mid-process flip never moves a process.
60
62
  if (vars.flag_naive_pass === "1") {
61
- await seamBuild()
63
+ await seamBuild(tier)
62
64
  } else {
63
65
  await architecture()
64
66
  await packages()
@@ -0,0 +1,301 @@
1
+ @inmem
2
+ Feature: the risk tier is proposed at triage and recorded behind GTD_FLAG_RISK_TIER
3
+
4
+ With GTD_FLAG_NAIVE_PASS and GTD_FLAG_RISK_TIER both on, design.triage asks
5
+ for a risk tier and production signals, and the design gate's exit lands them
6
+ as Gtd-Mark trailers: one `risk-tier=<low|high>`, one `production-signal`
7
+ per signal. A missing or unreadable tier is high. With the flag off, nothing
8
+ is asked and no commit carries a Gtd-Mark.
9
+
10
+ Background:
11
+ Given the shipped workflows, as gtd install scaffolds them
12
+ And an environment variable "GTD_QUALITYREVIEWS" set to ""
13
+ And an environment variable "GTD_FLAG_NAIVE_PASS" set to "1"
14
+
15
+ Rule: with GTD_FLAG_RISK_TIER on
16
+
17
+ Background:
18
+ Given an environment variable "GTD_FLAG_RISK_TIER" set to "1"
19
+ And a file "src/calc.ts" with:
20
+ """
21
+ export const add = (a: number, b: number) => a + b
22
+ """
23
+ And a file "src/calc.test.ts" with:
24
+ """
25
+ import { expect, it } from "vitest"
26
+ it("adds", () => expect(1 + 1).toBe(2))
27
+ """
28
+ And the working tree is committed as "feat: add calculator"
29
+ And a file "TODO.md" with:
30
+ """
31
+ # Add subtract
32
+
33
+ subtract(a, b) returns the difference.
34
+ """
35
+ And step "unwind" changes nothing
36
+ And step "start-gate.check" changes nothing
37
+
38
+ Scenario: with both flags on the triage prompt asks for the tier and the signals
39
+ When the process is driven
40
+ Then the process rests at "design.triage"
41
+ And the resting step's content contains "## Risk Tier"
42
+ And the resting step's content contains "## Production Signals"
43
+ And the resting step's content contains "unsure means `high`"
44
+
45
+ Scenario: a low tier marks risk-tier=low on the design-exit commit
46
+ Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
47
+ """
48
+ # Requirements
49
+
50
+ ## Risk Tier
51
+
52
+ low — trivial copy change
53
+ """
54
+ And step "design.gate.answer" changes nothing
55
+ When the process is driven
56
+ Then the process rests at "seam.naive"
57
+ And the last commit subject is "gtd(human): design.gate.answer → seam.naive"
58
+ And the last commit body contains "Gtd-Mark: risk-tier=low"
59
+
60
+ Scenario: a high tier marks the design-exit commit
61
+ Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
62
+ """
63
+ # Requirements
64
+
65
+ ## Risk Tier
66
+
67
+ high — touches payments
68
+ """
69
+ And step "design.gate.answer" changes nothing
70
+ When the process is driven
71
+ Then the process rests at "seam.naive"
72
+ And the last commit subject is "gtd(human): design.gate.answer → seam.naive"
73
+ And the last commit body contains "Gtd-Mark: risk-tier=high"
74
+
75
+ Scenario Outline: a missing section or an unknown word counts as high
76
+ Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
77
+ """
78
+ # Requirements
79
+
80
+ <tier section>
81
+ ## Concerns
82
+
83
+ ### Subtract
84
+ """
85
+ And step "design.gate.answer" changes nothing
86
+ When the process is driven
87
+ Then the process rests at "seam.naive"
88
+ And the last commit body contains "Gtd-Mark: risk-tier=high"
89
+
90
+ Examples:
91
+ | tier section |
92
+ | |
93
+ | ## Risk Tier\n\nmedium — hmm |
94
+ | ```\n## Risk Tier\n\nlow\n```\n |
95
+
96
+ Scenario Outline: signals written at triage land as marks for either tier
97
+ Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
98
+ """
99
+ # Requirements
100
+
101
+ ## Risk Tier
102
+
103
+ <tier> — reason
104
+
105
+ ## Production Signals
106
+
107
+ - error rate on /subtract stays flat
108
+ - p95 latency
109
+ """
110
+ And step "design.gate.answer" changes nothing
111
+ When the process is driven
112
+ Then the process rests at "seam.naive"
113
+ And the last commit body contains "Gtd-Mark: production-signal=error rate on /subtract stays flat"
114
+ And the last commit body contains "Gtd-Mark: production-signal=p95 latency"
115
+
116
+ Examples:
117
+ | tier |
118
+ | low |
119
+ | high |
120
+
121
+ Scenario Outline: low and high processes produce the same commit subjects to implement.building
122
+ Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
123
+ """
124
+ # Requirements
125
+
126
+ ## Risk Tier
127
+
128
+ <tier> — reason
129
+ """
130
+ And step "design.gate.answer" changes nothing
131
+ And step "seam.naive" writes "src/calc.ts" with:
132
+ """
133
+ export const add = (a: number, b: number) => a + b
134
+ export const subtract = (a: number, b: number) => a - b
135
+ """
136
+ And judge step "seam.judge" answers:
137
+ """
138
+ [{"id":"shape","answer":"land","p":0.9}]
139
+ """
140
+ When the process is driven
141
+ Then the process rests at "implement.building"
142
+ And the commit subjects end with:
143
+ """
144
+ gtd(agent): design.triage → design.gate.answer
145
+ gtd(human): design.gate.answer → seam.naive
146
+ gtd(agent): seam.naive → seam.judge
147
+ gtd(judge): seam.judge → implement.building
148
+ """
149
+
150
+ Examples:
151
+ | tier |
152
+ | low |
153
+ | high |
154
+
155
+ Scenario Outline: the implementation prompt demands a flag for high and none for low
156
+ Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
157
+ """
158
+ # Requirements
159
+
160
+ ## Risk Tier
161
+
162
+ <tier> — reason
163
+ """
164
+ And step "design.gate.answer" changes nothing
165
+ And step "seam.naive" writes "src/calc.ts" with:
166
+ """
167
+ export const add = (a: number, b: number) => a + b
168
+ export const subtract = (a: number, b: number) => a - b
169
+ """
170
+ And judge step "seam.judge" answers:
171
+ """
172
+ [{"id":"shape","answer":"land","p":0.9}]
173
+ """
174
+ When the process is driven
175
+ Then the process rests at "implement.building"
176
+ And the resting step's content contains "<demand>"
177
+ And the resting step's content does not contain "<absent>"
178
+
179
+ Examples:
180
+ | tier | demand | absent |
181
+ | high | Put the new behaviour behind a new release flag | Add no release flag |
182
+ | low | Add no release flag — this change is low risk tier | new release flag |
183
+
184
+ Scenario: the tier comes from the requirements the final design gate lap accepted
185
+ Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
186
+ """
187
+ # Requirements
188
+
189
+ ## Risk Tier
190
+
191
+ low — trivial copy change
192
+ """
193
+ And step "seam.naive" writes "src/calc.ts" with:
194
+ """
195
+ export const add = (a: number, b: number) => a + b
196
+ export const subtract = (a: number, b: number) => a - b
197
+ """
198
+ And judge step "seam.judge" answers:
199
+ """
200
+ [{"id":"shape","answer":"land","p":0.9}]
201
+ """
202
+ When the process is driven
203
+ Then the process rests at "design.gate.answer"
204
+ Given a file ".gtd/REQUIREMENTS.md" with:
205
+ """
206
+ # Requirements
207
+
208
+ ## Risk Tier
209
+
210
+ high — touches payments
211
+ """
212
+ And step "design.triage" from now on changes nothing
213
+ And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
214
+ """
215
+ # Requirements
216
+
217
+ ## Risk Tier
218
+
219
+ high — touches payments
220
+
221
+ ## Concerns
222
+
223
+ ### Subtract
224
+ """
225
+ And step "design.gate.answer" changes nothing
226
+ When the process is driven
227
+ Then the process rests at "implement.building"
228
+ And the resting step's content contains "Put the new behaviour behind a new release flag"
229
+ And the resting step's content does not contain "Add no release flag"
230
+
231
+ Rule: with GTD_FLAG_RISK_TIER off
232
+
233
+ Background:
234
+ Given an environment variable "GTD_FLAG_RISK_TIER" set to "0"
235
+ And a file "src/calc.ts" with:
236
+ """
237
+ export const add = (a: number, b: number) => a + b
238
+ """
239
+ And a file "src/calc.test.ts" with:
240
+ """
241
+ import { expect, it } from "vitest"
242
+ it("adds", () => expect(1 + 1).toBe(2))
243
+ """
244
+ And the working tree is committed as "feat: add calculator"
245
+ And a file "TODO.md" with:
246
+ """
247
+ # Add subtract
248
+
249
+ subtract(a, b) returns the difference.
250
+ """
251
+ And step "unwind" changes nothing
252
+ And step "start-gate.check" changes nothing
253
+
254
+ Scenario: the flag-off twin asks for no tier and marks no commit
255
+ Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
256
+ """
257
+ # Requirements
258
+
259
+ ## Risk Tier
260
+
261
+ low — trivial
262
+
263
+ ## Production Signals
264
+
265
+ - error rate stays flat
266
+ """
267
+ And step "design.gate.answer" changes nothing
268
+ When the process is driven
269
+ Then the process rests at "seam.naive"
270
+ And no commit body contains "Gtd-Mark"
271
+
272
+ Scenario: the flag-off twin's triage prompt has no tier section
273
+ When the process is driven
274
+ Then the process rests at "design.triage"
275
+ And the resting step's content does not contain "## Risk Tier"
276
+ And the resting step's content does not contain "## Production Signals"
277
+
278
+
279
+ Scenario: the flag-off twin keeps today's flag demand
280
+ Given step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
281
+ """
282
+ # Requirements
283
+
284
+ ## Risk Tier
285
+
286
+ low — trivial
287
+ """
288
+ And step "design.gate.answer" changes nothing
289
+ And step "seam.naive" writes "src/calc.ts" with:
290
+ """
291
+ export const add = (a: number, b: number) => a + b
292
+ export const subtract = (a: number, b: number) => a - b
293
+ """
294
+ And judge step "seam.judge" answers:
295
+ """
296
+ [{"id":"shape","answer":"land","p":0.9}]
297
+ """
298
+ When the process is driven
299
+ Then the process rests at "implement.building"
300
+ And the resting step's content contains "Put the new behaviour behind a new release flag"
301
+ And the resting step's content does not contain "Add no release flag"
@@ -16,6 +16,7 @@ import { coder, FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
16
16
  import * as t from "../shared/text.js"
17
17
  import { defaults } from "../shared/vars.js"
18
18
  import { refactorFirst } from "./refactor.js"
19
+ import type { RiskTier } from "./tier.js"
19
20
 
20
21
  /** Naive-pass retries after a `reject`; the next `reject` rests at `seam.steer`. */
21
22
  const NAIVE_RETRIES = 2
@@ -39,9 +40,9 @@ export const naivePass = (rejection?: t.SeamRejection): Promise<void> =>
39
40
  system: t.builderSystem(),
40
41
  })
41
42
 
42
- export const implement = (reference: string): Promise<void> =>
43
+ export const implement = (reference: string, tier?: RiskTier): Promise<void> =>
43
44
  scope("implement", async () => {
44
- await t.agentWithSkills("building", t.implementBuildingPrompt(reference), {
45
+ await t.agentWithSkills("building", t.implementBuildingPrompt(reference, tier), {
45
46
  label: "Implementing",
46
47
  model: coder(),
47
48
  system: t.builderSystem(),
@@ -94,7 +95,7 @@ const decide = (): Promise<Decision> =>
94
95
  }
95
96
  })
96
97
 
97
- export const seamBuild = async (): Promise<void> => {
98
+ export const seamBuild = async (tier?: RiskTier): Promise<void> => {
98
99
  const { reference, shape } = await decide()
99
100
  if (shape !== undefined && !(await refactorFirst(shape, { reference }))) {
100
101
  // Unreadable strays were never judged, and no refactor runs: a person reads the diff.
@@ -106,5 +107,5 @@ export const seamBuild = async (): Promise<void> => {
106
107
  }),
107
108
  )
108
109
  }
109
- await implement(reference)
110
+ await implement(reference, tier)
110
111
  }
@@ -1,10 +1,11 @@
1
1
  import { human, run } from "../../flows/index.js"
2
2
  import * as t from "../shared/text.js"
3
+ import { riskTierOn } from "./tier.js"
3
4
  import { ARCHITECTURE, coder, FEEDBACK, planner, REQUIREMENTS, REVIEW } from "../shared/steps.js"
4
5
 
5
6
  /** Turn the sketch since `base` into `.gtd/REQUIREMENTS.md`. */
6
7
  export const triage = (base: string): Promise<void> =>
7
- t.agentWithSkills("triage", t.designTriagePrompt(base), {
8
+ t.agentWithSkills("triage", t.designTriagePrompt(base, riskTierOn()), {
8
9
  label: "Triaging the change",
9
10
  file: REQUIREMENTS,
10
11
  mode: "qa",
@@ -30,7 +31,7 @@ export const decompose = (): Promise<void> =>
30
31
 
31
32
  export const answerProductQuestions = (): Promise<void> =>
32
33
  human("gate.answer", {
33
- message: t.designGateAnswerMessage(),
34
+ message: t.designGateAnswerMessage(riskTierOn()),
34
35
  label: "Awaiting your product answers",
35
36
  file: REQUIREMENTS,
36
37
  mode: "qa",