@pmelab/gtd 46.0.0 → 47.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.
@@ -26,7 +26,6 @@ import {
26
26
  fixCheck,
27
27
  fixNits,
28
28
  fixRisks,
29
- QUALITY,
30
29
  REQUIREMENTS,
31
30
  REVIEW,
32
31
  reviewMissing,
@@ -173,7 +172,7 @@ export const isCode = (path: string): boolean => path !== ".gtd" && !path.starts
173
172
 
174
173
  /**
175
174
  * gtd writes `.gtd/REVIEW.md` from the quality lap's `report` — no reviewer
176
- * turn — and removes what the fixer left in `.gtd/QUALITY.md`. A re-review
175
+ * turn. A re-review
177
176
  * after fixed nits restores the doc as committed at `carry`, answers kept;
178
177
  * a fresh one leaves out the notes the human already answered.
179
178
  */
@@ -199,7 +198,6 @@ const composing = (
199
198
  undecided: parseStandards(read(STANDARDS) ?? "").undecided,
200
199
  },
201
200
  }),
202
- [QUALITY],
203
201
  )
204
202
  : showScript(carry, REVIEW, REVIEW),
205
203
  { label: "Composing the review", file: REVIEW, base },
@@ -305,7 +303,7 @@ export interface BuildTailOptions extends TailOptions {
305
303
  readonly report?: LensReport | undefined
306
304
  }
307
305
 
308
- /** The build tail: fix (when entered red), keep green unless `green`, the triaged quality lap, then human review since `base`. */
306
+ /** The build tail: fix (when entered red), keep green unless `green`, the quality lap, then human review since `base`. */
309
307
  export function buildTail(base: string, options?: BuildTailOptions): Promise<kit.ReviewOutcome>
310
308
  /** @deprecated Pass `buildTail(base, { fixFirst, green, ...options })`. */
311
309
  export function buildTail(
@@ -329,10 +327,7 @@ export function buildTail(
329
327
  )
330
328
  }
331
329
  const [base, form] = options
332
- return tail((escalations, report) => humanReview(base, escalations, { ...form, report }), {
333
- ...form,
334
- triage: true,
335
- })
330
+ return tail((escalations, report) => humanReview(base, escalations, { ...form, report }), form)
336
331
  }
337
332
 
338
333
  const isOptionalBoolean = (value: unknown): value is boolean | null | undefined =>
@@ -1,17 +1,18 @@
1
- import { read, refuse, removeScript, run, scope, showScript, vars } from "../../flows/index.js"
2
- import * as kit from "../../workflow-kit/index.js"
3
1
  import {
4
- escalation,
5
- FIX_CAP,
6
- guarded,
7
- healthy,
8
- type EscalationCount,
9
- type Guard,
10
- } from "./health.js"
2
+ read,
3
+ refuse,
4
+ removeScript,
5
+ run,
6
+ scope,
7
+ showScript,
8
+ vars,
9
+ writeScript,
10
+ } from "../../flows/index.js"
11
+ import * as kit from "../../workflow-kit/index.js"
12
+ import { guarded, healthy, type EscalationCount, type Guard } from "./health.js"
11
13
  import {
12
14
  awaitReview,
13
15
  fixCheck,
14
- fixQuality,
15
16
  fixRisks,
16
17
  QUALITY,
17
18
  REVIEW,
@@ -19,7 +20,7 @@ import {
19
20
  reviewMissing,
20
21
  reviewQuality,
21
22
  } from "./steps.js"
22
- import { NO_FINDINGS, triageFindings, type LensReport } from "./lenses.js"
23
+ import { findingsOf, NO_FINDINGS, triageFindings, type Finding, type LensReport } from "./lenses.js"
23
24
  import * as t from "./text.js"
24
25
 
25
26
  /** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
@@ -34,29 +35,30 @@ export const qualityLap = (
34
35
  guard?: Guard,
35
36
  lenses?: readonly string[],
36
37
  base?: string,
38
+ ): Promise<"clean" | "findings"> => lensLap(guard, lenses, base, () => {})
39
+
40
+ // `seen` gets each lens and every finding in the file once its turn is done.
41
+ const lensLap = (
42
+ guard: Guard | undefined,
43
+ lenses: readonly string[] | undefined,
44
+ base: string | undefined,
45
+ seen: (lens: string, findings: readonly Finding[]) => void,
37
46
  ): Promise<"clean" | "findings"> =>
38
47
  kit.qualityLap(
39
48
  lenses ?? kit.lensEntriesOf(vars.qualityReviews ?? ""),
40
- guarded((entry: string) => {
49
+ guarded(async (entry: string) => {
41
50
  const { lens, model } = kit.lensOf(entry)
42
- return reviewQuality(lens, base, model)
51
+ await reviewQuality(lens, base, model)
52
+ seen(lens, findingsOf(read(QUALITY) ?? ""))
43
53
  }, guard),
44
54
  QUALITY,
45
55
  )
46
56
 
47
- /** Resolves `true` once `.gtd/QUALITY.md` is resolved, `false` when the fix cap escalated instead. */
48
- export const fixQualityFindings = (
49
- escalations: EscalationCount,
50
- guard?: Guard,
51
- guardRests = true,
52
- rules?: string,
53
- ): Promise<boolean> =>
54
- kit.fixFindings({
55
- fix: guarded(() => fixQuality(rules), guard),
56
- findings: QUALITY,
57
- cap: FIX_CAP,
58
- escalate: () => escalation(escalations, guard, guardRests, rules),
59
- })
57
+ // What the fixer left is in the lap's report by now; the next lap must not append to it.
58
+ const closeQuality = async (): Promise<void> => {
59
+ if (read(QUALITY) === undefined) return
60
+ await run("quality.closing", removeScript([QUALITY]), { label: "Clearing quality findings" })
61
+ }
60
62
 
61
63
  /** `.gtd/REVIEW.md` removed at the end of a round at commit `round`. */
62
64
  export const closeReview = (round: string): Promise<void> =>
@@ -89,17 +91,12 @@ export interface TailOptions {
89
91
  readonly lensBase?: string | undefined
90
92
  /** Appended to every fix and describe prompt of the tail. */
91
93
  readonly rules?: string | undefined
92
- /**
93
- * Judge the lap's findings: fix the mechanical ones, drop noise, and hand
94
- * `finish` the rest as notes for the human review; default `false`. `finish`
95
- * must remove `.gtd/QUALITY.md`, which still holds what the fixer left.
96
- */
97
- readonly triage?: boolean | undefined
98
94
  }
99
95
 
100
96
  /**
101
- * The build tail: fix (when entered red), keep green, the quality lap, then
102
- * `finish` — whatever the calling workflow runs last, a human review or nothing.
97
+ * The build tail: fix (when entered red), keep green, the quality lap — a
98
+ * judge sorts its findings, the mechanical ones are fixed, noise dropped —
99
+ * then `finish` with the rest as notes: a human review, or carrying them on.
103
100
  */
104
101
  export function tail<T>(
105
102
  finish: (escalations: EscalationCount, report: LensReport) => Promise<T>,
@@ -108,7 +105,7 @@ export function tail<T>(
108
105
  /** @deprecated Pass `tail(finish, { fixFirst, green, guard, lenses, guardRests, lensBase, rules })`. */
109
106
  export function tail<T>(
110
107
  fixFirst: boolean,
111
- finish: (escalations: EscalationCount) => Promise<T>,
108
+ finish: (escalations: EscalationCount, report: LensReport) => Promise<T>,
112
109
  green?: boolean,
113
110
  guard?: Guard,
114
111
  lenses?: readonly string[],
@@ -164,29 +161,34 @@ const runTail = <T>(finish: Finish<T>, options: TailOptions): Promise<T> =>
164
161
  const guardRests = options.guardRests === undefined ? true : options.guardRests
165
162
  const escalations: EscalationCount = { rounds: 0 }
166
163
  const fix = () => fixCheck(rules)
167
- const repair = async () => {
168
- await guarded(fix, guard)()
169
- await healthy(fix, { fixesSoFar: 1, escalations, guard, guardRests, rules })
170
- }
171
164
  // fixFirst's own fix + healthy below already is the full run.
172
165
  // Truthy, not `true`: an untyped config's flags read as they always did.
173
- if (options.fixFirst) await repair()
174
- else if (!options.green) await healthy(fix, { escalations, guard, guardRests, rules })
166
+ if (options.fixFirst) {
167
+ await guarded(fix, guard)()
168
+ await healthy(fix, { fixesSoFar: 1, escalations, guard, guardRests, rules })
169
+ } else if (!options.green) await healthy(fix, { escalations, guard, guardRests, rules })
175
170
  // The lap runs once a tail: after its findings are fixed, `finish` follows.
176
- const lap = await qualityLap(guard, lenses, lensBase)
177
- if (options.triage) {
178
- const report =
179
- lap === "findings"
180
- ? await triageFindings({ escalations, guard, guardRests, rules })
181
- : NO_FINDINGS
182
- return finish(escalations, report)
183
- }
184
- if (lap === "findings") {
185
- if (await fixQualityFindings(escalations, guard, guardRests, rules)) {
186
- await healthy(fix, { escalations, guard, guardRests, rules })
187
- } else await repair()
171
+ // Lenses append, so a title the file did not hold before a lens's turn is that lens's.
172
+ const byTitle: { title: string; lens: string }[] = []
173
+ const lap = await lensLap(guard, lenses, lensBase, (lens, findings) => {
174
+ const before = byTitle.map((f) => f.title)
175
+ for (const { title } of findings) {
176
+ const at = before.indexOf(title)
177
+ if (at >= 0) before.splice(at, 1)
178
+ else byTitle.push({ title, lens })
179
+ }
180
+ })
181
+ // Asked once per finding in file order, so a title two lenses wrote goes to each in turn.
182
+ const lensOf = (f: Finding): string | undefined => {
183
+ const at = byTitle.findIndex((e) => e.title === f.title)
184
+ return at < 0 ? undefined : byTitle.splice(at, 1)[0]!.lens
188
185
  }
189
- return finish(escalations, NO_FINDINGS)
186
+ const report =
187
+ lap === "findings"
188
+ ? await triageFindings({ escalations, guard, guardRests, rules, lensOf })
189
+ : NO_FINDINGS
190
+ await closeQuality()
191
+ return finish(escalations, report)
190
192
  })
191
193
 
192
194
  /**
@@ -198,10 +200,22 @@ export const handOffReview = (
198
200
  base: string,
199
201
  escalations: EscalationCount,
200
202
  guard?: Guard,
203
+ report: LensReport = NO_FINDINGS,
201
204
  ): Promise<void> =>
202
205
  kit.reviewToHandOff(base, {
203
206
  file: REVIEW,
204
- reviewing: guarded((b, carry) => reviewing(b, carry), guard),
207
+ reviewing: guarded(async (b, carry) => {
208
+ await reviewing(b, carry)
209
+ // gtd adds the lap's chunks itself: .gtd/QUALITY.md is gone, so a reviewer leaving one out would lose it.
210
+ const review = read(REVIEW)
211
+ if (review === undefined) return
212
+ const full = t.withLapFindings(review, report)
213
+ if (full !== review) {
214
+ await run("review.carrying", writeScript(REVIEW, full), {
215
+ label: "Adding the quality lap's findings",
216
+ })
217
+ }
218
+ }, guard),
205
219
  fixRisks: guarded(fixRisks, guard),
206
220
  keepGreen: () => healthy(fixCheck, { escalations, guard }),
207
221
  awaitReview: (b) => awaitReview(b, t.handOffAwaitReviewMessage(b)),
@@ -220,6 +234,9 @@ export const handOffReview = (
220
234
  "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note to hand to fix.",
221
235
  })
222
236
 
223
- /** The tail the fix workflow runs: ends at the hand-off review since `base`. */
237
+ /** The tail the fix workflow runs: ends at the hand-off review since `base`, which gets what the lap left. */
224
238
  export const handOffTail = (fixFirst: boolean, base: string, guard?: Guard): Promise<void> =>
225
- tail((escalations) => handOffReview(base, escalations, guard), { fixFirst, guard })
239
+ tail((escalations, report) => handOffReview(base, escalations, guard, report), {
240
+ fixFirst,
241
+ guard,
242
+ })
@@ -117,16 +117,22 @@ export const qualityFindingsFile = (
117
117
  ): string =>
118
118
  `# Quality\n\n${findings.map((f) => `## ${f.title}\n\n${f.body}`.trim()).join("\n\n")}\n`
119
119
 
120
+ /** Where a reported finding came from; absent when unknown or, for `slice`, a single-slice build. */
121
+ export interface Origin {
122
+ readonly lens?: string | undefined
123
+ readonly slice?: string | undefined
124
+ }
125
+
120
126
  export interface ComposedReview {
121
- readonly notes: readonly {
127
+ readonly notes: readonly (Origin & {
122
128
  readonly title: string
123
129
  readonly body: string
124
130
  readonly unresolved?: true
125
131
  /** A fix turn removed its chunk while renaming another. */
126
132
  readonly unclear?: true
127
- }[]
128
- readonly fixed: readonly { readonly title: string; readonly commit: string }[]
129
- readonly noise: readonly { readonly title: string; readonly p: number }[]
133
+ })[]
134
+ readonly fixed: readonly (Origin & { readonly title: string; readonly commit: string })[]
135
+ readonly noise: readonly (Origin & { readonly title: string; readonly p: number })[]
130
136
  /** `Then` lines a `.feature` file at the review base no longer carries. */
131
137
  readonly weakened?: readonly RemovedAssertions[]
132
138
  /** The commit that fixed the human's nits, on the review that follows it. */
@@ -154,6 +160,34 @@ const unboxed = (body: string): string =>
154
160
 
155
161
  const short = (hash: string): string => hash.slice(0, 7)
156
162
 
163
+ /** `slice 2, lens tests`; empty when neither is known. */
164
+ const originOf = (f: Origin): string =>
165
+ [
166
+ ...(f.slice === undefined ? [] : [`slice ${f.slice}`]),
167
+ ...(f.lens === undefined ? [] : [`lens ${f.lens}`]),
168
+ ].join(", ")
169
+
170
+ const originLine = (f: Origin): string => {
171
+ const origin = originOf(f)
172
+ return origin === "" ? "" : `_From ${origin}._\n\n`
173
+ }
174
+
175
+ const withOrigin = (f: Origin, rest: string): string =>
176
+ [rest, originOf(f)].filter((s) => s !== "").join(", ")
177
+
178
+ type LapNote = ComposedReview["notes"][number]
179
+
180
+ const noteBody = (n: LapNote): string => {
181
+ const body = unboxed(n.body) || "_No detail given._"
182
+ if (n.unresolved === true) return `_The fixer left this unresolved._\n\n${body}`
183
+ if (n.unclear === true)
184
+ return `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${body}`
185
+ return body
186
+ }
187
+
188
+ // A one-slice review needs no provenance: only carried findings name their slice and lens.
189
+ const carriedOnly = (f: Origin): Origin => (f.slice === undefined ? {} : f)
190
+
157
191
  /**
158
192
  * \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note, each ending in its
159
193
  * answer boxes, then the fixed and noise lists. Chunk titles are made unique —
@@ -200,23 +234,14 @@ export const composedReview = (base: string, report: ComposedReview): string =>
200
234
  ]),
201
235
  ...conflicts,
202
236
  ...notes.map((n) =>
203
- chunk(
204
- n.title,
205
- `${
206
- n.unresolved === true
207
- ? `_The fixer left this unresolved._\n\n${unboxed(n.body)}`
208
- : n.unclear === true
209
- ? `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${unboxed(n.body)}`
210
- : unboxed(n.body) || "_No detail given._"
211
- }\n\n${answerBoxes}`,
212
- ),
237
+ chunk(n.title, `${originLine(carriedOnly(n))}${noteBody(n)}\n\n${answerBoxes}`),
213
238
  ),
214
239
  ...(report.fixed.length === 0
215
240
  ? []
216
241
  : [
217
242
  chunk(
218
243
  "Fixed automatically",
219
- report.fixed.map((f) => `- ${f.title} (${short(f.commit)})`).join("\n"),
244
+ report.fixed.map((f) => fixedLine(f, carriedOnly(f))).join("\n"),
220
245
  ),
221
246
  ]),
222
247
  ...(added.length === 0
@@ -229,7 +254,12 @@ export const composedReview = (base: string, report: ComposedReview): string =>
229
254
  ]),
230
255
  ...(report.noise.length === 0
231
256
  ? []
232
- : [chunk("Dropped as noise", report.noise.map((f) => `- ${f.title} (p=${f.p})`).join("\n"))]),
257
+ : [
258
+ chunk(
259
+ "Dropped as noise",
260
+ report.noise.map((f) => noiseLine(f, carriedOnly(f))).join("\n"),
261
+ ),
262
+ ]),
233
263
  ]
234
264
  const body =
235
265
  chunks.length > 0
@@ -243,19 +273,6 @@ export const composedReview = (base: string, report: ComposedReview): string =>
243
273
  return `# Review: ${short(base)}\n\n<!-- base: ${base} -->\n\n${body.join("\n\n")}\n`
244
274
  }
245
275
 
246
- export const qualityUnresolvedMessage = (): string =>
247
- `The quality lap's fix turns hit their cap and \`.gtd/QUALITY.md\` still
248
- holds findings. Nothing after this fixes them, so the process will not
249
- go on with them open.
250
-
251
- Fix what matters by hand, then delete or empty \`.gtd/QUALITY.md\` —
252
- dropping a finding is your call to make.
253
-
254
- What each change does next (then run \`gtd land\`):
255
- - **Delete or empty \`.gtd/QUALITY.md\`** — the suite runs on your edits, then the next slice is built, or after the last one you review the change (**build.review.reviewing**).
256
- - **Anything else** — rests here again.
257
- `
258
-
259
276
  export const startGateBlockedMessage = (): string =>
260
277
  `The test baseline is red — gtd will not start new work on a broken suite.
261
278
  \`.gtd/FEEDBACK.md\` holds the failing output.
@@ -1125,6 +1142,45 @@ const lapRange: Record<ReviewRange, string> = {
1125
1142
  "it covers the whole refactor since it started, on every round. A refactor may update an existing test's call sites only: give any test-file hunk its own chunk, and say so if it changes a test, an assertion or an expected value.",
1126
1143
  }
1127
1144
 
1145
+ /** What the quality lap left for a reviewer-written review: its notes, fixes and noise. */
1146
+ export type LapFindings = Pick<ComposedReview, "notes" | "fixed" | "noise">
1147
+
1148
+ const fixedLine = (f: ComposedReview["fixed"][number], origin: Origin = f): string =>
1149
+ `- ${f.title} (${withOrigin(origin, short(f.commit))})`
1150
+
1151
+ const noiseLine = (f: ComposedReview["noise"][number], origin: Origin = f): string =>
1152
+ `- ${f.title} (${withOrigin(origin, `p=${f.p}`)})`
1153
+
1154
+ /**
1155
+ * `review` with the lap's notes appended as prose chunks — no pointer, no
1156
+ * answer boxes — then what it fixed and dropped. A title the review already
1157
+ * holds gets a numbered suffix, as notes are told apart by title.
1158
+ */
1159
+ export const withLapFindings = (review: string, { notes, fixed, noise }: LapFindings): string => {
1160
+ const used = new Set(
1161
+ review
1162
+ .split("\n")
1163
+ .filter((line) => line.startsWith("## "))
1164
+ .map((line) => line.slice(3).trim()),
1165
+ )
1166
+ const unique = (title: string): string => {
1167
+ let name = title
1168
+ for (let n = 2; used.has(name); n++) name = `${title} (${n})`
1169
+ used.add(name)
1170
+ return name
1171
+ }
1172
+ const chunks = [
1173
+ ...notes.map((n) => `## ${unique(n.title)}\n\n${originLine(n)}${noteBody(n)}`),
1174
+ ...(fixed.length === 0
1175
+ ? []
1176
+ : [`## ${unique("Fixed automatically")}\n\n${fixed.map((f) => fixedLine(f)).join("\n")}`]),
1177
+ ...(noise.length === 0
1178
+ ? []
1179
+ : [`## ${unique("Dropped as noise")}\n\n${noise.map((f) => noiseLine(f)).join("\n")}`]),
1180
+ ]
1181
+ return chunks.length === 0 ? review : `${review.trimEnd()}\n\n${chunks.join("\n\n")}\n`
1182
+ }
1183
+
1128
1184
  export const buildReviewReviewingPrompt = (
1129
1185
  base: string,
1130
1186
  lap: ReviewRange,
@@ -1,121 +0,0 @@
1
- @inmem
2
- Feature: quality findings left unresolved between slices hold the next slice back
3
-
4
- Between slices no review reads the quality lap's leftovers, so when the fix
5
- cap leaves `.gtd/QUALITY.md` behind the process rests for a person before
6
- the next slice. The last slice's leftovers become review notes instead (see
7
- `lens-review.feature`).
8
-
9
- Background:
10
- Given the shipped workflows, as gtd install scaffolds them
11
- And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
12
- And an environment variable "GTD_FLAGMODE" set to "false"
13
- And an environment variable "GTD_SLICEMAXCRITERIA" set to "1"
14
- And a file "src/calc.ts" with:
15
- """
16
- export const add = (a: number, b: number) => a + b
17
- """
18
- And the working tree is committed as "feat: add calculator"
19
- And a file "TODO.md" with:
20
- """
21
- # More arithmetic
22
-
23
- subtract and multiply.
24
- """
25
- And step "unwind" changes nothing
26
- And step "start-gate.check" changes nothing
27
- And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
28
- """
29
- # Requirements
30
-
31
- ## Acceptance Criteria
32
-
33
- - AC-1: subtract(a, b) returns a - b.
34
- - AC-2: multiply(a, b) returns a * b.
35
- """
36
- And step "design.gate.answer" changes nothing
37
- And step "design.slicing" writes ".gtd/SLICES.md" with:
38
- """
39
- # Slices
40
-
41
- ## Subtract
42
-
43
- - AC-1
44
-
45
- ## Multiply
46
-
47
- - AC-2
48
- """
49
- And step "seam.naive", when prompted with "This slice builds AC-1 only", writes "src/sub.test.ts" with:
50
- """
51
- it("[AC-1] subtracts", () => {})
52
- """
53
- And step "seam.naive", when prompted with "This slice builds AC-2 only", writes "src/mul.test.ts" with:
54
- """
55
- it("[AC-2] multiplies", () => {})
56
- """
57
- And step "seam.check" changes nothing
58
- And step "implement.applying" runs its script
59
- And step "implement.health.check" changes nothing
60
- And step "implement.closing" runs its script
61
- And judge step "implement.coverage", when its questions contain "AC-1", answers:
62
- """
63
- [{"id":"AC-1","answer":"yes","p":0.9}]
64
- """
65
- And judge step "implement.coverage", when its questions contain "AC-2", answers:
66
- """
67
- [{"id":"AC-2","answer":"yes","p":0.9}]
68
- """
69
-
70
- Scenario: findings the fix cap left after a slice rest at build.quality-unresolved
71
- Given step "build.quality.correctness.reviewing" writes ".gtd/QUALITY.md" with:
72
- """
73
- # Quality
74
-
75
- ## subtract has no negative test
76
-
77
- Add a test subtracting a larger number.
78
- """
79
- And step "build.fix.quality.fixing" from now on changes nothing
80
- And step "build.health.describe" writes ".gtd/ESCALATION.md" with:
81
- """
82
- # Escalation
83
-
84
- The fix turn changed nothing.
85
- """
86
- When the process is driven
87
- Then the process rests at "build.health.stop"
88
- Given step "build.fix" writes "src/calc.ts" with:
89
- """
90
- export const add = (a: number, b: number) => a + b
91
- // checked
92
- """
93
- And step "build.health.check" changes nothing
94
- When gtd lands
95
- And the process is driven
96
- Then the process rests at "build.quality-unresolved"
97
- And ".gtd/QUALITY.md" contains "subtract has no negative test"
98
-
99
- When gtd lands
100
- And the process is driven
101
- Then the process rests at "build.quality-unresolved"
102
-
103
- Given the file ".gtd/QUALITY.md" is deleted
104
- And step "build.quality.correctness.reviewing" from now on changes nothing
105
- And step "build.review.composing" runs its script
106
- When gtd lands
107
- And the process is driven
108
- Then the process rests at "build.review.await-review"
109
- And the commit subjects end with:
110
- """
111
- gtd(human): build.quality-unresolved → build.health.check
112
- gtd(check): build.health.check → seam.naive
113
- gtd(agent): seam.naive → seam.check
114
- gtd(check): seam.check → implement.applying
115
- gtd(check): implement.applying → implement.health.check
116
- gtd(check): implement.health.check → implement.coverage
117
- gtd(judge): implement.coverage → implement.closing
118
- gtd(check): implement.closing → build.quality.correctness.reviewing
119
- gtd(agent): build.quality.correctness.reviewing → build.review.composing
120
- gtd(check): build.review.composing → build.review.await-review
121
- """