@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.
Files changed (72) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +109 -104
  3. package/dist/chunks/{reserved-CQ7amcKG.mjs → reserved-B8EC-WE8.mjs} +124 -230
  4. package/dist/chunks/{review-CXzUXJHL.mjs → review-HdrX0FJ7.mjs} +30 -125
  5. package/dist/chunks/{steering-CLhu8C7q.mjs → steering-CCNvngSH.mjs} +256 -65
  6. package/dist/chunks/{tail-JZ9PAOwp.mjs → tail-BSLah2mA.mjs} +155 -63
  7. package/dist/gtd.bundle.mjs +259 -22
  8. package/dist/steering.mjs +2 -2
  9. package/dist/testkit.mjs +6 -6
  10. package/dist/web/main.css +1 -1
  11. package/dist/web/main.js +551 -66
  12. package/dist/workflow-kit.mjs +1 -1
  13. package/dist/workflows/cleanup.mjs +2 -2
  14. package/dist/workflows/feature.mjs +47 -102
  15. package/dist/workflows/fix.mjs +3 -3
  16. package/dist/workflows/review.mjs +3 -3
  17. package/dist/workflows/rollout.mjs +1 -1
  18. package/dist/workflows.mjs +614 -574
  19. package/package.json +1 -1
  20. package/skills/authoring/SKILL.md +3 -3
  21. package/src/cli/Cli.ts +5 -5
  22. package/src/edge/Edge.test.ts +1 -1
  23. package/src/steering/FORMATS.md +5 -1
  24. package/src/steering/SteeringFormats.test.ts +2 -2
  25. package/src/steering/contract.test.ts +1 -0
  26. package/src/steering/findings.test.ts +47 -0
  27. package/src/steering/findings.ts +350 -0
  28. package/src/steering/fixtures/findings/gate.md +34 -0
  29. package/src/steering/fixtures/findings/gate.read.json +87 -0
  30. package/src/steering/index.test.ts +2 -2
  31. package/src/steering/index.ts +4 -3
  32. package/src/steering/review.test.ts +1 -75
  33. package/src/steering/review.ts +1 -74
  34. package/src/workflow/compile.test.ts +1 -0
  35. package/src/workflows/feature/criteria-coverage.feature +104 -0
  36. package/src/workflows/feature/feature.test.ts +1 -1
  37. package/src/workflows/feature/feature.ts +17 -20
  38. package/src/workflows/feature/green.ts +2 -5
  39. package/src/workflows/feature/lens-review.feature +323 -171
  40. package/src/workflows/feature/naive-acceptance.feature +31 -32
  41. package/src/workflows/feature/prototype.ts +5 -8
  42. package/src/workflows/feature/refactor-absorbs.feature +1 -7
  43. package/src/workflows/feature/refactor-first.feature +2 -10
  44. package/src/workflows/feature/refactor-rebuild.feature +20 -36
  45. package/src/workflows/feature/refactor-review.feature +33 -117
  46. package/src/workflows/feature/refactor.test.ts +0 -2
  47. package/src/workflows/feature/refactor.ts +0 -1
  48. package/src/workflows/feature/review-gate.feature +6 -22
  49. package/src/workflows/feature/reviewer-walkthrough.feature +16 -103
  50. package/src/workflows/feature/seam.test.ts +19 -101
  51. package/src/workflows/feature/seam.ts +13 -89
  52. package/src/workflows/feature/standards.feature +75 -34
  53. package/src/workflows/fix/fix-lens-origin.feature +20 -7
  54. package/src/workflows/fix/fix-quality.feature +104 -23
  55. package/src/workflows/review/review-door.feature +62 -59
  56. package/src/workflows/shared/access.test.ts +1 -0
  57. package/src/workflows/shared/learn.test.ts +0 -14
  58. package/src/workflows/shared/learn.ts +1 -6
  59. package/src/workflows/shared/lenses.ts +149 -43
  60. package/src/workflows/shared/review.test.ts +30 -27
  61. package/src/workflows/shared/review.ts +29 -196
  62. package/src/workflows/shared/skills.test.ts +3 -1
  63. package/src/workflows/shared/steps.test.ts +9 -6
  64. package/src/workflows/shared/steps.ts +12 -3
  65. package/src/workflows/shared/tail.test.ts +20 -2
  66. package/src/workflows/shared/tail.ts +38 -16
  67. package/src/workflows/shared/text.test.ts +122 -294
  68. package/src/workflows/shared/text.ts +181 -358
  69. package/src/workflows/shared/vars.ts +1 -1
  70. package/src/workflows/feature/review-answers.feature +0 -324
  71. package/src/workflows/feature/severity-budget.feature +0 -116
  72. 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
- composedReview,
37
- titledNotes,
38
- walkthroughBlock,
39
- withWalkthrough,
40
- rankedReview,
41
- unranked,
36
+ findingGateFile,
37
+ nitFixBlock,
38
+ withSummary,
42
39
  } from "./text.js"
43
- import { reviewDecisions, steeringFormatFor, unansweredNotes } from "../../steering/index.js"
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 with ID-tagged behaviour tests, green before it ends", () => {
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-tests: no test of the naive pass names AC-2",
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("names AC-2")
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("names AC-2")
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-tests: x", hash: "abc123" }]) {
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("composedReview", () => {
1263
- const BASE = "0123456789abcdef0123456789abcdef01234567"
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
- "## Fixed automatically",
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("titles notes uniquely, never a summary's, and leaves out answered ones", () => {
1410
- const notes = [
1411
- { title: "Fixed automatically", body: "a" },
1412
- { title: "x", body: "b" },
1413
- { title: "x", body: "c" },
1414
- ]
1415
- expect(titledNotes({ ...empty, notes }).map((n) => n.title)).toEqual([
1416
- "Fixed automatically (2)",
1417
- "x",
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
- "## Subtract",
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
- "_The fixer left this unresolved._",
1320
+ "Dropped at the finding gate:",
1451
1321
  "",
1452
- "It may overflow.",
1322
+ "- d",
1453
1323
  "",
1454
- "In the code:",
1324
+ "Dropped as noise:",
1455
1325
  "",
1456
- "- [ ] ./src/calc.ts#2-2 — subtract(a, b)",
1326
+ "- n (p=0.8)",
1457
1327
  "",
1458
- "Tick exactly one:",
1328
+ "Unresolved after the finding gate:",
1459
1329
  "",
1460
- "- [ ] fix as suggested",
1461
- "- [ ] won't fix",
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("appends the fixes and noise, numbered past a title the reviewer took", () => {
1470
- const out = withWalkthrough(review, {
1471
- ...empty,
1472
- fixed: [{ title: "a", commit: "0123456789", lens: "tests" }],
1473
- noise: [{ title: "b", p: 0.9 }],
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
- out.endsWith(
1477
- "## Fixed automatically (2)\n\n- a (0123456)\n\n## Dropped as noise\n\n- b (p=0.9)\n",
1478
- ),
1479
- ).toBe(true)
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("leaves a review with nothing to add byte for byte, trailing newlines and all", () => {
1483
- expect(withWalkthrough(review, empty)).toBe(review)
1484
- expect(withWalkthrough(review.trimEnd(), empty)).toBe(review.trimEnd())
1485
- expect(withWalkthrough(review, { ...empty, notes: [{ title: "absent", body: "x" }] })).toBe(
1486
- review,
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("keeps Risk: out of a finding's chunk", () => {
1507
- expect(walkthroughBlock([{ title: "x", body: "y" }])).toContain(
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("rankedReview", () => {
1514
- const review = [
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("puts the ranked chunks first, each led by its tier, and leaves the rest in place", () => {
1538
- expect(
1539
- rankedReview(unranked(review), [
1540
- { title: "Token", label: "critical: security" },
1541
- { title: "Rename", label: "could" },
1542
- ]),
1543
- ).toBe(
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
- "# Review: abc1234",
1377
+ "# Quality",
1546
1378
  "",
1547
- "<!-- base: abc1234 -->",
1379
+ "## a",
1548
1380
  "",
1549
- "## Token",
1381
+ "_From lens tests._",
1550
1382
  "",
1551
- "_critical: security_",
1383
+ "Design.",
1552
1384
  "",
1553
- "Reads the token.",
1385
+ "- [ ] fix",
1554
1386
  "",
1555
- "- [ ] ./a.ts#2-2",
1387
+ "## b",
1556
1388
  "",
1557
- "## Rename",
1389
+ "_The fixer left it unresolved._",
1558
1390
  "",
1559
- "_could_",
1391
+ "Left over.",
1560
1392
  "",
1561
- "- [ ] ./a.ts#1-1",
1393
+ "- [ ] fix",
1562
1394
  "",
1563
- "## Fixed automatically",
1395
+ "# Dropped as noise",
1564
1396
  "",
1565
- "- x (abc1234)",
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("keeps a setext heading whole", () => {
1572
- expect(
1573
- rankedReview("# Review: a\n\nToken\n-----\n\n- [ ] ./a.ts#1\n", [
1574
- { title: "Token", label: "could" },
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
- it("drops a stale tier, so a re-review is ranked afresh", () => {
1596
- expect(unranked(review)).not.toContain("_could_")
1597
- expect(unranked(unranked(review))).toBe(unranked(review))
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