@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
|
@@ -33,14 +33,16 @@ import {
|
|
|
33
33
|
seamSteerMessage,
|
|
34
34
|
summaryPrompt,
|
|
35
35
|
withSkills,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
withWalkthrough,
|
|
40
|
-
rankedReview,
|
|
41
|
-
unranked,
|
|
36
|
+
findingGateFile,
|
|
37
|
+
nitFixBlock,
|
|
38
|
+
withSummary,
|
|
42
39
|
} from "./text.js"
|
|
43
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
findingEntries,
|
|
42
|
+
reviewDecisions,
|
|
43
|
+
steeringFormatFor,
|
|
44
|
+
unansweredNotes,
|
|
45
|
+
} from "../../steering/index.js"
|
|
44
46
|
import { captureStep, renderText } from "./text.fixture.js"
|
|
45
47
|
|
|
46
48
|
describe("withSkills", () => {
|
|
@@ -898,14 +900,27 @@ describe("the seam phase's texts", () => {
|
|
|
898
900
|
expect(prompt).toContain("solved the task the wrong way")
|
|
899
901
|
})
|
|
900
902
|
|
|
901
|
-
it("asks for a breadth-first pass
|
|
903
|
+
it("asks for a breadth-first pass tested through existing code, green before it ends", () => {
|
|
902
904
|
const prompt = renderText(() => naivePassPrompt())
|
|
903
905
|
expect(prompt).toContain("breadth-first")
|
|
904
|
-
expect(prompt).toContain("`[AC-3] …`")
|
|
905
906
|
expect(prompt).toContain("never a file this pass adds")
|
|
906
907
|
expect(prompt).toContain("green")
|
|
907
908
|
})
|
|
908
909
|
|
|
910
|
+
it.each([
|
|
911
|
+
["naive", () => naivePassPrompt()],
|
|
912
|
+
["building", () => implementBuildingPrompt("abc123")],
|
|
913
|
+
["prototype fixing", () => prototypeFixingPrompt()],
|
|
914
|
+
])(
|
|
915
|
+
"the %s prompt asks for a test per behaviour, never a criterion ID in its name",
|
|
916
|
+
(_n, prompt) => {
|
|
917
|
+
const text = renderText(prompt)
|
|
918
|
+
expect(text).toContain("one test per behaviour, not per\n criterion")
|
|
919
|
+
expect(text).toContain("never put criterion IDs in test or scenario names")
|
|
920
|
+
expect(text).not.toContain("[AC-3]")
|
|
921
|
+
},
|
|
922
|
+
)
|
|
923
|
+
|
|
909
924
|
it("has a red retry pick up the kept attempt instead of starting over", () => {
|
|
910
925
|
const prompt = renderText(() => naivePassPrompt({ red: "abc123" }))
|
|
911
926
|
expect(prompt).toContain("left the suite red")
|
|
@@ -972,16 +987,16 @@ describe("the seam phase's texts", () => {
|
|
|
972
987
|
|
|
973
988
|
it("hands a faulty pass back with the fault and the kept attempt to pick up", () => {
|
|
974
989
|
const fault = {
|
|
975
|
-
fault: "acceptance-
|
|
990
|
+
fault: "acceptance-internals: these tests import files the naive pass added",
|
|
976
991
|
hash: "abc123",
|
|
977
992
|
}
|
|
978
993
|
const prompt = renderText(() => naivePassPrompt(fault))
|
|
979
|
-
expect(prompt).toContain("
|
|
994
|
+
expect(prompt).toContain("import files the naive pass added")
|
|
980
995
|
expect(prompt).toContain(
|
|
981
996
|
"git diff --binary --no-color --no-ext-diff --no-textconv abc123^ abc123 | git apply",
|
|
982
997
|
)
|
|
983
998
|
const message = renderText(() => seamSteerMessage(fault))
|
|
984
|
-
expect(message).toContain("
|
|
999
|
+
expect(message).toContain("import files the naive pass added")
|
|
985
1000
|
expect(message).toContain("`seam.check` runs its suite")
|
|
986
1001
|
expect(message).toContain("Overrule")
|
|
987
1002
|
})
|
|
@@ -997,7 +1012,7 @@ describe("the seam phase's texts", () => {
|
|
|
997
1012
|
})
|
|
998
1013
|
|
|
999
1014
|
it("promises no judge on an overrule with flagMode off", () => {
|
|
1000
|
-
for (const retry of [{ red: "abc123" }, { fault: "acceptance-
|
|
1015
|
+
for (const retry of [{ red: "abc123" }, { fault: "acceptance-internals: x", hash: "abc123" }]) {
|
|
1001
1016
|
const message = renderText(() => seamSteerMessage(retry, false))
|
|
1002
1017
|
expect(message).not.toContain("seam judge")
|
|
1003
1018
|
expect(message).toContain("it lands as the implementation")
|
|
@@ -1259,138 +1274,8 @@ describe("buildReviewCollectingPrompt answer boxes", () => {
|
|
|
1259
1274
|
})
|
|
1260
1275
|
})
|
|
1261
1276
|
|
|
1262
|
-
describe("
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
describe("standards", () => {
|
|
1266
|
-
const conflict = { text: "Calculators are functions.", olds: ["Calculators are classes."] }
|
|
1267
|
-
const withConflict = composedReview(BASE, {
|
|
1268
|
-
notes: [],
|
|
1269
|
-
fixed: [],
|
|
1270
|
-
noise: [],
|
|
1271
|
-
standards: { added: [], undecided: [conflict] },
|
|
1272
|
-
})
|
|
1273
|
-
|
|
1274
|
-
it("asks about each undecided standard in a note that blocks sign-off", () => {
|
|
1275
|
-
expect(withConflict).toMatch(/^## Findings\n\n### Standard conflict [0-9a-f]{8}$/m)
|
|
1276
|
-
expect(withConflict).toContain("- [ ] keep the new one (drop the old)")
|
|
1277
|
-
expect(unansweredNotes(withConflict)).toHaveLength(1)
|
|
1278
|
-
})
|
|
1279
|
-
|
|
1280
|
-
it("reads its boxes by position, like a note's", () => {
|
|
1281
|
-
const ticked = withConflict.replace("- [ ] keep the old one", "- [x] keep the old one")
|
|
1282
|
-
expect(unansweredNotes(ticked)).toEqual([])
|
|
1283
|
-
expect(reviewDecisions(withConflict, ticked)).toEqual([
|
|
1284
|
-
expect.objectContaining({ choice: "wontfix" }),
|
|
1285
|
-
])
|
|
1286
|
-
})
|
|
1287
|
-
|
|
1288
|
-
it("lists the standards added", () => {
|
|
1289
|
-
const doc = composedReview(BASE, {
|
|
1290
|
-
notes: [],
|
|
1291
|
-
fixed: [],
|
|
1292
|
-
noise: [],
|
|
1293
|
-
standards: { added: ["Prefer early returns."], undecided: [] },
|
|
1294
|
-
})
|
|
1295
|
-
expect(doc).toContain("## Standards added")
|
|
1296
|
-
expect(doc).toContain("- Prefer early returns.")
|
|
1297
|
-
})
|
|
1298
|
-
})
|
|
1299
|
-
|
|
1300
|
-
it("is a valid review with unique chunk titles, so every note matches its own chunk", () => {
|
|
1301
|
-
const doc = composedReview(BASE, {
|
|
1302
|
-
notes: [
|
|
1303
|
-
{ title: "same", body: "one" },
|
|
1304
|
-
{ title: "same", body: "two", unresolved: true },
|
|
1305
|
-
],
|
|
1306
|
-
fixed: [{ title: "f", commit: "fedcba9876543210" }],
|
|
1307
|
-
noise: [{ title: "n", p: 0.8 }],
|
|
1308
|
-
})
|
|
1309
|
-
expect(steeringFormatFor("review")!.validate(doc)).toEqual([])
|
|
1310
|
-
expect(doc).toContain("# Review: 0123456\n\n<!-- base: " + BASE + " -->")
|
|
1311
|
-
expect(doc).toContain("## Findings\n\n### same\n\none")
|
|
1312
|
-
expect(doc).toContain("### same (2)\n\n_The fixer left this unresolved._\n\ntwo")
|
|
1313
|
-
expect(doc).toContain("## Fixed automatically\n\n- f (fedcba9)")
|
|
1314
|
-
expect(doc).toContain("## Dropped as noise\n\n- n (p=0.8)")
|
|
1315
|
-
})
|
|
1316
|
-
|
|
1317
|
-
it("holds one chunk when the lap left nothing", () => {
|
|
1318
|
-
const doc = composedReview(BASE, { notes: [], fixed: [], noise: [] })
|
|
1319
|
-
expect(steeringFormatFor("review")!.validate(doc)).toEqual([])
|
|
1320
|
-
expect(doc).toContain("## Nothing to note")
|
|
1321
|
-
})
|
|
1322
|
-
|
|
1323
|
-
it("never reuses a title, a summary's included", () => {
|
|
1324
|
-
const doc = composedReview(BASE, {
|
|
1325
|
-
notes: [
|
|
1326
|
-
{ title: "a", body: "1" },
|
|
1327
|
-
{ title: "a (2)", body: "2" },
|
|
1328
|
-
{ title: "a", body: "3" },
|
|
1329
|
-
{ title: "Fixed automatically", body: "4" },
|
|
1330
|
-
],
|
|
1331
|
-
fixed: [{ title: "f", commit: "fedcba9876543210" }],
|
|
1332
|
-
noise: [],
|
|
1333
|
-
})
|
|
1334
|
-
const titles = doc.split("\n").filter((l) => /^#{2,3} /.test(l))
|
|
1335
|
-
expect(titles).toEqual([
|
|
1336
|
-
"## Findings",
|
|
1337
|
-
"### a",
|
|
1338
|
-
"### a (2)",
|
|
1339
|
-
"### a (3)",
|
|
1340
|
-
"### Fixed automatically (2)",
|
|
1341
|
-
"## Fixed automatically",
|
|
1342
|
-
])
|
|
1343
|
-
})
|
|
1344
|
-
|
|
1345
|
-
it("turns a lens's task boxes into plain bullets, so no pointer or risk is read", () => {
|
|
1346
|
-
const doc = composedReview(BASE, {
|
|
1347
|
-
notes: [
|
|
1348
|
-
{ title: "a", body: "- [ ] ./src/a.ts#1-1 Risk: x\n- [x] done\n1. [ ] one\n> - [ ] q" },
|
|
1349
|
-
],
|
|
1350
|
-
fixed: [],
|
|
1351
|
-
noise: [],
|
|
1352
|
-
})
|
|
1353
|
-
expect(steeringFormatFor("review")!.validate(doc)).toEqual([])
|
|
1354
|
-
expect(doc).toContain("- ./src/a.ts#1-1 Risk: x\n- done\n1. one\n> - q")
|
|
1355
|
-
})
|
|
1356
|
-
|
|
1357
|
-
it("names the nit fix on the review after it", () => {
|
|
1358
|
-
const doc = composedReview(BASE, { notes: [], fixed: [], noise: [], nitFix: "abcdef0123" })
|
|
1359
|
-
expect(doc).toContain(
|
|
1360
|
-
"## Fixed from your notes\n\nYour nits and fix-as-suggested notes were worked on in abcdef0;",
|
|
1361
|
-
)
|
|
1362
|
-
})
|
|
1363
|
-
|
|
1364
|
-
it("ends every note, and only a note, in its three answer boxes", () => {
|
|
1365
|
-
const doc = composedReview(BASE, {
|
|
1366
|
-
notes: [{ title: "a", body: "- ends in a list" }],
|
|
1367
|
-
fixed: [{ title: "f", commit: "fedcba9876543210" }],
|
|
1368
|
-
noise: [],
|
|
1369
|
-
})
|
|
1370
|
-
expect(doc).toContain(
|
|
1371
|
-
"### 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",
|
|
1372
|
-
)
|
|
1373
|
-
expect(doc.match(/fix as suggested/g)).toHaveLength(1)
|
|
1374
|
-
expect(unansweredNotes(doc)).toEqual([{ title: "a", line: 7, reason: "tick exactly one box" }])
|
|
1375
|
-
})
|
|
1376
|
-
|
|
1377
|
-
it("leaves out the notes already answered", () => {
|
|
1378
|
-
const doc = composedReview(BASE, {
|
|
1379
|
-
notes: [
|
|
1380
|
-
{ title: "a", body: "1" },
|
|
1381
|
-
{ title: "a", body: "2" },
|
|
1382
|
-
],
|
|
1383
|
-
fixed: [],
|
|
1384
|
-
noise: [],
|
|
1385
|
-
answered: new Set(["a"]),
|
|
1386
|
-
})
|
|
1387
|
-
expect(doc).not.toContain("# a\n")
|
|
1388
|
-
expect(doc).toContain("## a (2)\n\n2")
|
|
1389
|
-
})
|
|
1390
|
-
})
|
|
1391
|
-
|
|
1392
|
-
describe("walkthrough", () => {
|
|
1393
|
-
const empty = { notes: [], fixed: [], noise: [] }
|
|
1277
|
+
describe("withSummary", () => {
|
|
1278
|
+
const empty = { fixed: [], gateFixed: [], dropped: [], noise: [], unresolved: [] }
|
|
1394
1279
|
const review = [
|
|
1395
1280
|
"# Review: abc1234",
|
|
1396
1281
|
"",
|
|
@@ -1400,201 +1285,144 @@ describe("walkthrough", () => {
|
|
|
1400
1285
|
"",
|
|
1401
1286
|
"- [ ] ./src/calc.ts#2-2 — subtract(a, b)",
|
|
1402
1287
|
"",
|
|
1403
|
-
"##
|
|
1288
|
+
"## Quality lap",
|
|
1404
1289
|
"",
|
|
1405
1290
|
"- [ ] ./src/calc.ts#2-2 — the reviewer's own chunk",
|
|
1406
1291
|
"",
|
|
1407
1292
|
].join("\n")
|
|
1408
1293
|
|
|
1409
|
-
it("
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
"x (2)",
|
|
1419
|
-
])
|
|
1420
|
-
expect(titledNotes({ ...empty, notes, answered: new Set(["x"]) }).map((n) => n.title)).toEqual([
|
|
1421
|
-
"Fixed automatically (2)",
|
|
1422
|
-
"x (2)",
|
|
1423
|
-
])
|
|
1424
|
-
})
|
|
1425
|
-
|
|
1426
|
-
it("lists each note with its exact heading and quoted body", () => {
|
|
1427
|
-
const block = walkthroughBlock([
|
|
1428
|
-
{ title: "No test", body: "Add one.\n\n- [ ] a box", lens: "tests" },
|
|
1429
|
-
])
|
|
1430
|
-
expect(block).toContain("- `## No test`:\n\n > Add one.\n >\n > - a box")
|
|
1431
|
-
expect(block).toContain("its heading exactly as given")
|
|
1432
|
-
})
|
|
1433
|
-
|
|
1434
|
-
it("names the nit fix's commit, and says nothing with nothing to tell", () => {
|
|
1435
|
-
expect(walkthroughBlock([], "0123456789")).toContain(
|
|
1436
|
-
"Commit `0123456` worked on the human's nits",
|
|
1437
|
-
)
|
|
1438
|
-
expect(walkthroughBlock([])).toBe("")
|
|
1439
|
-
})
|
|
1440
|
-
|
|
1441
|
-
it("puts a note's text under its heading and its boxes after the reviewer's pointers", () => {
|
|
1442
|
-
const out = withWalkthrough(review, {
|
|
1443
|
-
...empty,
|
|
1444
|
-
notes: [{ title: "Subtract", body: "It may overflow.", unresolved: true }],
|
|
1294
|
+
it("lists what the lap did in one chunk, numbered past a title the reviewer took", () => {
|
|
1295
|
+
const out = withSummary(review, {
|
|
1296
|
+
report: {
|
|
1297
|
+
fixed: [{ title: "f", commit: "fedcba9876543210", lens: "tests" }],
|
|
1298
|
+
gateFixed: [{ title: "g", commit: "0123456789abcdef" }],
|
|
1299
|
+
dropped: [{ title: "d" }],
|
|
1300
|
+
noise: [{ title: "n", p: 0.8 }],
|
|
1301
|
+
unresolved: [{ title: "u" }, { title: "v", unclear: true }],
|
|
1302
|
+
},
|
|
1445
1303
|
})
|
|
1304
|
+
expect(steeringFormatFor("review")!.validate(out)).toEqual([])
|
|
1446
1305
|
expect(out).toContain(
|
|
1447
1306
|
[
|
|
1448
|
-
"##
|
|
1307
|
+
"## Quality lap (2)",
|
|
1308
|
+
"",
|
|
1309
|
+
"What the quality lenses found, and what became of it. Write a note under",
|
|
1310
|
+
"a line to reopen it.",
|
|
1311
|
+
"",
|
|
1312
|
+
"Fixed automatically:",
|
|
1313
|
+
"",
|
|
1314
|
+
"- f (fedcba9, lens tests)",
|
|
1315
|
+
"",
|
|
1316
|
+
"Fixed at the finding gate:",
|
|
1317
|
+
"",
|
|
1318
|
+
"- g (0123456)",
|
|
1449
1319
|
"",
|
|
1450
|
-
"
|
|
1320
|
+
"Dropped at the finding gate:",
|
|
1451
1321
|
"",
|
|
1452
|
-
"
|
|
1322
|
+
"- d",
|
|
1453
1323
|
"",
|
|
1454
|
-
"
|
|
1324
|
+
"Dropped as noise:",
|
|
1455
1325
|
"",
|
|
1456
|
-
"-
|
|
1326
|
+
"- n (p=0.8)",
|
|
1457
1327
|
"",
|
|
1458
|
-
"
|
|
1328
|
+
"Unresolved after the finding gate:",
|
|
1459
1329
|
"",
|
|
1460
|
-
"-
|
|
1461
|
-
"-
|
|
1462
|
-
"- [ ] _your answer_",
|
|
1330
|
+
"- u",
|
|
1331
|
+
"- v (removed while renaming another)",
|
|
1463
1332
|
"",
|
|
1464
|
-
"## Fixed automatically",
|
|
1465
1333
|
].join("\n"),
|
|
1466
1334
|
)
|
|
1467
1335
|
})
|
|
1468
1336
|
|
|
1469
|
-
it("
|
|
1470
|
-
const
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1337
|
+
it("asks about each undecided standard in a note that blocks sign-off, read by position", () => {
|
|
1338
|
+
const conflict = { text: "Calculators are functions.", olds: ["Calculators are classes."] }
|
|
1339
|
+
const out = withSummary(review, {
|
|
1340
|
+
report: empty,
|
|
1341
|
+
standards: { added: [], undecided: [conflict] },
|
|
1474
1342
|
})
|
|
1475
|
-
expect(
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
).
|
|
1343
|
+
expect(out).toMatch(/^## Standard conflict [0-9a-f]{8}$/m)
|
|
1344
|
+
expect(unansweredNotes(out)).toHaveLength(1)
|
|
1345
|
+
const ticked = out.replace("- [ ] keep the old one", "- [x] keep the old one")
|
|
1346
|
+
expect(unansweredNotes(ticked)).toEqual([])
|
|
1347
|
+
expect(reviewDecisions(out, ticked)).toEqual([expect.objectContaining({ choice: "wontfix" })])
|
|
1480
1348
|
})
|
|
1481
1349
|
|
|
1482
|
-
it("
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
)
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
it("keeps a note ending in a list apart from the pointers, so its answer carries the list", () => {
|
|
1491
|
-
const out = withWalkthrough(review, {
|
|
1492
|
-
...empty,
|
|
1493
|
-
notes: [{ title: "Subtract", body: "Do this:\n\n- step one\n- step two" }],
|
|
1494
|
-
}).replace("- [ ] fix as suggested", "- [x] fix as suggested")
|
|
1495
|
-
const [decision] = reviewDecisions("", out)
|
|
1496
|
-
expect(decision?.body).toContain("step two")
|
|
1497
|
-
expect(decision?.body).not.toContain("./src/calc.ts")
|
|
1498
|
-
})
|
|
1499
|
-
|
|
1500
|
-
it("shows a title holding backticks as one unbroken code span", () => {
|
|
1501
|
-
expect(walkthroughBlock([{ title: "`sub` drifts", body: "x" }])).toContain(
|
|
1502
|
-
"- `` ## `sub` drifts ``:",
|
|
1503
|
-
)
|
|
1350
|
+
it("lists the standards added", () => {
|
|
1351
|
+
const out = withSummary(review, {
|
|
1352
|
+
report: empty,
|
|
1353
|
+
standards: { added: ["Prefer early returns."], undecided: [] },
|
|
1354
|
+
})
|
|
1355
|
+
expect(out).toContain("## Standards added")
|
|
1356
|
+
expect(out).toContain("- Prefer early returns.")
|
|
1504
1357
|
})
|
|
1505
1358
|
|
|
1506
|
-
it("
|
|
1507
|
-
expect(
|
|
1508
|
-
"never open a note in a finding's chunk with `Risk:`",
|
|
1509
|
-
)
|
|
1359
|
+
it("leaves a review with nothing to add byte for byte, trailing newlines and all", () => {
|
|
1360
|
+
expect(withSummary(`${review}\n\n`, { report: empty })).toBe(`${review}\n\n`)
|
|
1510
1361
|
})
|
|
1511
1362
|
})
|
|
1512
1363
|
|
|
1513
|
-
describe("
|
|
1514
|
-
const
|
|
1515
|
-
"# Review: abc1234",
|
|
1516
|
-
"",
|
|
1517
|
-
"<!-- base: abc1234 -->",
|
|
1518
|
-
"",
|
|
1519
|
-
"## Rename",
|
|
1520
|
-
"",
|
|
1521
|
-
"- [ ] ./a.ts#1-1",
|
|
1522
|
-
"",
|
|
1523
|
-
"## Token",
|
|
1524
|
-
"",
|
|
1525
|
-
"_could_",
|
|
1526
|
-
"",
|
|
1527
|
-
"Reads the token.",
|
|
1528
|
-
"",
|
|
1529
|
-
"- [ ] ./a.ts#2-2",
|
|
1530
|
-
"",
|
|
1531
|
-
"## Fixed automatically",
|
|
1532
|
-
"",
|
|
1533
|
-
"- x (abc1234)",
|
|
1534
|
-
"",
|
|
1535
|
-
].join("\n")
|
|
1364
|
+
describe("findingGateFile", () => {
|
|
1365
|
+
const gate = steeringFormatFor("findings")!
|
|
1536
1366
|
|
|
1537
|
-
it("
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
{ title: "
|
|
1541
|
-
{ title: "
|
|
1542
|
-
]
|
|
1543
|
-
|
|
1367
|
+
it("lists the open notes, then the noise, each with an unticked box, and reads back clean", () => {
|
|
1368
|
+
const file = findingGateFile(
|
|
1369
|
+
[
|
|
1370
|
+
{ title: "a", body: "Design.", lens: "tests" },
|
|
1371
|
+
{ title: "b", body: "Left over.", unresolved: true },
|
|
1372
|
+
],
|
|
1373
|
+
[{ title: "n", body: "Meh.", p: 0.8 }],
|
|
1374
|
+
)
|
|
1375
|
+
expect(file).toBe(
|
|
1544
1376
|
[
|
|
1545
|
-
"#
|
|
1377
|
+
"# Quality",
|
|
1546
1378
|
"",
|
|
1547
|
-
"
|
|
1379
|
+
"## a",
|
|
1548
1380
|
"",
|
|
1549
|
-
"
|
|
1381
|
+
"_From lens tests._",
|
|
1550
1382
|
"",
|
|
1551
|
-
"
|
|
1383
|
+
"Design.",
|
|
1552
1384
|
"",
|
|
1553
|
-
"
|
|
1385
|
+
"- [ ] fix",
|
|
1554
1386
|
"",
|
|
1555
|
-
"
|
|
1387
|
+
"## b",
|
|
1556
1388
|
"",
|
|
1557
|
-
"
|
|
1389
|
+
"_The fixer left it unresolved._",
|
|
1558
1390
|
"",
|
|
1559
|
-
"
|
|
1391
|
+
"Left over.",
|
|
1560
1392
|
"",
|
|
1561
|
-
"- [ ]
|
|
1393
|
+
"- [ ] fix",
|
|
1562
1394
|
"",
|
|
1563
|
-
"
|
|
1395
|
+
"# Dropped as noise",
|
|
1564
1396
|
"",
|
|
1565
|
-
"
|
|
1397
|
+
"## n",
|
|
1398
|
+
"",
|
|
1399
|
+
"_The judge called it noise (p=0.8)._",
|
|
1400
|
+
"",
|
|
1401
|
+
"Meh.",
|
|
1402
|
+
"",
|
|
1403
|
+
"- [ ] fix",
|
|
1566
1404
|
"",
|
|
1567
1405
|
].join("\n"),
|
|
1568
1406
|
)
|
|
1407
|
+
expect(gate.validate(file)).toEqual([])
|
|
1408
|
+
expect(findingEntries(file).map((e) => [e.title, e.noise, e.fix])).toEqual([
|
|
1409
|
+
["a", false, false],
|
|
1410
|
+
["b", false, false],
|
|
1411
|
+
["n", true, false],
|
|
1412
|
+
])
|
|
1569
1413
|
})
|
|
1570
1414
|
|
|
1571
|
-
it("
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
]),
|
|
1576
|
-
).toBe("# Review: a\n\nToken\n-----\n\n_could_\n\n- [ ] ./a.ts#1\n")
|
|
1577
|
-
})
|
|
1578
|
-
|
|
1579
|
-
it("keeps a chunk's leading indented code block indented", () => {
|
|
1580
|
-
const code = "# Review: abc1234\n\n## Code\n\n const x = 1\n\n- [ ] ./a.ts#1-1\n"
|
|
1581
|
-
expect(rankedReview(code, [{ title: "Code", label: "could" }])).toContain(
|
|
1582
|
-
"_could_\n\n const x = 1",
|
|
1583
|
-
)
|
|
1584
|
-
})
|
|
1585
|
-
|
|
1586
|
-
it("drops a stale tier with no blank line under the heading, never a longer paragraph", () => {
|
|
1587
|
-
expect(unranked("## A\n_could_\n\nbody\n")).toBe("## A\nbody\n")
|
|
1588
|
-
expect(unranked("## A\n\n_could_\n- [ ] ./a.ts#1\n")).toBe("## A\n\n- [ ] ./a.ts#1\n")
|
|
1589
|
-
const code = "## A\n\n _could_\n"
|
|
1590
|
-
expect(unranked(code)).toBe(code)
|
|
1591
|
-
const prose = "## A\n\n_should: x_\nruns on\n"
|
|
1592
|
-
expect(unranked(prose)).toBe(prose)
|
|
1415
|
+
it("turns a lens's task boxes into plain bullets, so no box is read but its own", () => {
|
|
1416
|
+
const file = findingGateFile([{ title: "a", body: "- [ ] fix\n- [x] done" }], [])
|
|
1417
|
+
expect(file).toContain("- fix\n- done")
|
|
1418
|
+
expect(findingEntries(file)[0]!.boxes).toBe(1)
|
|
1593
1419
|
})
|
|
1420
|
+
})
|
|
1594
1421
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
expect(
|
|
1422
|
+
describe("nitFixBlock", () => {
|
|
1423
|
+
it("names the nit fix's commit, and says nothing with nothing to tell", () => {
|
|
1424
|
+
expect(nitFixBlock("0123456789")).toContain("Commit `0123456` worked on the human's nits")
|
|
1425
|
+
expect(nitFixBlock()).toBe("")
|
|
1598
1426
|
})
|
|
1599
1427
|
})
|
|
1600
1428
|
|