@pmelab/gtd 54.0.0 → 56.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 +109 -104
- package/dist/chunks/{reserved-CQ7amcKG.mjs → reserved-B8EC-WE8.mjs} +124 -230
- package/dist/chunks/{review-CXzUXJHL.mjs → review-HdrX0FJ7.mjs} +30 -125
- package/dist/chunks/{steering-CLhu8C7q.mjs → steering-CCNvngSH.mjs} +256 -65
- package/dist/chunks/{tail-JZ9PAOwp.mjs → tail-BSLah2mA.mjs} +155 -63
- package/dist/gtd.bundle.mjs +259 -22
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +6 -6
- package/dist/web/main.css +1 -1
- package/dist/web/main.js +551 -66
- package/dist/workflow-kit.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +47 -102
- package/dist/workflows/fix.mjs +3 -3
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +614 -574
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +3 -3
- package/src/cli/Cli.ts +5 -5
- package/src/edge/Edge.test.ts +1 -1
- package/src/steering/FORMATS.md +5 -1
- package/src/steering/SteeringFormats.test.ts +2 -2
- package/src/steering/contract.test.ts +1 -0
- package/src/steering/findings.test.ts +47 -0
- package/src/steering/findings.ts +350 -0
- package/src/steering/fixtures/findings/gate.md +34 -0
- package/src/steering/fixtures/findings/gate.read.json +87 -0
- package/src/steering/index.test.ts +2 -2
- package/src/steering/index.ts +4 -3
- package/src/steering/review.test.ts +1 -75
- package/src/steering/review.ts +1 -74
- package/src/workflow/compile.test.ts +1 -0
- package/src/workflows/feature/criteria-coverage.feature +104 -0
- package/src/workflows/feature/feature.test.ts +1 -1
- package/src/workflows/feature/feature.ts +17 -20
- package/src/workflows/feature/green.ts +2 -5
- package/src/workflows/feature/lens-review.feature +323 -171
- package/src/workflows/feature/naive-acceptance.feature +31 -32
- package/src/workflows/feature/prototype.ts +5 -8
- package/src/workflows/feature/refactor-absorbs.feature +1 -7
- package/src/workflows/feature/refactor-first.feature +2 -10
- package/src/workflows/feature/refactor-rebuild.feature +20 -36
- package/src/workflows/feature/refactor-review.feature +33 -117
- package/src/workflows/feature/refactor.test.ts +0 -2
- package/src/workflows/feature/refactor.ts +0 -1
- package/src/workflows/feature/review-gate.feature +6 -22
- package/src/workflows/feature/reviewer-walkthrough.feature +16 -103
- package/src/workflows/feature/seam.test.ts +19 -101
- package/src/workflows/feature/seam.ts +13 -89
- package/src/workflows/feature/standards.feature +75 -34
- package/src/workflows/fix/fix-lens-origin.feature +20 -7
- package/src/workflows/fix/fix-quality.feature +104 -23
- package/src/workflows/review/review-door.feature +62 -59
- package/src/workflows/shared/access.test.ts +1 -0
- package/src/workflows/shared/learn.test.ts +0 -14
- package/src/workflows/shared/learn.ts +1 -6
- package/src/workflows/shared/lenses.ts +149 -43
- package/src/workflows/shared/review.test.ts +30 -27
- package/src/workflows/shared/review.ts +29 -196
- package/src/workflows/shared/skills.test.ts +3 -1
- package/src/workflows/shared/steps.test.ts +9 -6
- package/src/workflows/shared/steps.ts +12 -3
- package/src/workflows/shared/tail.test.ts +20 -2
- package/src/workflows/shared/tail.ts +38 -16
- package/src/workflows/shared/text.test.ts +122 -294
- package/src/workflows/shared/text.ts +181 -358
- package/src/workflows/shared/vars.ts +1 -1
- package/src/workflows/feature/review-answers.feature +0 -324
- package/src/workflows/feature/severity-budget.feature +0 -116
- package/src/workflows/feature/severity-sorted-review.feature +0 -198
|
@@ -2118,7 +2118,7 @@ describe("answer boxes", async () => {
|
|
|
2118
2118
|
})
|
|
2119
2119
|
|
|
2120
2120
|
describe("review — sectioned layout", async () => {
|
|
2121
|
-
const { reviewChunks, reviewDecisions, reviewSections,
|
|
2121
|
+
const { reviewChunks, reviewDecisions, reviewSections, unansweredNotes } =
|
|
2122
2122
|
await import("./review.js")
|
|
2123
2123
|
const boxes = (ticked?: 0 | 1 | 2): string[] => [
|
|
2124
2124
|
"Tick exactly one:",
|
|
@@ -2230,78 +2230,4 @@ describe("review — sectioned layout", async () => {
|
|
|
2230
2230
|
.map((a) => a.title)
|
|
2231
2231
|
expect(titles.some((t) => t.includes("A finding"))).toBe(false)
|
|
2232
2232
|
})
|
|
2233
|
-
|
|
2234
|
-
describe("sectionedReview", () => {
|
|
2235
|
-
const flat = [
|
|
2236
|
-
...head,
|
|
2237
|
-
"## Weakened scenarios",
|
|
2238
|
-
"",
|
|
2239
|
-
"- a.feature — s — Then x",
|
|
2240
|
-
"",
|
|
2241
|
-
"## A finding",
|
|
2242
|
-
"",
|
|
2243
|
-
"_should: contract_",
|
|
2244
|
-
"",
|
|
2245
|
-
"Leaks a token.",
|
|
2246
|
-
"",
|
|
2247
|
-
...boxes(),
|
|
2248
|
-
"",
|
|
2249
|
-
"Wiring",
|
|
2250
|
-
"------",
|
|
2251
|
-
"",
|
|
2252
|
-
"### Detail",
|
|
2253
|
-
"",
|
|
2254
|
-
"- [ ] ./src/b.ts#3-4 — wires it",
|
|
2255
|
-
"",
|
|
2256
|
-
"## Fixed automatically",
|
|
2257
|
-
"",
|
|
2258
|
-
"- f (fedcba9)",
|
|
2259
|
-
"",
|
|
2260
|
-
].join("\n")
|
|
2261
|
-
|
|
2262
|
-
it("moves boxed chunks under Findings and the named ones under Walkthrough, a level deeper", () => {
|
|
2263
|
-
expect(sectionedReview(flat, new Set(["Wiring"]))).toBe(
|
|
2264
|
-
[
|
|
2265
|
-
...head,
|
|
2266
|
-
"## Weakened scenarios",
|
|
2267
|
-
"",
|
|
2268
|
-
"- a.feature — s — Then x",
|
|
2269
|
-
"",
|
|
2270
|
-
"## Findings",
|
|
2271
|
-
"",
|
|
2272
|
-
"### A finding",
|
|
2273
|
-
"",
|
|
2274
|
-
"_should: contract_",
|
|
2275
|
-
"",
|
|
2276
|
-
"Leaks a token.",
|
|
2277
|
-
"",
|
|
2278
|
-
...boxes(),
|
|
2279
|
-
"",
|
|
2280
|
-
"## Walkthrough",
|
|
2281
|
-
"",
|
|
2282
|
-
"### Wiring",
|
|
2283
|
-
"",
|
|
2284
|
-
"#### Detail",
|
|
2285
|
-
"",
|
|
2286
|
-
"- [ ] ./src/b.ts#3-4 — wires it",
|
|
2287
|
-
"",
|
|
2288
|
-
"## Fixed automatically",
|
|
2289
|
-
"",
|
|
2290
|
-
"- f (fedcba9)",
|
|
2291
|
-
"",
|
|
2292
|
-
].join("\n"),
|
|
2293
|
-
)
|
|
2294
|
-
})
|
|
2295
|
-
|
|
2296
|
-
it("is valid and stable once sectioned", () => {
|
|
2297
|
-
const once = sectionedReview(flat, new Set(["Wiring"]))
|
|
2298
|
-
expect(review.validate(once)).toEqual([])
|
|
2299
|
-
expect(sectionedReview(once, new Set(["Wiring"]))).toBe(once)
|
|
2300
|
-
})
|
|
2301
|
-
|
|
2302
|
-
it("leaves a review with nothing to group byte for byte", () => {
|
|
2303
|
-
const plain = [...head, "## Nothing to note", "", "Review the diff yourself.", ""].join("\n")
|
|
2304
|
-
expect(sectionedReview(plain, new Set())).toBe(plain)
|
|
2305
|
-
})
|
|
2306
|
-
})
|
|
2307
2233
|
})
|
package/src/steering/review.ts
CHANGED
|
@@ -96,7 +96,7 @@ export const ANSWER_WONT_FIX = "won't fix"
|
|
|
96
96
|
|
|
97
97
|
/** A chunk's leading `_critical: security_` paragraph: the tier gtd ranked it with, and why. */
|
|
98
98
|
const SEVERITY_LINE_RE = /^_((?:critical|should|could)(?::[^_\n]*)?)_$/
|
|
99
|
-
|
|
99
|
+
const severityLine = (label: string): string => `_${label}_`
|
|
100
100
|
|
|
101
101
|
interface ReviewDoc {
|
|
102
102
|
readonly shortHash?: string
|
|
@@ -1591,76 +1591,3 @@ export const reviewSections = (
|
|
|
1591
1591
|
section: s.section,
|
|
1592
1592
|
line: s.headingLine,
|
|
1593
1593
|
}))
|
|
1594
|
-
|
|
1595
|
-
const isBlankLine = (line: string | undefined): boolean => line !== undefined && line.trim() === ""
|
|
1596
|
-
|
|
1597
|
-
/** How many lines a chunk's heading takes: two for a setext heading and its underline. */
|
|
1598
|
-
const headingSize = ({ heading, headingLine }: ChunkBlock): number =>
|
|
1599
|
-
toLspPosition(heading.position!.end).line - headingLine + 1
|
|
1600
|
-
|
|
1601
|
-
/** \`block\` with its heading an ATX \`### \` on one line, and every heading in its body one level deeper. */
|
|
1602
|
-
const demoted = (chunk: ChunkBlock, block: readonly string[]): string[] => {
|
|
1603
|
-
const out = [...block]
|
|
1604
|
-
for (const node of chunk.body) {
|
|
1605
|
-
if (node.type !== "heading" || node.depth < 3 || node.depth > 5) continue
|
|
1606
|
-
const at = toLspPosition(node.position!.start).line - chunk.headingLine
|
|
1607
|
-
out[at] = out[at]!.replace(/^( {0,3})#/, "$1##")
|
|
1608
|
-
}
|
|
1609
|
-
const size = headingSize(chunk)
|
|
1610
|
-
const title = size > 1 ? `### ${out[0]!.trim()}` : out[0]!.replace(/^ {0,3}##/, "###")
|
|
1611
|
-
return [title, ...out.slice(size)]
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
/** A chunk's \`size\` heading lines, then its body with the blank lines around it trimmed. */
|
|
1615
|
-
const chunkText = (block: readonly string[], size: number): string => {
|
|
1616
|
-
const rest = block.slice(size)
|
|
1617
|
-
while (rest.length > 0 && isBlankLine(rest[0])) rest.shift()
|
|
1618
|
-
while (rest.length > 0 && isBlankLine(rest.at(-1))) rest.pop()
|
|
1619
|
-
const heading = block.slice(0, size).join("\n")
|
|
1620
|
-
return rest.length === 0 ? heading : `${heading}\n\n${rest.join("\n")}`
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
/**
|
|
1624
|
-
* A flat review regrouped: every chunk with answer boxes under `## Findings`
|
|
1625
|
-
* and every chunk titled in `walkthrough` under `## Walkthrough`, each in its
|
|
1626
|
-
* current order and one heading level deeper. Any other chunk stays a `## ` of
|
|
1627
|
-
* its own, before the sections when it came before them, after otherwise. An
|
|
1628
|
-
* already sectioned review comes back unchanged.
|
|
1629
|
-
*/
|
|
1630
|
-
export const sectionedReview = (content: string, walkthrough: ReadonlySet<string>): string => {
|
|
1631
|
-
const tree = parseMarkdown(content)
|
|
1632
|
-
const { chunks, sections } = splitChunks(tree, content)
|
|
1633
|
-
if (sections.length > 0 || chunks.length === 0) return content
|
|
1634
|
-
const { changesets } = parseReviewDoc(content)
|
|
1635
|
-
const lines = content.split("\n")
|
|
1636
|
-
const sectionOfChunk = (i: number): ReviewSection | undefined =>
|
|
1637
|
-
changesets[i]!.answers !== undefined
|
|
1638
|
-
? "findings"
|
|
1639
|
-
: walkthrough.has(changesets[i]!.title)
|
|
1640
|
-
? "walkthrough"
|
|
1641
|
-
: undefined
|
|
1642
|
-
const text = (i: number, demote: boolean): string => {
|
|
1643
|
-
const chunk = chunks[i]!
|
|
1644
|
-
const block = lines.slice(chunk.headingLine, chunks[i + 1]?.headingLine ?? lines.length)
|
|
1645
|
-
return chunkText(demote ? demoted(chunk, block) : block, demote ? 1 : headingSize(chunk))
|
|
1646
|
-
}
|
|
1647
|
-
const grouped = chunks.map((_, i) => sectionOfChunk(i))
|
|
1648
|
-
const first = grouped.findIndex((g) => g !== undefined)
|
|
1649
|
-
if (first === -1) return content
|
|
1650
|
-
const others = (keep: (i: number) => boolean): string[] =>
|
|
1651
|
-
chunks.flatMap((_, i) => (grouped[i] === undefined && keep(i) ? [text(i, false)] : []))
|
|
1652
|
-
const section = (s: ReviewSection): string[] => {
|
|
1653
|
-
const members = chunks.flatMap((_, i) => (grouped[i] === s ? [text(i, true)] : []))
|
|
1654
|
-
return members.length === 0 ? [] : [`## ${REVIEW_SECTIONS[s]}`, ...members]
|
|
1655
|
-
}
|
|
1656
|
-
const head = lines.slice(0, chunks[0]!.headingLine).join("\n").trim()
|
|
1657
|
-
return `${[
|
|
1658
|
-
head,
|
|
1659
|
-
...others((i) => i < first),
|
|
1660
|
-
...section("findings"),
|
|
1661
|
-
...section("walkthrough"),
|
|
1662
|
-
...others((i) => i > first),
|
|
1663
|
-
]
|
|
1664
|
-
.filter((p) => p !== "")
|
|
1665
|
-
.join("\n\n")}\n`
|
|
1666
|
-
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
@inmem
|
|
2
|
+
Feature: the criteria-coverage lens checks the change's tests against its criteria
|
|
3
|
+
|
|
4
|
+
The `criteria-coverage` quality lens gets the requirements the change was
|
|
5
|
+
built to, quoted in its prompt, and writes one finding per acceptance
|
|
6
|
+
criterion no test exercises. Its findings are triaged like any other lens's.
|
|
7
|
+
|
|
8
|
+
Background:
|
|
9
|
+
Given the shipped workflows, as gtd install scaffolds them
|
|
10
|
+
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
11
|
+
And an environment variable "GTD_QUALITYREVIEWS" set to "criteria-coverage"
|
|
12
|
+
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
13
|
+
And a file "src/calc.ts" with:
|
|
14
|
+
"""
|
|
15
|
+
export const add = (a: number, b: number) => a + b
|
|
16
|
+
"""
|
|
17
|
+
And the working tree is committed as "feat: add calculator"
|
|
18
|
+
And a file "TODO.md" with:
|
|
19
|
+
"""
|
|
20
|
+
# Add subtract
|
|
21
|
+
|
|
22
|
+
subtract(a, b) returns the difference.
|
|
23
|
+
"""
|
|
24
|
+
And step "unwind" changes nothing
|
|
25
|
+
And step "start-gate.check" changes nothing
|
|
26
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
27
|
+
"""
|
|
28
|
+
# Requirements
|
|
29
|
+
|
|
30
|
+
## Impact
|
|
31
|
+
|
|
32
|
+
- the calculator module
|
|
33
|
+
|
|
34
|
+
## Acceptance Criteria
|
|
35
|
+
|
|
36
|
+
- AC-1: `subtract(5, 3)` returns 2.
|
|
37
|
+
- AC-2: `subtract(1, 3)` returns -2.
|
|
38
|
+
"""
|
|
39
|
+
And step "design.gate.answer" changes nothing
|
|
40
|
+
And step "seam.naive" writes "src/calc.test.ts" with:
|
|
41
|
+
"""
|
|
42
|
+
it("subtracts", () => expect(subtract(5, 3)).toBe(2))
|
|
43
|
+
"""
|
|
44
|
+
And step "seam.naive" writes "src/calc.ts" with:
|
|
45
|
+
"""
|
|
46
|
+
export const add = (a: number, b: number) => a + b
|
|
47
|
+
export const subtract = (a: number, b: number) => a - b
|
|
48
|
+
"""
|
|
49
|
+
And step "seam.check" changes nothing
|
|
50
|
+
And step "implement.applying" runs its script
|
|
51
|
+
And step "implement.health.check" changes nothing
|
|
52
|
+
And step "implement.closing" runs its script
|
|
53
|
+
|
|
54
|
+
Scenario: the lens gets the closed requirements and its finding per uncovered criterion is fixed
|
|
55
|
+
When the process is driven
|
|
56
|
+
Then the process rests at "build.quality.criteria-coverage.reviewing"
|
|
57
|
+
And ".gtd/REQUIREMENTS.md" does not exist
|
|
58
|
+
And the resting step's content contains " > - AC-1: `subtract(5, 3)` returns 2."
|
|
59
|
+
And the resting step's content contains " > - AC-2: `subtract(1, 3)` returns -2."
|
|
60
|
+
And the resting step's content contains "one finding per criterion no test exercises"
|
|
61
|
+
|
|
62
|
+
Given step "build.quality.criteria-coverage.reviewing" writes ".gtd/QUALITY.md" with:
|
|
63
|
+
"""
|
|
64
|
+
# Quality
|
|
65
|
+
|
|
66
|
+
## AC-2 is not tested
|
|
67
|
+
|
|
68
|
+
No test subtracts a larger number.
|
|
69
|
+
"""
|
|
70
|
+
And judge step "build.quality.triage" answers:
|
|
71
|
+
"""
|
|
72
|
+
[{"id":"Q1","answer":"mechanical","p":0.9}]
|
|
73
|
+
"""
|
|
74
|
+
And step "build.fix.quality.fixing" writes "src/calc.test.ts" with:
|
|
75
|
+
"""
|
|
76
|
+
it("subtracts", () => expect(subtract(5, 3)).toBe(2))
|
|
77
|
+
it("subtracts a larger number", () => expect(subtract(1, 3)).toBe(-2))
|
|
78
|
+
"""
|
|
79
|
+
And step "build.fix.quality.fixing" deletes ".gtd/QUALITY.md"
|
|
80
|
+
And step "build.health.check" changes nothing
|
|
81
|
+
And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
82
|
+
"""
|
|
83
|
+
# Review: abc1234
|
|
84
|
+
|
|
85
|
+
<!-- base: 0000000 -->
|
|
86
|
+
|
|
87
|
+
## Subtract
|
|
88
|
+
|
|
89
|
+
- [ ] ./src/calc.ts#2-2 — subtract(a, b)
|
|
90
|
+
"""
|
|
91
|
+
And step "build.review.carrying" runs its script
|
|
92
|
+
When the process is driven
|
|
93
|
+
Then the process rests at "build.review.await-review"
|
|
94
|
+
And ".gtd/REVIEW.md" contains "- AC-2 is not tested ("
|
|
95
|
+
And the commit subjects end with:
|
|
96
|
+
"""
|
|
97
|
+
gtd(check): implement.closing → build.quality.criteria-coverage.reviewing
|
|
98
|
+
gtd(agent): build.quality.criteria-coverage.reviewing → build.quality.triage
|
|
99
|
+
gtd(judge): build.quality.triage → build.fix.quality.fixing
|
|
100
|
+
gtd(agent): build.fix.quality.fixing → build.health.check
|
|
101
|
+
gtd(check): build.health.check → build.review.reviewing
|
|
102
|
+
gtd(agent): build.review.reviewing → build.review.carrying
|
|
103
|
+
gtd(check): build.review.carrying → build.review.await-review
|
|
104
|
+
"""
|
|
@@ -91,7 +91,7 @@ describe("afterTail feedback lap", () => {
|
|
|
91
91
|
await repo.drive({
|
|
92
92
|
"review-gate.check": () => undefined,
|
|
93
93
|
"build.health.check": () => undefined,
|
|
94
|
-
"build.review.
|
|
94
|
+
"build.review.reviewing": (_, r) => r.write(".gtd/REVIEW.md", review()),
|
|
95
95
|
})
|
|
96
96
|
repo.write(".gtd/REVIEW.md", review("also export multiply"))
|
|
97
97
|
await repo.land()
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { gate } from "../shared/health.js"
|
|
12
12
|
import { flagModeOn, shipped } from "../shared/options.js"
|
|
13
13
|
import { FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
|
|
14
|
-
import { refusePendingHandOff
|
|
14
|
+
import { refusePendingHandOff } from "../shared/tail.js"
|
|
15
15
|
import * as t from "../shared/text.js"
|
|
16
16
|
import {
|
|
17
17
|
criterionIds,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type StatedCriteria,
|
|
23
23
|
} from "./criteria.js"
|
|
24
24
|
import { design } from "./planning.js"
|
|
25
|
-
import { buildTail,
|
|
25
|
+
import { buildTail, isCode, reUnwind, type ReviewOutcome } from "../shared/review.js"
|
|
26
26
|
import { prototypeRounds } from "./prototype.js"
|
|
27
27
|
import { implement, implementing, seamBuild } from "./seam.js"
|
|
28
28
|
import { recordRisk, riskTierOn } from "./tier.js"
|
|
@@ -32,10 +32,9 @@ import { recordRisk, riskTierOn } from "./tier.js"
|
|
|
32
32
|
// green-baseline gate → design → a naive pass, judged with `flagMode` on: it
|
|
33
33
|
// lands as the implementation, or (refactoring first when the judge asks) is
|
|
34
34
|
// built fresh from → the health loop → a hands-on test of every criterion,
|
|
35
|
-
// fixed and re-tested → the triaged quality lap
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// sends a design → implement lap.
|
|
35
|
+
// fixed and re-tested → the triaged quality lap and its finding gate → a
|
|
36
|
+
// reviewer's walkthrough of the whole change for the human, which signs off
|
|
37
|
+
// (the episode ends back at `idle`) or sends a design → implement lap.
|
|
39
38
|
|
|
40
39
|
/** Revert the sketch that started the process out of the working tree; its intent survives in history. */
|
|
41
40
|
export const unwind = (): Promise<void> => {
|
|
@@ -57,6 +56,8 @@ export const buildFeature = async (firstBase: string): Promise<void> => {
|
|
|
57
56
|
const handedOut = new Set(listedIds())
|
|
58
57
|
const seam = await seamBuild(firstBase)
|
|
59
58
|
const used = gather(handedOut)
|
|
59
|
+
// Read before the implementation closes the file: the quality lap's coverage lens checks them.
|
|
60
|
+
const requirements = read(REQUIREMENTS)
|
|
60
61
|
const closed = await implementing({
|
|
61
62
|
reference: seam.reference,
|
|
62
63
|
tier,
|
|
@@ -72,7 +73,7 @@ export const buildFeature = async (firstBase: string): Promise<void> => {
|
|
|
72
73
|
!changesSince(seam.base).some((c) => isCode(c.path) && !carried.includes(c.path))
|
|
73
74
|
? firstBase
|
|
74
75
|
: seam.base
|
|
75
|
-
const outcome = await
|
|
76
|
+
const outcome = await buildTail(base, { green: true, lensBase: base, requirements })
|
|
76
77
|
await featureFeedback(outcome, base, { closed, used })
|
|
77
78
|
}
|
|
78
79
|
|
|
@@ -83,17 +84,6 @@ const gather = (handedOut: Set<string>): readonly string[] => {
|
|
|
83
84
|
return [...handedOut]
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
/** The quality lap, then a reviewer's walkthrough since `base` for the human. */
|
|
87
|
-
const reviewedTail = (
|
|
88
|
-
base: string,
|
|
89
|
-
lensBase: string,
|
|
90
|
-
lap: "first" | "feedback" = "first",
|
|
91
|
-
): Promise<ReviewOutcome> =>
|
|
92
|
-
tail(
|
|
93
|
-
(escalations, report) => humanReview(base, escalations, { report, walkthrough: true, lap }),
|
|
94
|
-
{ green: true, lensBase },
|
|
95
|
-
)
|
|
96
|
-
|
|
97
87
|
// seam.naive / seam.judge never run on a feedback lap: its design → implement is built unjudged
|
|
98
88
|
// and unrefactored.
|
|
99
89
|
const featureFeedback = async (
|
|
@@ -113,8 +103,14 @@ const featureFeedback = async (
|
|
|
113
103
|
gather(handedOut)
|
|
114
104
|
})
|
|
115
105
|
const tier = riskTierOn() ? recordRisk() : undefined
|
|
106
|
+
const requirements = read(REQUIREMENTS)
|
|
116
107
|
closed = await implementing({ tier, amend: true })
|
|
117
|
-
outcome = await
|
|
108
|
+
outcome = await buildTail(outcome.base, {
|
|
109
|
+
green: true,
|
|
110
|
+
lensBase,
|
|
111
|
+
requirements,
|
|
112
|
+
lap: "feedback",
|
|
113
|
+
})
|
|
118
114
|
}
|
|
119
115
|
}
|
|
120
116
|
|
|
@@ -133,8 +129,9 @@ export const afterTail = async (first: ReviewOutcome): Promise<void> => {
|
|
|
133
129
|
await reUnwind(outcome)
|
|
134
130
|
await design(outcome.base)
|
|
135
131
|
const tier = riskTierOn() ? recordRisk() : undefined
|
|
132
|
+
const requirements = read(REQUIREMENTS)
|
|
136
133
|
await implement({ tier, amend: true })
|
|
137
|
-
outcome = await buildTail(outcome.base, { green: true })
|
|
134
|
+
outcome = await buildTail(outcome.base, { green: true, requirements })
|
|
138
135
|
}
|
|
139
136
|
}
|
|
140
137
|
|
|
@@ -3,7 +3,7 @@ import { coder, FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
|
|
|
3
3
|
import * as t from "../shared/text.js"
|
|
4
4
|
import { requireCriteriaFrozen } from "./criteria.js"
|
|
5
5
|
|
|
6
|
-
export const green = (
|
|
6
|
+
export const green = (): Promise<void> =>
|
|
7
7
|
healthy(
|
|
8
8
|
() =>
|
|
9
9
|
t.agentWithSkills("fix.suite.fixing", t.implementFixPrompt(), {
|
|
@@ -13,10 +13,7 @@ export const green = (guard?: () => void): Promise<void> =>
|
|
|
13
13
|
system: t.builderSystem(),
|
|
14
14
|
}),
|
|
15
15
|
{
|
|
16
|
-
guard: () =>
|
|
17
|
-
requireCriteriaFrozen(REQUIREMENTS)
|
|
18
|
-
guard?.()
|
|
19
|
-
},
|
|
16
|
+
guard: () => requireCriteriaFrozen(REQUIREMENTS),
|
|
20
17
|
guardRests: false,
|
|
21
18
|
},
|
|
22
19
|
)
|