@pmelab/gtd 64.1.0 → 64.1.2
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 +9 -7
- package/dist/chunks/{flows-B7ZRCe-B.mjs → flows-dnIE_qbe.mjs} +7 -13
- package/dist/chunks/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-D8jQpDG7.mjs} +1 -2
- package/dist/chunks/{steering-Cem5i6Xl.mjs → steering-CQzL-lX1.mjs} +124 -187
- package/dist/chunks/{testkit-DfWZQCWQ.mjs → testkit-D539mnDI.mjs} +353 -496
- package/dist/chunks/{wire-CPNSm_9N.mjs → wire-CaPJ9Tme.mjs} +12 -24
- package/dist/flows.mjs +1 -2
- package/dist/gtd.bundle.mjs +878 -1767
- package/dist/gtd.core.mjs +190 -182
- package/dist/steering.mjs +1 -2
- package/dist/testkit/gherkin.mjs +3 -5
- package/dist/testkit.mjs +2 -2
- package/dist/web/main.css +1 -1
- package/dist/web/main.js +359 -401
- package/dist/wire.mjs +1 -2
- package/dist/workflow-kit.mjs +6 -10
- package/package.json +19 -32
- package/src/edge/Edge.test.ts +1 -1
- package/src/edge/Edge.ts +1 -1
- package/src/emit/GitScript.test.ts +1 -1
- package/src/flows/runtime.test.ts +1 -1
- package/src/replay/Replay.test.ts +21 -7
- package/src/steering/Blocks.test.ts +3 -1
- package/src/steering/Blocks.ts +14 -6
- package/src/steering/SteeringFormat.ts +5 -0
- package/src/steering/qa.test.ts +3 -3
- package/src/steering/qa.ts +1 -1
- package/src/steering/review.test.ts +26 -1
- package/src/step/LandStep.ts +1 -1
- package/src/twins/GitTiers.ts +1 -1
- package/src/wire/BeatPlain.ts +3 -3
- package/src/wire/Select.ts +1 -1
package/dist/wire.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { ACCESS_REFUSAL, BEAT_KINDS, DEMAND_BRIEFING, EXIT_CODES, EXIT_OK, EXIT_RUNTIME_ERROR, EXIT_SIGINT, EXIT_SIGTERM, EXIT_STRAYS_FOUND, EXIT_USAGE_ERROR, PIN_PATH, UNATTRIBUTED_MODEL, WIRE_SCHEMA, beatDocument, beatKindOf, demandOf, globMatches, judgeJson, landFields, landProseText, noopText, renderBeatJson, renderBeatPlain, renderFormat, renderLandJson, renderLandPlain, renderScriptJson, resolveSession, scriptFields, selectPath, stallDiagnosis, statusOf };
|
|
1
|
+
export { O as ACCESS_REFUSAL, _ as BEAT_KINDS, f as DEMAND_BRIEFING, x as EXIT_CODES, S as EXIT_OK, C as EXIT_RUNTIME_ERROR, w as EXIT_SIGINT, T as EXIT_SIGTERM, E as EXIT_STRAYS_FOUND, D as EXIT_USAGE_ERROR, k as PIN_PATH, A as UNATTRIBUTED_MODEL, j as WIRE_SCHEMA, m as beatDocument, v as beatKindOf, y as demandOf, t as globMatches, i as judgeJson, s as landFields, c as landProseText, l as noopText, h as renderBeatJson, p as renderBeatPlain, M as renderFormat, u as renderLandJson, d as renderLandPlain, a as renderScriptJson, n as resolveSession, o as scriptFields, r as selectPath, b as stallDiagnosis, g as statusOf } from "./chunks/wire-CaPJ9Tme.mjs";
|
package/dist/workflow-kit.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as head, F as changesSince, G as mark, P as changes, X as read, Z as refuse, d as hasThreadFor, et as scope, g as requireReplies, v as requireThreadsClosed } from "./chunks/flows-
|
|
2
|
-
import { C as reviewRisks, N as parseThreads, S as reviewNotes, T as unansweredNotes, n as stripCodeThreads, x as reviewDecisions } from "./chunks/steering-
|
|
3
|
-
|
|
1
|
+
import { B as head, F as changesSince, G as mark, P as changes, X as read, Z as refuse, d as hasThreadFor, et as scope, g as requireReplies, v as requireThreadsClosed } from "./chunks/flows-dnIE_qbe.mjs";
|
|
2
|
+
import { C as reviewRisks, N as parseThreads, S as reviewNotes, T as unansweredNotes, n as stripCodeThreads, x as reviewDecisions } from "./chunks/steering-CQzL-lX1.mjs";
|
|
3
|
+
// src/workflow-kit/health.ts
|
|
4
4
|
/** Hand a red suite that repeated attempts could not fix to a person, until `limit` rounds are spent. */
|
|
5
5
|
const escalation = async (count, steps) => {
|
|
6
6
|
if (count.rounds >= steps.limit) return steps.exhausted();
|
|
@@ -74,8 +74,7 @@ const healthy = async (fix, health) => {
|
|
|
74
74
|
const gate = (name, passes, blocked) => scope(name, async () => {
|
|
75
75
|
while (!await passes()) await blocked();
|
|
76
76
|
});
|
|
77
|
-
|
|
78
|
-
//#region src/workflow-kit/quality.ts
|
|
77
|
+
// src/workflow-kit/quality.ts
|
|
79
78
|
const lensOf = (entry) => {
|
|
80
79
|
const at = entry.indexOf("=");
|
|
81
80
|
return at < 0 ? {
|
|
@@ -106,8 +105,7 @@ const fixFindings = async (f) => {
|
|
|
106
105
|
await f.escalate();
|
|
107
106
|
return false;
|
|
108
107
|
};
|
|
109
|
-
|
|
110
|
-
//#region src/workflow-kit/scenarios.ts
|
|
108
|
+
// src/workflow-kit/scenarios.ts
|
|
111
109
|
const DOC_STRING = /^("""|```)/;
|
|
112
110
|
const THEN = /^Then\s/;
|
|
113
111
|
const CONTINUATION = /^(And|But|\*)\s/;
|
|
@@ -161,8 +159,7 @@ const removedAssertions = (changes) => changes.filter((c) => c.path.endsWith(".f
|
|
|
161
159
|
lines
|
|
162
160
|
};
|
|
163
161
|
}).filter((r) => r.lines.length > 0);
|
|
164
|
-
|
|
165
|
-
//#region src/workflow-kit/review.ts
|
|
162
|
+
// src/workflow-kit/review.ts
|
|
166
163
|
const collect = async (p, capture, round, requested) => {
|
|
167
164
|
await p.collecting(capture, round);
|
|
168
165
|
if (read(p.feedback) !== void 0) return "feedback";
|
|
@@ -426,5 +423,4 @@ const reviewToHandOff = async (base, p) => {
|
|
|
426
423
|
return;
|
|
427
424
|
}
|
|
428
425
|
};
|
|
429
|
-
//#endregion
|
|
430
426
|
export { assertionLines, escalation, fixFindings, gate, healthy, lensEntriesOf, lensOf, lensScopes, lensesOf, qualityLap, removedAssertions, review, reviewToHandOff, untilSettled };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmelab/gtd",
|
|
3
|
-
"version": "64.1.
|
|
3
|
+
"version": "64.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
|
|
6
6
|
"bin": {
|
|
@@ -83,33 +83,17 @@
|
|
|
83
83
|
"scripts": {
|
|
84
84
|
"prepare": "husky",
|
|
85
85
|
"dev": "node dev/run.mjs",
|
|
86
|
-
"
|
|
87
|
-
"postbuild": "node scripts/assert-no-test-doubles.mjs",
|
|
88
|
-
"watch": "tsdown --watch",
|
|
86
|
+
"watch": "vp pack --watch",
|
|
89
87
|
"bench:replay": "node scripts/bench-replay.mjs",
|
|
90
88
|
"test": "jiti scripts/test-states.main.ts",
|
|
91
|
-
"test:unit": "vitest run --project unit",
|
|
92
|
-
"test:workflows": "vitest run --project workflows",
|
|
93
89
|
"test:watch": "vitest",
|
|
94
90
|
"test:changed": "vitest run --project unit --project e2e-inmem --project workflows --changed HEAD",
|
|
95
|
-
"test:e2e": "
|
|
96
|
-
"
|
|
97
|
-
"lint": "oxlint .",
|
|
98
|
-
"lint:sh": "jiti scripts/generate-shell-corpus.ts --check && shellcheck -s sh tests/shell/corpus/*.sh",
|
|
99
|
-
"lint:boundaries": "depcruise src tests claude",
|
|
100
|
-
"format": "oxfmt --write .",
|
|
101
|
-
"format:check": "oxfmt --check .",
|
|
91
|
+
"test:e2e": "vp run test:e2e:inmem && vp run test:e2e:live",
|
|
92
|
+
"format": "vp fmt",
|
|
102
93
|
"test:mutation": "stryker run",
|
|
103
|
-
"test:e2e:inmem": "vitest run --project e2e-inmem",
|
|
104
|
-
"test:e2e:live": "vitest run --project e2e-live --no-file-parallelism",
|
|
105
|
-
"analyze": "fallow --summary --quiet",
|
|
106
|
-
"test:web": "vitest run --project storybook",
|
|
107
94
|
"storybook": "storybook dev --host 0.0.0.0",
|
|
108
95
|
"coverage": "vitest run --project unit --coverage"
|
|
109
96
|
},
|
|
110
|
-
"lint-staged": {
|
|
111
|
-
"*": "oxfmt --no-error-on-unmatched-pattern --write"
|
|
112
|
-
},
|
|
113
97
|
"devDependencies": {
|
|
114
98
|
"@commitlint/cli": "^21.2.3",
|
|
115
99
|
"@commitlint/config-conventional": "^21.2.3",
|
|
@@ -119,8 +103,8 @@
|
|
|
119
103
|
"@semantic-release/git": "^10.0.1",
|
|
120
104
|
"@semantic-release/github": "^12.0.8",
|
|
121
105
|
"@semantic-release/npm": "^12.0.0",
|
|
122
|
-
"@storybook/addon-vitest": "^10.6.
|
|
123
|
-
"@storybook/react-vite": "^10.6.
|
|
106
|
+
"@storybook/addon-vitest": "^10.6.5",
|
|
107
|
+
"@storybook/react-vite": "^10.6.5",
|
|
124
108
|
"@stryker-mutator/core": "^9.6.1",
|
|
125
109
|
"@stryker-mutator/typescript-checker": "^9.6.1",
|
|
126
110
|
"@stryker-mutator/vitest-runner": "^9.6.1",
|
|
@@ -134,29 +118,27 @@
|
|
|
134
118
|
"@types/react": "^19.2.7",
|
|
135
119
|
"@types/react-dom": "^19.2.3",
|
|
136
120
|
"@vitejs/plugin-react": "^5.2.0",
|
|
137
|
-
"@vitest/browser": "
|
|
138
|
-
"@vitest/browser-playwright": "
|
|
139
|
-
"@vitest/coverage-v8": "
|
|
121
|
+
"@vitest/browser": "5.0.5",
|
|
122
|
+
"@vitest/browser-playwright": "5.0.5",
|
|
123
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
140
124
|
"dependency-cruiser": "^18.3.1",
|
|
141
125
|
"fallow": "3.25.0",
|
|
142
126
|
"fast-check": "^4.8.0",
|
|
143
127
|
"husky": "^9.1.7",
|
|
144
128
|
"jiti": "^2.7.0",
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"oxlint": "^1.74.0",
|
|
129
|
+
"oxfmt": "0.59.0",
|
|
130
|
+
"oxlint": "1.74.0",
|
|
148
131
|
"playwright": "^1.62.1",
|
|
149
132
|
"prettier": "^3.8.1",
|
|
150
133
|
"quickpickle": "1.11.2",
|
|
151
134
|
"react": "^19.2.0",
|
|
152
135
|
"react-dom": "^19.2.0",
|
|
153
136
|
"semantic-release": "^25.0.5",
|
|
154
|
-
"storybook": "^10.6.
|
|
137
|
+
"storybook": "^10.6.5",
|
|
155
138
|
"tailwind-merge": "^3.7.0",
|
|
156
139
|
"tailwindcss": "^4.3.3",
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"vitest": "^4.1.12",
|
|
140
|
+
"vite-plus": "1.4.0",
|
|
141
|
+
"vitest": "5.0.5",
|
|
160
142
|
"yaml": "^2.8.2",
|
|
161
143
|
"zustand": "^5.0.15"
|
|
162
144
|
},
|
|
@@ -190,5 +172,10 @@
|
|
|
190
172
|
"rolldown": "^1.1.5",
|
|
191
173
|
"vscode-languageserver": "^10.1.0",
|
|
192
174
|
"vscode-languageserver-textdocument": "^1.0.13"
|
|
175
|
+
},
|
|
176
|
+
"overrides": {
|
|
177
|
+
"storybook": {
|
|
178
|
+
"vite-plus": "$vite-plus"
|
|
179
|
+
}
|
|
193
180
|
}
|
|
194
181
|
}
|
package/src/edge/Edge.test.ts
CHANGED
|
@@ -757,7 +757,7 @@ describe("restAt", () => {
|
|
|
757
757
|
`--model=${model}`,
|
|
758
758
|
)
|
|
759
759
|
expect(exitCode).toBe(0)
|
|
760
|
-
|
|
760
|
+
applied(repo, (JSON.parse(stdout) as { readonly script: string }).script)
|
|
761
761
|
await takePinBeat(repo, "add")
|
|
762
762
|
}
|
|
763
763
|
const { cost } = await provide(currentRest, repo)
|
package/src/edge/Edge.ts
CHANGED
|
@@ -238,7 +238,7 @@ const historyFormatFault = (history: History, location: EpisodeLocation): string
|
|
|
238
238
|
const runOf = (
|
|
239
239
|
history: History,
|
|
240
240
|
location: EpisodeLocation,
|
|
241
|
-
closingHash
|
|
241
|
+
closingHash?: string,
|
|
242
242
|
): Effect.Effect<ProcessRun, Error> => {
|
|
243
243
|
const fault = historyFormatFault(history, location)
|
|
244
244
|
return fault === undefined
|
|
@@ -234,7 +234,7 @@ describe("shellQuote", () => {
|
|
|
234
234
|
})
|
|
235
235
|
|
|
236
236
|
// 500 samples through 500 bash spawns starved the 30s test timeout whenever
|
|
237
|
-
// the suite ran under
|
|
237
|
+
// the suite ran under `npm test`'s parallel task load. The property is unchanged;
|
|
238
238
|
// all samples now round-trip through a SINGLE bash, NUL-delimited on the way
|
|
239
239
|
// back out (NUL is filtered from the inputs, so it can't collide).
|
|
240
240
|
it("round-trips arbitrary strings — including newlines and non-ASCII — through a real bash printf", () => {
|
|
@@ -146,7 +146,7 @@ describe("kept()", () => {
|
|
|
146
146
|
})
|
|
147
147
|
|
|
148
148
|
/** A context that records every step request and resolves it with `reply`. */
|
|
149
|
-
const recording = (reply
|
|
149
|
+
const recording = (reply?: unknown) => {
|
|
150
150
|
const requests: StepRequest[] = []
|
|
151
151
|
installContext({
|
|
152
152
|
step: async (request: StepRequest) => {
|
|
@@ -370,7 +370,9 @@ describe("replay", () => {
|
|
|
370
370
|
})
|
|
371
371
|
|
|
372
372
|
it("is reported when history continues past the end of the flow", async () => {
|
|
373
|
-
const short = async () =>
|
|
373
|
+
const short = async () => {
|
|
374
|
+
await human("only")
|
|
375
|
+
}
|
|
374
376
|
const h = new History().land("human", "only", 1, "only").land("human", "only", 2, "only")
|
|
375
377
|
expect((await replayOf(short, h)).kind).toBe("divergence")
|
|
376
378
|
})
|
|
@@ -606,7 +608,9 @@ describe("replay", () => {
|
|
|
606
608
|
})
|
|
607
609
|
|
|
608
610
|
it("ends the episode on return and on restart", async () => {
|
|
609
|
-
const returning = async () =>
|
|
611
|
+
const returning = async () => {
|
|
612
|
+
await human("only")
|
|
613
|
+
}
|
|
610
614
|
const h = new History().land("human", "only", 1, "only")
|
|
611
615
|
expect(await replayOf(returning, h)).toMatchObject({ kind: "ended", via: "return" })
|
|
612
616
|
|
|
@@ -1337,7 +1341,9 @@ describe("replay: a repin's anchor", () => {
|
|
|
1337
1341
|
})
|
|
1338
1342
|
|
|
1339
1343
|
it("diverges when the flow ends where it names a step", async () => {
|
|
1340
|
-
const outcome = await replayOf(async () =>
|
|
1344
|
+
const outcome = await replayOf(async () => {
|
|
1345
|
+
await human("idle")
|
|
1346
|
+
}, anchoredHistory())
|
|
1341
1347
|
expect(outcome).toMatchObject({ kind: "divergence", at: hashOf(3), reached: undefined })
|
|
1342
1348
|
})
|
|
1343
1349
|
|
|
@@ -1346,7 +1352,9 @@ describe("replay: a repin's anchor", () => {
|
|
|
1346
1352
|
.land("human", "idle", 1, "build", { "a.txt": "a" })
|
|
1347
1353
|
.land("agent", "build", 1, "old")
|
|
1348
1354
|
.raw(anchorTo("gtd:unpin", 1))
|
|
1349
|
-
const outcome = await replayOf(async () =>
|
|
1355
|
+
const outcome = await replayOf(async () => {
|
|
1356
|
+
await human("idle")
|
|
1357
|
+
}, h)
|
|
1350
1358
|
expect(outcome.kind).toBe("ended")
|
|
1351
1359
|
})
|
|
1352
1360
|
|
|
@@ -1355,13 +1363,17 @@ describe("replay: a repin's anchor", () => {
|
|
|
1355
1363
|
.land("human", "idle", 1, "build", { "a.txt": "a" })
|
|
1356
1364
|
.land("agent", "build", 1, "old")
|
|
1357
1365
|
.raw(`gtd(check): pin → unpin\n\nGtd-Pin: add\nGtd-Anchor: ${hashOf(1)}\nGtd-Format: 3`)
|
|
1358
|
-
const outcome = await replayOf(async () =>
|
|
1366
|
+
const outcome = await replayOf(async () => {
|
|
1367
|
+
await human("idle")
|
|
1368
|
+
}, h)
|
|
1359
1369
|
expect(outcome.kind).toBe("ended")
|
|
1360
1370
|
})
|
|
1361
1371
|
|
|
1362
1372
|
it("reads a format 4 anchor naming unpin as a step", async () => {
|
|
1363
1373
|
const outcome = await replayOf(
|
|
1364
|
-
async () =>
|
|
1374
|
+
async () => {
|
|
1375
|
+
await human("idle")
|
|
1376
|
+
},
|
|
1365
1377
|
new History()
|
|
1366
1378
|
.land("human", "idle", 1, "build", { "a.txt": "a" })
|
|
1367
1379
|
.land("agent", "build", 1, "old")
|
|
@@ -1372,7 +1384,9 @@ describe("replay: a repin's anchor", () => {
|
|
|
1372
1384
|
|
|
1373
1385
|
it("fails when it names no commit replay reads", async () => {
|
|
1374
1386
|
const h = anchoredHistory().raw(anchorTo("build", 2))
|
|
1375
|
-
const outcome = await replayOf(async () =>
|
|
1387
|
+
const outcome = await replayOf(async () => {
|
|
1388
|
+
await human("idle")
|
|
1389
|
+
}, h)
|
|
1376
1390
|
expect(outcome.kind === "failed" && outcome.message).toContain(
|
|
1377
1391
|
"which is no commit replay reads",
|
|
1378
1392
|
)
|
|
@@ -35,7 +35,9 @@ describe("blockRunInline — spec-feedback regression: a nested list item's own
|
|
|
35
35
|
const nodes = blockNodesOf(content, tree)
|
|
36
36
|
const topItem = nodes[0]?.block?.items?.[0]
|
|
37
37
|
expect(topItem?.inline).toEqual([{ kind: "text", value: "outer" }])
|
|
38
|
-
expect(topItem?.items).toEqual([
|
|
38
|
+
expect(topItem?.items).toEqual([
|
|
39
|
+
{ text: "inner", inline: [{ kind: "text", value: "inner" }], line: 1 },
|
|
40
|
+
])
|
|
39
41
|
})
|
|
40
42
|
})
|
|
41
43
|
|
package/src/steering/Blocks.ts
CHANGED
|
@@ -128,21 +128,28 @@ const nodeInline = (
|
|
|
128
128
|
return title.length > 0 ? [{ kind: "text", value: title }] : []
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
type NoteAt = ReturnType<typeof noteLookup>
|
|
132
|
+
|
|
131
133
|
/** One list item as a `BlockListItem`, recursing into a nested `list` child (there is at most one, CommonMark's own shape) as its own `items`. */
|
|
132
|
-
const blockListItemOf = (content: string, item: ListItem): BlockListItem => {
|
|
134
|
+
const blockListItemOf = (content: string, item: ListItem, noteAt: NoteAt): BlockListItem => {
|
|
133
135
|
const nested = item.children.filter((c): c is List => c.type === "list")
|
|
134
|
-
const items = nested.flatMap((list) => blockListItemsOf(content, list.children))
|
|
136
|
+
const items = nested.flatMap((list) => blockListItemsOf(content, list.children, noteAt))
|
|
137
|
+
const line = item.position === undefined ? undefined : toLspPosition(item.position.start).line
|
|
135
138
|
return {
|
|
136
139
|
text: listItemText(content, item),
|
|
137
140
|
inline: listItemInline(content, item),
|
|
138
141
|
...(item.checked === true || item.checked === false ? { checked: item.checked } : {}),
|
|
139
142
|
...(items.length > 0 ? { items } : {}),
|
|
143
|
+
...(line === undefined ? {} : { line, ...noteAt(line) }),
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
/** Every item of a `list` node's own `children`, as `BlockListItem`s, in document order. */
|
|
144
|
-
const blockListItemsOf = (
|
|
145
|
-
|
|
148
|
+
const blockListItemsOf = (
|
|
149
|
+
content: string,
|
|
150
|
+
items: readonly ListItem[],
|
|
151
|
+
noteAt: NoteAt,
|
|
152
|
+
): readonly BlockListItem[] => items.map((item) => blockListItemOf(content, item, noteAt))
|
|
146
153
|
|
|
147
154
|
/** The `title` an empty fenced code block (a `` ``` ``/`` ``` `` pair with nothing between them) falls back to — its real body is `""`, and every node still carries a non-empty title. */
|
|
148
155
|
const EMPTY_CODE_BLOCK_TITLE = "(empty code block)"
|
|
@@ -184,6 +191,7 @@ const blockTitle = (content: string, node: RootContent, options?: BlockWalkOptio
|
|
|
184
191
|
const blockOf = (
|
|
185
192
|
content: string,
|
|
186
193
|
node: RootContent,
|
|
194
|
+
noteAt: NoteAt,
|
|
187
195
|
options?: BlockWalkOptions,
|
|
188
196
|
): SteeringViewNode["block"] | undefined => {
|
|
189
197
|
switch (node.type) {
|
|
@@ -197,7 +205,7 @@ const blockOf = (
|
|
|
197
205
|
return {
|
|
198
206
|
kind: "list",
|
|
199
207
|
ordered: node.ordered === true,
|
|
200
|
-
items: blockListItemsOf(content, visibleListItems(node, options)),
|
|
208
|
+
items: blockListItemsOf(content, visibleListItems(node, options), noteAt),
|
|
201
209
|
}
|
|
202
210
|
case "code":
|
|
203
211
|
return {
|
|
@@ -285,7 +293,7 @@ export const blockNodesOfRun = (
|
|
|
285
293
|
const noteAt = noteLookup(content, options?.footnotes)
|
|
286
294
|
return visibleNodes(content, nodes, options).map((node) => {
|
|
287
295
|
const startLine = toLspPosition(node.position!.start).line
|
|
288
|
-
const block = blockOf(content, node, options)
|
|
296
|
+
const block = blockOf(content, node, noteAt, options)
|
|
289
297
|
return {
|
|
290
298
|
title: blockTitle(content, node, options),
|
|
291
299
|
anchor: { kind: "paragraph" as const, line: startLine },
|
|
@@ -89,6 +89,11 @@ export interface BlockListItem {
|
|
|
89
89
|
readonly items?: readonly BlockListItem[]
|
|
90
90
|
/** `text`'s own inline structure — the shared renderer walks this instead of `text` when present. Populated at every nesting depth. */
|
|
91
91
|
readonly inline?: readonly InlineNode[]
|
|
92
|
+
/** The item's own 0-based source line: a `paragraph` anchor at it notes this item alone. */
|
|
93
|
+
readonly line?: number
|
|
94
|
+
/** The note or thread whose marker sits on `line`, as on a view node. */
|
|
95
|
+
readonly note?: string
|
|
96
|
+
readonly thread?: SteeringViewThread
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
export interface SteeringViewThread {
|
package/src/steering/qa.test.ts
CHANGED
|
@@ -1172,7 +1172,7 @@ describe("qa.view — block nodes (package 02, T1/T2)", () => {
|
|
|
1172
1172
|
expect(topItem?.text).not.toContain("Nested")
|
|
1173
1173
|
expect(topItem?.text).not.toContain("-")
|
|
1174
1174
|
expect(topItem?.items).toEqual([
|
|
1175
|
-
{ text: "Nested", inline: [{ kind: "text", value: "Nested" }] },
|
|
1175
|
+
{ text: "Nested", inline: [{ kind: "text", value: "Nested" }], line: 2 },
|
|
1176
1176
|
])
|
|
1177
1177
|
})
|
|
1178
1178
|
|
|
@@ -1180,8 +1180,8 @@ describe("qa.view — block nodes (package 02, T1/T2)", () => {
|
|
|
1180
1180
|
const content = ["- [ ] Not done", "- [x] Done", ""].join("\n")
|
|
1181
1181
|
const view = qa.view(content)
|
|
1182
1182
|
expect(view.nodes[0]?.block?.items).toEqual([
|
|
1183
|
-
{ text: "Not done", checked: false, inline: [{ kind: "text", value: "Not done" }] },
|
|
1184
|
-
{ text: "Done", checked: true, inline: [{ kind: "text", value: "Done" }] },
|
|
1183
|
+
{ text: "Not done", checked: false, inline: [{ kind: "text", value: "Not done" }], line: 0 },
|
|
1184
|
+
{ text: "Done", checked: true, inline: [{ kind: "text", value: "Done" }], line: 1 },
|
|
1185
1185
|
])
|
|
1186
1186
|
})
|
|
1187
1187
|
|
package/src/steering/qa.ts
CHANGED
|
@@ -822,7 +822,7 @@ const replaceOptionTextEdit = (
|
|
|
822
822
|
* Collapses a typed answer to the single-line shape a list-item label must
|
|
823
823
|
* be. A raw textarea value's interior newlines would break `.gtd/`'s oxfmt
|
|
824
824
|
* fixed point the moment they are spliced onto a `- [x] ` line, and every
|
|
825
|
-
* steering file — server-written ones included — is covered by `
|
|
825
|
+
* steering file — server-written ones included — is covered by the `check` task.
|
|
826
826
|
*/
|
|
827
827
|
/**
|
|
828
828
|
* Empty normalizes to `FREE_TEXT_PLACEHOLDER`, never `""`: an empty label
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { FOOTNOTE_ACTION_TITLE, steeringFormatFor, viewOf } from "./internal.js"
|
|
2
|
+
import { applySteeringEdits, FOOTNOTE_ACTION_TITLE, steeringFormatFor, viewOf } from "./internal.js"
|
|
3
3
|
import { parseReviewDoc, unansweredNotes } from "./review.js"
|
|
4
4
|
import { getParseCount } from "./internal.js"
|
|
5
5
|
|
|
@@ -1370,6 +1370,31 @@ describe("review.annotate", () => {
|
|
|
1370
1370
|
expect(result.ok).toBe(true)
|
|
1371
1371
|
})
|
|
1372
1372
|
|
|
1373
|
+
it("a paragraph anchor on one item of a chunk's list notes that item alone", () => {
|
|
1374
|
+
const summary = [
|
|
1375
|
+
"# Review: abc1234",
|
|
1376
|
+
"<!-- base: abc1234def5678901234567890123456789abcd -->",
|
|
1377
|
+
"",
|
|
1378
|
+
"## Quality lap",
|
|
1379
|
+
"",
|
|
1380
|
+
"Fixed automatically:",
|
|
1381
|
+
"",
|
|
1382
|
+
"- Rounds wrong (lens correctness)",
|
|
1383
|
+
"- Missing test (lens tests)",
|
|
1384
|
+
"",
|
|
1385
|
+
].join("\n")
|
|
1386
|
+
const before = review.view(summary).nodes[0]!.body![1]!.block!.items!
|
|
1387
|
+
expect(before.map((item) => item.line)).toEqual([7, 8])
|
|
1388
|
+
const result = review.annotate(summary, { kind: "paragraph", line: 8 }, "reopen it")
|
|
1389
|
+
if (!result.ok) throw new Error(result.reason)
|
|
1390
|
+
const after = applySteeringEdits(summary, result.edits)
|
|
1391
|
+
expect(review.validate(after)).toEqual([])
|
|
1392
|
+
const items = review.view(after).nodes[0]!.body![1]!.block!.items!
|
|
1393
|
+
expect(items[0]).not.toHaveProperty("thread")
|
|
1394
|
+
expect(items[0]).not.toHaveProperty("note")
|
|
1395
|
+
expect(items[1]!.thread?.entries).toEqual([{ author: "me", text: "reopen it" }])
|
|
1396
|
+
})
|
|
1397
|
+
|
|
1373
1398
|
it("accepts a paragraph anchor in a prose-only document", () => {
|
|
1374
1399
|
const result = review.annotate(
|
|
1375
1400
|
"Just some prose.\n",
|
package/src/step/LandStep.ts
CHANGED
|
@@ -16,7 +16,7 @@ export type GitWrite =
|
|
|
16
16
|
/** A landed step's trailing report, rendered by `GitScript.ts` into a `printf` line. */
|
|
17
17
|
export type Outcome =
|
|
18
18
|
| { readonly kind: "transition"; readonly from: StateName; readonly to: StateName }
|
|
19
|
-
| { readonly kind: "commit"; readonly subject:
|
|
19
|
+
| { readonly kind: "commit"; readonly subject: string }
|
|
20
20
|
| { readonly kind: "note"; readonly text: string }
|
|
21
21
|
|
|
22
22
|
/**
|
package/src/twins/GitTiers.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { tmpdir } from "node:os"
|
|
|
14
14
|
import { execFileSync, execSync, spawnSync } from "node:child_process"
|
|
15
15
|
// Imported directly (not just from *.test.ts) — a leak of this module into
|
|
16
16
|
// the shipped CLI bundle would try to inline `vitest` and fail the build
|
|
17
|
-
// loudly (`deps.alwaysBundle` in `
|
|
17
|
+
// loudly (`deps.alwaysBundle` in `vite.config.ts`), a guard alongside the
|
|
18
18
|
// `TEST_DOUBLE_SENTINEL` check.
|
|
19
19
|
import { afterEach, beforeEach, describe, expect, it } from "vitest"
|
|
20
20
|
import { Effect, Exit, Option } from "effect"
|
package/src/wire/BeatPlain.ts
CHANGED
|
@@ -32,9 +32,9 @@ const nextStatusLine = (next: BeatDocument["next"]): string =>
|
|
|
32
32
|
|
|
33
33
|
/** Builds `{[key]: value}` for each entry whose value isn't `undefined` — the shared "omit absent optional fields" shape the header uses. */
|
|
34
34
|
const definedFields = (
|
|
35
|
-
entries: readonly (readonly [string,
|
|
36
|
-
): Record<string,
|
|
37
|
-
const result: Record<string,
|
|
35
|
+
entries: readonly (readonly [string, string | undefined])[],
|
|
36
|
+
): Record<string, string> => {
|
|
37
|
+
const result: Record<string, string> = {}
|
|
38
38
|
for (const [key, value] of entries) if (value !== undefined) result[key] = value
|
|
39
39
|
return result
|
|
40
40
|
}
|
package/src/wire/Select.ts
CHANGED
|
@@ -18,7 +18,7 @@ const toSelection = (value: unknown): Selection => {
|
|
|
18
18
|
if (typeof value === "object") {
|
|
19
19
|
return { kind: "value", text: JSON.stringify(value) }
|
|
20
20
|
}
|
|
21
|
-
return { kind: "value", text: String(value) }
|
|
21
|
+
return { kind: "value", text: String(value as string | number | boolean | bigint | undefined) }
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** A segment that resolves to no key at all (missing from an object, out-of-range or non-numeric against an array) — distinct from a present key holding `undefined`. */
|