@pmelab/gtd 55.0.0 → 57.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 +72 -80
- package/dist/chunks/{reserved-DHafcW7t.mjs → reserved-X2RkNHin.mjs} +110 -318
- package/dist/chunks/{review-BJGKwlYv.mjs → review-v7sh5GF4.mjs} +30 -125
- package/dist/chunks/{steering-CLhu8C7q.mjs → steering-CCNvngSH.mjs} +256 -65
- package/dist/chunks/{tail-B9Hw2AFB.mjs → tail-S04KEewi.mjs} +144 -56
- 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 +63 -175
- 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 +603 -727
- 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 +1 -7
- package/src/workflows/feature/criteria.test.ts +1 -41
- package/src/workflows/feature/criteria.ts +2 -14
- package/src/workflows/feature/fast-path.feature +0 -1
- package/src/workflows/feature/feature.test.ts +1 -1
- package/src/workflows/feature/feature.ts +16 -25
- package/src/workflows/feature/flag-mode.feature +1 -1
- package/src/workflows/feature/lens-models.feature +0 -1
- package/src/workflows/feature/lens-review.feature +323 -172
- package/src/workflows/feature/naive-test.feature +258 -0
- package/src/workflows/feature/one-step-land.feature +0 -1
- package/src/workflows/feature/refactor-absorbs.feature +1 -8
- package/src/workflows/feature/refactor-first.feature +2 -11
- package/src/workflows/feature/refactor-rebuild.feature +4 -25
- package/src/workflows/feature/refactor-review.feature +33 -118
- package/src/workflows/feature/refactor.test.ts +0 -2
- package/src/workflows/feature/refactor.ts +0 -1
- package/src/workflows/feature/review-gate.feature +7 -24
- package/src/workflows/feature/reviewer-walkthrough.feature +16 -104
- package/src/workflows/feature/seam-rejection.feature +0 -1
- package/src/workflows/feature/seam.feature +0 -1
- package/src/workflows/feature/seam.test.ts +1 -2
- package/src/workflows/feature/seam.ts +56 -10
- package/src/workflows/feature/standards.feature +75 -35
- package/src/workflows/fix/fix-lens-origin.feature +20 -7
- package/src/workflows/fix/fix-quality.feature +102 -23
- package/src/workflows/review/review-door.feature +62 -59
- 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/options.ts +9 -1
- package/src/workflows/shared/prose.ts +0 -6
- package/src/workflows/shared/review.test.ts +27 -26
- package/src/workflows/shared/review.ts +29 -196
- package/src/workflows/shared/skills.test.ts +0 -4
- package/src/workflows/shared/skills.ts +0 -5
- package/src/workflows/shared/steps.ts +1 -1
- package/src/workflows/shared/tail.ts +18 -9
- package/src/workflows/shared/text.test.ts +125 -329
- package/src/workflows/shared/text.ts +173 -471
- package/src/workflows/shared/vars.ts +2 -3
- package/src/workflows/feature/hands-on-skip.feature +0 -316
- package/src/workflows/feature/prototype-rounds.feature +0 -156
- package/src/workflows/feature/prototype.feature +0 -428
- package/src/workflows/feature/prototype.test.ts +0 -43
- package/src/workflows/feature/prototype.ts +0 -198
- 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
|
@@ -5,7 +5,6 @@ import { join } from "node:path"
|
|
|
5
5
|
import { afterEach, describe, expect, it } from "vitest"
|
|
6
6
|
import {
|
|
7
7
|
designImpactPrompt,
|
|
8
|
-
prototypeFixingPrompt,
|
|
9
8
|
buildReviewTriageMessage,
|
|
10
9
|
buildReviewReviewingPrompt,
|
|
11
10
|
agentWithSkills,
|
|
@@ -21,8 +20,7 @@ import {
|
|
|
21
20
|
buildReviewCollectingPrompt,
|
|
22
21
|
designGateAnswerMessage,
|
|
23
22
|
designTriagePrompt,
|
|
24
|
-
|
|
25
|
-
prototypeTestingPrompt,
|
|
23
|
+
naiveTryMessage,
|
|
26
24
|
implementBuildingPrompt,
|
|
27
25
|
implementFixPrompt,
|
|
28
26
|
naivePassPrompt,
|
|
@@ -33,14 +31,16 @@ import {
|
|
|
33
31
|
seamSteerMessage,
|
|
34
32
|
summaryPrompt,
|
|
35
33
|
withSkills,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
withWalkthrough,
|
|
40
|
-
rankedReview,
|
|
41
|
-
unranked,
|
|
34
|
+
findingGateFile,
|
|
35
|
+
nitFixBlock,
|
|
36
|
+
withSummary,
|
|
42
37
|
} from "./text.js"
|
|
43
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
findingEntries,
|
|
40
|
+
reviewDecisions,
|
|
41
|
+
steeringFormatFor,
|
|
42
|
+
unansweredNotes,
|
|
43
|
+
} from "../../steering/index.js"
|
|
44
44
|
import { captureStep, renderText } from "./text.fixture.js"
|
|
45
45
|
|
|
46
46
|
describe("withSkills", () => {
|
|
@@ -174,37 +174,6 @@ describe("the risk tier prompt and gate line", () => {
|
|
|
174
174
|
})
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
-
describe("the (no hands-on) tag", () => {
|
|
178
|
-
it("is explained at triage and at the design gate", () => {
|
|
179
|
-
expect(renderText(() => designTriagePrompt("base"))).toContain(
|
|
180
|
-
"`- AC-<n> (no hands-on): <criterion>`",
|
|
181
|
-
)
|
|
182
|
-
expect(renderText(() => designGateAnswerMessage())).toContain(
|
|
183
|
-
"add or remove `(no hands-on)` between an `AC-<n>` and its colon",
|
|
184
|
-
)
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it("names the tagged criteria the testing turn skips, and only when there are any", () => {
|
|
188
|
-
expect(renderText(() => prototypeTestingPrompt(["AC-2", "AC-3"], false))).toContain(
|
|
189
|
-
"- Skip AC-2, AC-3: no person can observe them",
|
|
190
|
-
)
|
|
191
|
-
expect(renderText(() => prototypeTestingPrompt([], false))).not.toContain("Skip")
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it("is kept when the stated criteria are recorded", () => {
|
|
195
|
-
const script = normaliseCriteriaScript({
|
|
196
|
-
path: "TODO.md",
|
|
197
|
-
commit: "abc",
|
|
198
|
-
criteria: [
|
|
199
|
-
{ id: "AC-1", text: "a" },
|
|
200
|
-
{ id: "AC-2", text: "b", noHandsOn: true },
|
|
201
|
-
],
|
|
202
|
-
})
|
|
203
|
-
expect(script).toContain("'- AC-1: a'")
|
|
204
|
-
expect(script).toContain("'- AC-2 (no hands-on): b'")
|
|
205
|
-
})
|
|
206
|
-
})
|
|
207
|
-
|
|
208
177
|
describe("the shared open-question instruction", () => {
|
|
209
178
|
it("leaves the option count to the agent, floored at two real options", () => {
|
|
210
179
|
const prompt = renderText(() => designTriagePrompt("base"))
|
|
@@ -867,17 +836,11 @@ describe("Impact in prompts", () => {
|
|
|
867
836
|
it.each([
|
|
868
837
|
["naive", () => naivePassPrompt()],
|
|
869
838
|
["implement", () => implementBuildingPrompt(undefined)],
|
|
870
|
-
["prototype fixing", () => prototypeFixingPrompt()],
|
|
871
839
|
])("[AC-5] [AC-7] the %s prompt gives Impact as hints, not allowed files", (_n, prompt) => {
|
|
872
840
|
const p = renderText(prompt).replace(/\s+/g, " ")
|
|
873
841
|
expect(p).toContain("`## Impact`")
|
|
874
842
|
expect(p).toContain("not a list of allowed files")
|
|
875
843
|
})
|
|
876
|
-
|
|
877
|
-
it("the prototype testing prompt, which changes no code, carries no Impact hints", () => {
|
|
878
|
-
const p = renderText(() => prototypeTestingPrompt([], false))
|
|
879
|
-
expect(p).not.toContain("not a list of allowed files")
|
|
880
|
-
})
|
|
881
844
|
})
|
|
882
845
|
|
|
883
846
|
describe("the seam phase's texts", () => {
|
|
@@ -898,6 +861,27 @@ describe("the seam phase's texts", () => {
|
|
|
898
861
|
expect(prompt).toContain("solved the task the wrong way")
|
|
899
862
|
})
|
|
900
863
|
|
|
864
|
+
it("hands a pass a person sent back its tip, their notes and their edits", () => {
|
|
865
|
+
const prompt = renderText(() =>
|
|
866
|
+
naivePassPrompt({ tested: "tip123", notes: "the button does nothing", edits: "edit456" }),
|
|
867
|
+
)
|
|
868
|
+
expect(prompt).toContain("A person tried your previous attempt (`tip123`) by hand")
|
|
869
|
+
expect(prompt).toContain(
|
|
870
|
+
"git diff --binary --no-color --no-ext-diff --no-textconv tip123^ tip123",
|
|
871
|
+
)
|
|
872
|
+
expect(prompt).toContain("> the button does nothing")
|
|
873
|
+
expect(prompt).toContain("git show edit456 -- . ':(exclude).gtd'")
|
|
874
|
+
const bare = renderText(() => naivePassPrompt({ tested: "tip123" }))
|
|
875
|
+
expect(bare).not.toContain("Their notes")
|
|
876
|
+
expect(bare).not.toContain("git show")
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
it("offers send-back and sign-off at the naive test, naming what a sign-off reaches", () => {
|
|
880
|
+
expect(renderText(() => naiveTryMessage(true))).toContain("the seam judge rules on its shape")
|
|
881
|
+
expect(renderText(() => naiveTryMessage(false))).toContain("lands as the implementation")
|
|
882
|
+
expect(renderText(() => naiveTryMessage(true))).toContain("`.gtd/NAIVE-TEST.md`")
|
|
883
|
+
})
|
|
884
|
+
|
|
901
885
|
it("asks for a breadth-first pass tested through existing code, green before it ends", () => {
|
|
902
886
|
const prompt = renderText(() => naivePassPrompt())
|
|
903
887
|
expect(prompt).toContain("breadth-first")
|
|
@@ -908,7 +892,6 @@ describe("the seam phase's texts", () => {
|
|
|
908
892
|
it.each([
|
|
909
893
|
["naive", () => naivePassPrompt()],
|
|
910
894
|
["building", () => implementBuildingPrompt("abc123")],
|
|
911
|
-
["prototype fixing", () => prototypeFixingPrompt()],
|
|
912
895
|
])(
|
|
913
896
|
"the %s prompt asks for a test per behaviour, never a criterion ID in its name",
|
|
914
897
|
(_n, prompt) => {
|
|
@@ -1272,138 +1255,8 @@ describe("buildReviewCollectingPrompt answer boxes", () => {
|
|
|
1272
1255
|
})
|
|
1273
1256
|
})
|
|
1274
1257
|
|
|
1275
|
-
describe("
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1278
|
-
describe("standards", () => {
|
|
1279
|
-
const conflict = { text: "Calculators are functions.", olds: ["Calculators are classes."] }
|
|
1280
|
-
const withConflict = composedReview(BASE, {
|
|
1281
|
-
notes: [],
|
|
1282
|
-
fixed: [],
|
|
1283
|
-
noise: [],
|
|
1284
|
-
standards: { added: [], undecided: [conflict] },
|
|
1285
|
-
})
|
|
1286
|
-
|
|
1287
|
-
it("asks about each undecided standard in a note that blocks sign-off", () => {
|
|
1288
|
-
expect(withConflict).toMatch(/^## Findings\n\n### Standard conflict [0-9a-f]{8}$/m)
|
|
1289
|
-
expect(withConflict).toContain("- [ ] keep the new one (drop the old)")
|
|
1290
|
-
expect(unansweredNotes(withConflict)).toHaveLength(1)
|
|
1291
|
-
})
|
|
1292
|
-
|
|
1293
|
-
it("reads its boxes by position, like a note's", () => {
|
|
1294
|
-
const ticked = withConflict.replace("- [ ] keep the old one", "- [x] keep the old one")
|
|
1295
|
-
expect(unansweredNotes(ticked)).toEqual([])
|
|
1296
|
-
expect(reviewDecisions(withConflict, ticked)).toEqual([
|
|
1297
|
-
expect.objectContaining({ choice: "wontfix" }),
|
|
1298
|
-
])
|
|
1299
|
-
})
|
|
1300
|
-
|
|
1301
|
-
it("lists the standards added", () => {
|
|
1302
|
-
const doc = composedReview(BASE, {
|
|
1303
|
-
notes: [],
|
|
1304
|
-
fixed: [],
|
|
1305
|
-
noise: [],
|
|
1306
|
-
standards: { added: ["Prefer early returns."], undecided: [] },
|
|
1307
|
-
})
|
|
1308
|
-
expect(doc).toContain("## Standards added")
|
|
1309
|
-
expect(doc).toContain("- Prefer early returns.")
|
|
1310
|
-
})
|
|
1311
|
-
})
|
|
1312
|
-
|
|
1313
|
-
it("is a valid review with unique chunk titles, so every note matches its own chunk", () => {
|
|
1314
|
-
const doc = composedReview(BASE, {
|
|
1315
|
-
notes: [
|
|
1316
|
-
{ title: "same", body: "one" },
|
|
1317
|
-
{ title: "same", body: "two", unresolved: true },
|
|
1318
|
-
],
|
|
1319
|
-
fixed: [{ title: "f", commit: "fedcba9876543210" }],
|
|
1320
|
-
noise: [{ title: "n", p: 0.8 }],
|
|
1321
|
-
})
|
|
1322
|
-
expect(steeringFormatFor("review")!.validate(doc)).toEqual([])
|
|
1323
|
-
expect(doc).toContain("# Review: 0123456\n\n<!-- base: " + BASE + " -->")
|
|
1324
|
-
expect(doc).toContain("## Findings\n\n### same\n\none")
|
|
1325
|
-
expect(doc).toContain("### same (2)\n\n_The fixer left this unresolved._\n\ntwo")
|
|
1326
|
-
expect(doc).toContain("## Fixed automatically\n\n- f (fedcba9)")
|
|
1327
|
-
expect(doc).toContain("## Dropped as noise\n\n- n (p=0.8)")
|
|
1328
|
-
})
|
|
1329
|
-
|
|
1330
|
-
it("holds one chunk when the lap left nothing", () => {
|
|
1331
|
-
const doc = composedReview(BASE, { notes: [], fixed: [], noise: [] })
|
|
1332
|
-
expect(steeringFormatFor("review")!.validate(doc)).toEqual([])
|
|
1333
|
-
expect(doc).toContain("## Nothing to note")
|
|
1334
|
-
})
|
|
1335
|
-
|
|
1336
|
-
it("never reuses a title, a summary's included", () => {
|
|
1337
|
-
const doc = composedReview(BASE, {
|
|
1338
|
-
notes: [
|
|
1339
|
-
{ title: "a", body: "1" },
|
|
1340
|
-
{ title: "a (2)", body: "2" },
|
|
1341
|
-
{ title: "a", body: "3" },
|
|
1342
|
-
{ title: "Fixed automatically", body: "4" },
|
|
1343
|
-
],
|
|
1344
|
-
fixed: [{ title: "f", commit: "fedcba9876543210" }],
|
|
1345
|
-
noise: [],
|
|
1346
|
-
})
|
|
1347
|
-
const titles = doc.split("\n").filter((l) => /^#{2,3} /.test(l))
|
|
1348
|
-
expect(titles).toEqual([
|
|
1349
|
-
"## Findings",
|
|
1350
|
-
"### a",
|
|
1351
|
-
"### a (2)",
|
|
1352
|
-
"### a (3)",
|
|
1353
|
-
"### Fixed automatically (2)",
|
|
1354
|
-
"## Fixed automatically",
|
|
1355
|
-
])
|
|
1356
|
-
})
|
|
1357
|
-
|
|
1358
|
-
it("turns a lens's task boxes into plain bullets, so no pointer or risk is read", () => {
|
|
1359
|
-
const doc = composedReview(BASE, {
|
|
1360
|
-
notes: [
|
|
1361
|
-
{ title: "a", body: "- [ ] ./src/a.ts#1-1 Risk: x\n- [x] done\n1. [ ] one\n> - [ ] q" },
|
|
1362
|
-
],
|
|
1363
|
-
fixed: [],
|
|
1364
|
-
noise: [],
|
|
1365
|
-
})
|
|
1366
|
-
expect(steeringFormatFor("review")!.validate(doc)).toEqual([])
|
|
1367
|
-
expect(doc).toContain("- ./src/a.ts#1-1 Risk: x\n- done\n1. one\n> - q")
|
|
1368
|
-
})
|
|
1369
|
-
|
|
1370
|
-
it("names the nit fix on the review after it", () => {
|
|
1371
|
-
const doc = composedReview(BASE, { notes: [], fixed: [], noise: [], nitFix: "abcdef0123" })
|
|
1372
|
-
expect(doc).toContain(
|
|
1373
|
-
"## Fixed from your notes\n\nYour nits and fix-as-suggested notes were worked on in abcdef0;",
|
|
1374
|
-
)
|
|
1375
|
-
})
|
|
1376
|
-
|
|
1377
|
-
it("ends every note, and only a note, in its three answer boxes", () => {
|
|
1378
|
-
const doc = composedReview(BASE, {
|
|
1379
|
-
notes: [{ title: "a", body: "- ends in a list" }],
|
|
1380
|
-
fixed: [{ title: "f", commit: "fedcba9876543210" }],
|
|
1381
|
-
noise: [],
|
|
1382
|
-
})
|
|
1383
|
-
expect(doc).toContain(
|
|
1384
|
-
"### a\n\n- ends in a list\n\nTick exactly one:\n\n- [ ] fix as suggested\n- [ ] won't fix\n- [ ] _your answer_\n\n## Fixed automatically",
|
|
1385
|
-
)
|
|
1386
|
-
expect(doc.match(/fix as suggested/g)).toHaveLength(1)
|
|
1387
|
-
expect(unansweredNotes(doc)).toEqual([{ title: "a", line: 7, reason: "tick exactly one box" }])
|
|
1388
|
-
})
|
|
1389
|
-
|
|
1390
|
-
it("leaves out the notes already answered", () => {
|
|
1391
|
-
const doc = composedReview(BASE, {
|
|
1392
|
-
notes: [
|
|
1393
|
-
{ title: "a", body: "1" },
|
|
1394
|
-
{ title: "a", body: "2" },
|
|
1395
|
-
],
|
|
1396
|
-
fixed: [],
|
|
1397
|
-
noise: [],
|
|
1398
|
-
answered: new Set(["a"]),
|
|
1399
|
-
})
|
|
1400
|
-
expect(doc).not.toContain("# a\n")
|
|
1401
|
-
expect(doc).toContain("## a (2)\n\n2")
|
|
1402
|
-
})
|
|
1403
|
-
})
|
|
1404
|
-
|
|
1405
|
-
describe("walkthrough", () => {
|
|
1406
|
-
const empty = { notes: [], fixed: [], noise: [] }
|
|
1258
|
+
describe("withSummary", () => {
|
|
1259
|
+
const empty = { fixed: [], gateFixed: [], dropped: [], noise: [], unresolved: [] }
|
|
1407
1260
|
const review = [
|
|
1408
1261
|
"# Review: abc1234",
|
|
1409
1262
|
"",
|
|
@@ -1413,201 +1266,144 @@ describe("walkthrough", () => {
|
|
|
1413
1266
|
"",
|
|
1414
1267
|
"- [ ] ./src/calc.ts#2-2 — subtract(a, b)",
|
|
1415
1268
|
"",
|
|
1416
|
-
"##
|
|
1269
|
+
"## Quality lap",
|
|
1417
1270
|
"",
|
|
1418
1271
|
"- [ ] ./src/calc.ts#2-2 — the reviewer's own chunk",
|
|
1419
1272
|
"",
|
|
1420
1273
|
].join("\n")
|
|
1421
1274
|
|
|
1422
|
-
it("
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
"x (2)",
|
|
1432
|
-
])
|
|
1433
|
-
expect(titledNotes({ ...empty, notes, answered: new Set(["x"]) }).map((n) => n.title)).toEqual([
|
|
1434
|
-
"Fixed automatically (2)",
|
|
1435
|
-
"x (2)",
|
|
1436
|
-
])
|
|
1437
|
-
})
|
|
1438
|
-
|
|
1439
|
-
it("lists each note with its exact heading and quoted body", () => {
|
|
1440
|
-
const block = walkthroughBlock([
|
|
1441
|
-
{ title: "No test", body: "Add one.\n\n- [ ] a box", lens: "tests" },
|
|
1442
|
-
])
|
|
1443
|
-
expect(block).toContain("- `## No test`:\n\n > Add one.\n >\n > - a box")
|
|
1444
|
-
expect(block).toContain("its heading exactly as given")
|
|
1445
|
-
})
|
|
1446
|
-
|
|
1447
|
-
it("names the nit fix's commit, and says nothing with nothing to tell", () => {
|
|
1448
|
-
expect(walkthroughBlock([], "0123456789")).toContain(
|
|
1449
|
-
"Commit `0123456` worked on the human's nits",
|
|
1450
|
-
)
|
|
1451
|
-
expect(walkthroughBlock([])).toBe("")
|
|
1452
|
-
})
|
|
1453
|
-
|
|
1454
|
-
it("puts a note's text under its heading and its boxes after the reviewer's pointers", () => {
|
|
1455
|
-
const out = withWalkthrough(review, {
|
|
1456
|
-
...empty,
|
|
1457
|
-
notes: [{ title: "Subtract", body: "It may overflow.", unresolved: true }],
|
|
1275
|
+
it("lists what the lap did in one chunk, numbered past a title the reviewer took", () => {
|
|
1276
|
+
const out = withSummary(review, {
|
|
1277
|
+
report: {
|
|
1278
|
+
fixed: [{ title: "f", commit: "fedcba9876543210", lens: "tests" }],
|
|
1279
|
+
gateFixed: [{ title: "g", commit: "0123456789abcdef" }],
|
|
1280
|
+
dropped: [{ title: "d" }],
|
|
1281
|
+
noise: [{ title: "n", p: 0.8 }],
|
|
1282
|
+
unresolved: [{ title: "u" }, { title: "v", unclear: true }],
|
|
1283
|
+
},
|
|
1458
1284
|
})
|
|
1285
|
+
expect(steeringFormatFor("review")!.validate(out)).toEqual([])
|
|
1459
1286
|
expect(out).toContain(
|
|
1460
1287
|
[
|
|
1461
|
-
"##
|
|
1288
|
+
"## Quality lap (2)",
|
|
1289
|
+
"",
|
|
1290
|
+
"What the quality lenses found, and what became of it. Write a note under",
|
|
1291
|
+
"a line to reopen it.",
|
|
1292
|
+
"",
|
|
1293
|
+
"Fixed automatically:",
|
|
1462
1294
|
"",
|
|
1463
|
-
"
|
|
1295
|
+
"- f (fedcba9, lens tests)",
|
|
1464
1296
|
"",
|
|
1465
|
-
"
|
|
1297
|
+
"Fixed at the finding gate:",
|
|
1466
1298
|
"",
|
|
1467
|
-
"
|
|
1299
|
+
"- g (0123456)",
|
|
1468
1300
|
"",
|
|
1469
|
-
"
|
|
1301
|
+
"Dropped at the finding gate:",
|
|
1470
1302
|
"",
|
|
1471
|
-
"
|
|
1303
|
+
"- d",
|
|
1472
1304
|
"",
|
|
1473
|
-
"
|
|
1474
|
-
"
|
|
1475
|
-
"-
|
|
1305
|
+
"Dropped as noise:",
|
|
1306
|
+
"",
|
|
1307
|
+
"- n (p=0.8)",
|
|
1308
|
+
"",
|
|
1309
|
+
"Unresolved after the finding gate:",
|
|
1310
|
+
"",
|
|
1311
|
+
"- u",
|
|
1312
|
+
"- v (removed while renaming another)",
|
|
1476
1313
|
"",
|
|
1477
|
-
"## Fixed automatically",
|
|
1478
1314
|
].join("\n"),
|
|
1479
1315
|
)
|
|
1480
1316
|
})
|
|
1481
1317
|
|
|
1482
|
-
it("
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1318
|
+
it("asks about each undecided standard in a note that blocks sign-off, read by position", () => {
|
|
1319
|
+
const conflict = { text: "Calculators are functions.", olds: ["Calculators are classes."] }
|
|
1320
|
+
const out = withSummary(review, {
|
|
1321
|
+
report: empty,
|
|
1322
|
+
standards: { added: [], undecided: [conflict] },
|
|
1487
1323
|
})
|
|
1488
|
-
expect(
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
).
|
|
1493
|
-
})
|
|
1494
|
-
|
|
1495
|
-
it("leaves a review with nothing to add byte for byte, trailing newlines and all", () => {
|
|
1496
|
-
expect(withWalkthrough(review, empty)).toBe(review)
|
|
1497
|
-
expect(withWalkthrough(review.trimEnd(), empty)).toBe(review.trimEnd())
|
|
1498
|
-
expect(withWalkthrough(review, { ...empty, notes: [{ title: "absent", body: "x" }] })).toBe(
|
|
1499
|
-
review,
|
|
1500
|
-
)
|
|
1324
|
+
expect(out).toMatch(/^## Standard conflict [0-9a-f]{8}$/m)
|
|
1325
|
+
expect(unansweredNotes(out)).toHaveLength(1)
|
|
1326
|
+
const ticked = out.replace("- [ ] keep the old one", "- [x] keep the old one")
|
|
1327
|
+
expect(unansweredNotes(ticked)).toEqual([])
|
|
1328
|
+
expect(reviewDecisions(out, ticked)).toEqual([expect.objectContaining({ choice: "wontfix" })])
|
|
1501
1329
|
})
|
|
1502
1330
|
|
|
1503
|
-
it("
|
|
1504
|
-
const out =
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
})
|
|
1508
|
-
|
|
1509
|
-
expect(
|
|
1510
|
-
expect(decision?.body).not.toContain("./src/calc.ts")
|
|
1511
|
-
})
|
|
1512
|
-
|
|
1513
|
-
it("shows a title holding backticks as one unbroken code span", () => {
|
|
1514
|
-
expect(walkthroughBlock([{ title: "`sub` drifts", body: "x" }])).toContain(
|
|
1515
|
-
"- `` ## `sub` drifts ``:",
|
|
1516
|
-
)
|
|
1331
|
+
it("lists the standards added", () => {
|
|
1332
|
+
const out = withSummary(review, {
|
|
1333
|
+
report: empty,
|
|
1334
|
+
standards: { added: ["Prefer early returns."], undecided: [] },
|
|
1335
|
+
})
|
|
1336
|
+
expect(out).toContain("## Standards added")
|
|
1337
|
+
expect(out).toContain("- Prefer early returns.")
|
|
1517
1338
|
})
|
|
1518
1339
|
|
|
1519
|
-
it("
|
|
1520
|
-
expect(
|
|
1521
|
-
"never open a note in a finding's chunk with `Risk:`",
|
|
1522
|
-
)
|
|
1340
|
+
it("leaves a review with nothing to add byte for byte, trailing newlines and all", () => {
|
|
1341
|
+
expect(withSummary(`${review}\n\n`, { report: empty })).toBe(`${review}\n\n`)
|
|
1523
1342
|
})
|
|
1524
1343
|
})
|
|
1525
1344
|
|
|
1526
|
-
describe("
|
|
1527
|
-
const
|
|
1528
|
-
"# Review: abc1234",
|
|
1529
|
-
"",
|
|
1530
|
-
"<!-- base: abc1234 -->",
|
|
1531
|
-
"",
|
|
1532
|
-
"## Rename",
|
|
1533
|
-
"",
|
|
1534
|
-
"- [ ] ./a.ts#1-1",
|
|
1535
|
-
"",
|
|
1536
|
-
"## Token",
|
|
1537
|
-
"",
|
|
1538
|
-
"_could_",
|
|
1539
|
-
"",
|
|
1540
|
-
"Reads the token.",
|
|
1541
|
-
"",
|
|
1542
|
-
"- [ ] ./a.ts#2-2",
|
|
1543
|
-
"",
|
|
1544
|
-
"## Fixed automatically",
|
|
1545
|
-
"",
|
|
1546
|
-
"- x (abc1234)",
|
|
1547
|
-
"",
|
|
1548
|
-
].join("\n")
|
|
1345
|
+
describe("findingGateFile", () => {
|
|
1346
|
+
const gate = steeringFormatFor("findings")!
|
|
1549
1347
|
|
|
1550
|
-
it("
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
{ title: "
|
|
1554
|
-
{ title: "
|
|
1555
|
-
]
|
|
1556
|
-
|
|
1348
|
+
it("lists the open notes, then the noise, each with an unticked box, and reads back clean", () => {
|
|
1349
|
+
const file = findingGateFile(
|
|
1350
|
+
[
|
|
1351
|
+
{ title: "a", body: "Design.", lens: "tests" },
|
|
1352
|
+
{ title: "b", body: "Left over.", unresolved: true },
|
|
1353
|
+
],
|
|
1354
|
+
[{ title: "n", body: "Meh.", p: 0.8 }],
|
|
1355
|
+
)
|
|
1356
|
+
expect(file).toBe(
|
|
1557
1357
|
[
|
|
1558
|
-
"#
|
|
1358
|
+
"# Quality",
|
|
1359
|
+
"",
|
|
1360
|
+
"## a",
|
|
1361
|
+
"",
|
|
1362
|
+
"_From lens tests._",
|
|
1559
1363
|
"",
|
|
1560
|
-
"
|
|
1364
|
+
"Design.",
|
|
1561
1365
|
"",
|
|
1562
|
-
"
|
|
1366
|
+
"- [ ] fix",
|
|
1563
1367
|
"",
|
|
1564
|
-
"
|
|
1368
|
+
"## b",
|
|
1565
1369
|
"",
|
|
1566
|
-
"
|
|
1370
|
+
"_The fixer left it unresolved._",
|
|
1567
1371
|
"",
|
|
1568
|
-
"
|
|
1372
|
+
"Left over.",
|
|
1569
1373
|
"",
|
|
1570
|
-
"
|
|
1374
|
+
"- [ ] fix",
|
|
1571
1375
|
"",
|
|
1572
|
-
"
|
|
1376
|
+
"# Dropped as noise",
|
|
1573
1377
|
"",
|
|
1574
|
-
"
|
|
1378
|
+
"## n",
|
|
1575
1379
|
"",
|
|
1576
|
-
"
|
|
1380
|
+
"_The judge called it noise (p=0.8)._",
|
|
1577
1381
|
"",
|
|
1578
|
-
"
|
|
1382
|
+
"Meh.",
|
|
1383
|
+
"",
|
|
1384
|
+
"- [ ] fix",
|
|
1579
1385
|
"",
|
|
1580
1386
|
].join("\n"),
|
|
1581
1387
|
)
|
|
1388
|
+
expect(gate.validate(file)).toEqual([])
|
|
1389
|
+
expect(findingEntries(file).map((e) => [e.title, e.noise, e.fix])).toEqual([
|
|
1390
|
+
["a", false, false],
|
|
1391
|
+
["b", false, false],
|
|
1392
|
+
["n", true, false],
|
|
1393
|
+
])
|
|
1582
1394
|
})
|
|
1583
1395
|
|
|
1584
|
-
it("
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
]),
|
|
1589
|
-
).toBe("# Review: a\n\nToken\n-----\n\n_could_\n\n- [ ] ./a.ts#1\n")
|
|
1590
|
-
})
|
|
1591
|
-
|
|
1592
|
-
it("keeps a chunk's leading indented code block indented", () => {
|
|
1593
|
-
const code = "# Review: abc1234\n\n## Code\n\n const x = 1\n\n- [ ] ./a.ts#1-1\n"
|
|
1594
|
-
expect(rankedReview(code, [{ title: "Code", label: "could" }])).toContain(
|
|
1595
|
-
"_could_\n\n const x = 1",
|
|
1596
|
-
)
|
|
1597
|
-
})
|
|
1598
|
-
|
|
1599
|
-
it("drops a stale tier with no blank line under the heading, never a longer paragraph", () => {
|
|
1600
|
-
expect(unranked("## A\n_could_\n\nbody\n")).toBe("## A\nbody\n")
|
|
1601
|
-
expect(unranked("## A\n\n_could_\n- [ ] ./a.ts#1\n")).toBe("## A\n\n- [ ] ./a.ts#1\n")
|
|
1602
|
-
const code = "## A\n\n _could_\n"
|
|
1603
|
-
expect(unranked(code)).toBe(code)
|
|
1604
|
-
const prose = "## A\n\n_should: x_\nruns on\n"
|
|
1605
|
-
expect(unranked(prose)).toBe(prose)
|
|
1396
|
+
it("turns a lens's task boxes into plain bullets, so no box is read but its own", () => {
|
|
1397
|
+
const file = findingGateFile([{ title: "a", body: "- [ ] fix\n- [x] done" }], [])
|
|
1398
|
+
expect(file).toContain("- fix\n- done")
|
|
1399
|
+
expect(findingEntries(file)[0]!.boxes).toBe(1)
|
|
1606
1400
|
})
|
|
1401
|
+
})
|
|
1607
1402
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
expect(
|
|
1403
|
+
describe("nitFixBlock", () => {
|
|
1404
|
+
it("names the nit fix's commit, and says nothing with nothing to tell", () => {
|
|
1405
|
+
expect(nitFixBlock("0123456789")).toContain("Commit `0123456` worked on the human's nits")
|
|
1406
|
+
expect(nitFixBlock()).toBe("")
|
|
1611
1407
|
})
|
|
1612
1408
|
})
|
|
1613
1409
|
|