@intentic/sandbox-contract 1.224.0 → 1.226.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 (109) hide show
  1. package/README.md +14 -14
  2. package/dist/agent-catalog.d.ts +2 -0
  3. package/dist/agent-catalog.d.ts.map +1 -1
  4. package/dist/agent-catalog.js +19 -3
  5. package/dist/agent-catalog.js.map +1 -1
  6. package/dist/chores/chores.js +42 -42
  7. package/dist/chores/chores.js.map +1 -1
  8. package/dist/chores/extension-update.js +2 -2
  9. package/dist/chores/extension-update.js.map +1 -1
  10. package/dist/chores/fix-deps.js +1 -1
  11. package/dist/chores/fix-deps.js.map +1 -1
  12. package/dist/chores/probes.js +1 -1
  13. package/dist/chores/probes.js.map +1 -1
  14. package/dist/chores/verdict.js +2 -2
  15. package/dist/chores/verdict.js.map +1 -1
  16. package/dist/contracts/capabilities.contract.d.ts +41 -0
  17. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  18. package/dist/contracts/exit.contract.d.ts +80 -0
  19. package/dist/contracts/exit.contract.d.ts.map +1 -0
  20. package/dist/contracts/exit.contract.js +13 -0
  21. package/dist/contracts/exit.contract.js.map +1 -0
  22. package/dist/contracts/settings.contract.d.ts +12 -0
  23. package/dist/contracts/settings.contract.d.ts.map +1 -1
  24. package/dist/events.d.ts +4 -4
  25. package/dist/events.d.ts.map +1 -1
  26. package/dist/events.js +5 -5
  27. package/dist/events.js.map +1 -1
  28. package/dist/fast-tier.d.ts +9 -0
  29. package/dist/fast-tier.d.ts.map +1 -0
  30. package/dist/fast-tier.js +19 -0
  31. package/dist/fast-tier.js.map +1 -0
  32. package/dist/history-state.js +3 -3
  33. package/dist/history-state.js.map +1 -1
  34. package/dist/index.d.ts +203 -68
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +5 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/model-order.d.ts +1 -0
  39. package/dist/model-order.d.ts.map +1 -1
  40. package/dist/model-order.js +5 -0
  41. package/dist/model-order.js.map +1 -1
  42. package/dist/output-fields.d.ts.map +1 -1
  43. package/dist/output-fields.js +2 -2
  44. package/dist/output-fields.js.map +1 -1
  45. package/dist/prompt-complexity.d.ts +19 -0
  46. package/dist/prompt-complexity.d.ts.map +1 -0
  47. package/dist/prompt-complexity.js +83 -0
  48. package/dist/prompt-complexity.js.map +1 -0
  49. package/dist/publish-drafts.js +2 -2
  50. package/dist/publish-drafts.js.map +1 -1
  51. package/dist/schemas.d.ts +214 -0
  52. package/dist/schemas.d.ts.map +1 -1
  53. package/dist/schemas.js +113 -2
  54. package/dist/schemas.js.map +1 -1
  55. package/dist/workflow-faults.js +3 -3
  56. package/dist/workflow-faults.js.map +1 -1
  57. package/dist/workspace-state.d.ts +20 -20
  58. package/dist/workspace-state.d.ts.map +1 -1
  59. package/dist/workspace-state.js +20 -20
  60. package/dist/workspace-state.js.map +1 -1
  61. package/package.json +5 -5
  62. package/src/agent-catalog.test.ts +72 -27
  63. package/src/agent-catalog.ts +91 -3
  64. package/src/agent-run-model.test.ts +3 -3
  65. package/src/capability-ledger.test.ts +34 -15
  66. package/src/chores/chores.test.ts +1 -1
  67. package/src/chores/chores.ts +42 -42
  68. package/src/chores/digest.test.ts +1 -1
  69. package/src/chores/extension-update.ts +2 -2
  70. package/src/chores/fix-deps.ts +1 -1
  71. package/src/chores/probes.test.ts +6 -6
  72. package/src/chores/probes.ts +1 -1
  73. package/src/chores/stack.test.ts +3 -3
  74. package/src/chores/verdict.test.ts +20 -20
  75. package/src/chores/verdict.ts +2 -2
  76. package/src/contract-lock.test.ts +1 -1
  77. package/src/contracts/exit.contract.ts +42 -0
  78. package/src/contracts/providers.contract.ts +1 -1
  79. package/src/conversation-ids.ts +1 -1
  80. package/src/events.test.ts +3 -3
  81. package/src/events.ts +6 -6
  82. package/src/fast-tier.test.ts +88 -0
  83. package/src/fast-tier.ts +72 -0
  84. package/src/history-state.ts +3 -3
  85. package/src/hostnames.test.ts +1 -1
  86. package/src/index.ts +5 -0
  87. package/src/model-order.test.ts +11 -11
  88. package/src/model-order.ts +22 -0
  89. package/src/output-fields.ts +2 -2
  90. package/src/path-refs.test.ts +4 -4
  91. package/src/prompt-complexity.test.ts +160 -0
  92. package/src/prompt-complexity.ts +271 -0
  93. package/src/publish-drafts.ts +2 -2
  94. package/src/quick-model.test.ts +11 -11
  95. package/src/routes.test.ts +11 -5
  96. package/src/runtime-state.test.ts +1 -1
  97. package/src/schemas.test.ts +8 -8
  98. package/src/schemas.ts +311 -6
  99. package/src/search-globs.test.ts +2 -2
  100. package/src/share-paths.test.ts +1 -1
  101. package/src/title.test.ts +9 -9
  102. package/src/title.ts +1 -1
  103. package/src/tunnel-ids.test.ts +3 -3
  104. package/src/versions.test.ts +3 -3
  105. package/src/versions.ts +1 -1
  106. package/src/workflow-faults.test.ts +6 -6
  107. package/src/workflow-faults.ts +3 -3
  108. package/src/workspace-state.test.ts +32 -32
  109. package/src/workspace-state.ts +20 -20
@@ -237,7 +237,7 @@ const security: Chore = {
237
237
  .toSorted((left, right) => left.name.localeCompare(right.name))
238
238
  .map(
239
239
  (advisory) =>
240
- `${advisory.severity} · ${advisory.name} ${advisory.title}${advisory.patched === undefined ? ` (no patch yet)` : ``}`,
240
+ `${advisory.severity} · ${advisory.name}, ${advisory.title}${advisory.patched === undefined ? ` (no patch yet)` : ``}`,
241
241
  ),
242
242
  // Identities, not counts: every advisory that appears or is fixed is genuinely news, and there is no
243
243
  // ordinary drift here to absorb.
@@ -248,13 +248,13 @@ const security: Chore = {
248
248
  // would be reading a different tree by then.
249
249
  why:
250
250
  `pnpm audit reports ${plural(blocking.length, `high or critical advisory`, `high or critical advisories`)} against ` +
251
- `${repoLabel(context.repo)} ${production.length} reaching a production dependency path, ${patchable.length} with a published patched range: ` +
251
+ `${repoLabel(context.repo)}, ${production.length} reaching a production dependency path, ${patchable.length} with a published patched range: ` +
252
252
  `${blocking.map((advisory) => `${advisory.name} (${advisory.severity}${advisory.dev ? `, dev-only` : ``}${advisory.patched === undefined ? `, no patch` : `, fixed in ${advisory.patched}`})`).join(`; `)}.`,
253
253
  };
254
254
  },
255
255
  diagnosis: `An advisory with a published fix is a version bump someone has to actually make; one without is a risk to decide about.`,
256
256
  goal:
257
- `For each advisory, establish whether this workspace reaches the vulnerable code path at all a transitive dependency of a ` +
257
+ `For each advisory, establish whether this workspace reaches the vulnerable code path at all: a transitive dependency of a ` +
258
258
  `build-time tool is a different problem from one in a running service. Where the fix is a version bump the lockfile can absorb, ` +
259
259
  `make it. Where it needs a real upgrade or has no patch published, leave it and say what it would take. Never rewrite ` +
260
260
  `application code to route around a CVE.`,
@@ -307,7 +307,7 @@ const dependencies: Chore = {
307
307
  },
308
308
  diagnosis: `Version drift is cheap to fix continuously and expensive to fix in one go, because the majors start depending on each other.`,
309
309
  goal:
310
- `Take the patch and minor upgrades in one pass those are what the lockfile can absorb without argument. Then take the majors ` +
310
+ `Take the patch and minor upgrades in one pass: those are what the lockfile can absorb without argument. Then take the majors ` +
311
311
  `ONE AT A TIME, reading each one's changelog for breaking changes before you touch anything, and stop at the first one that ` +
312
312
  `needs more than a mechanical fix: leave it, and say what it would take. Do not batch majors; a failing test after eight of them ` +
313
313
  `is a bisect nobody wanted.`,
@@ -367,7 +367,7 @@ const deadCode: Chore = {
367
367
  },
368
368
  diagnosis: `Code nothing reaches still has to be read, type-checked and kept compiling by everyone who works nearby.`,
369
369
  goal:
370
- `Re-run knip yourself first this measurement is hours old and the tree has moved. Then check each finding against how the ` +
370
+ `Re-run knip yourself first: this measurement is hours old and the tree has moved. Then check each finding against how the ` +
371
371
  `file is actually used: knip is confidently wrong about anything reachable from OUTSIDE the repository, which means a package's ` +
372
372
  `public entry points, files a bundler or framework loads by convention, and types consumed only by a downstream package. Delete ` +
373
373
  `what is genuinely unreachable. Leave the false positives and list them in one line each, so the next run's reader knows they ` +
@@ -386,7 +386,7 @@ const duplication: Chore = {
386
386
  id: `duplication`,
387
387
  title: `Find duplication worth collapsing`,
388
388
  icon: `clone`,
389
- description: `Copy-paste that has grown past a fifth of a percent of the tree. Reports only extracting is a design call.`,
389
+ description: `Copy-paste that has grown past a fifth of a percent of the tree. Reports only, extracting is a design call.`,
390
390
  kind: `drifting`,
391
391
  criterion: `jscpd reports more than 5% of the scanned tree duplicated.`,
392
392
  stance: `report`,
@@ -421,11 +421,11 @@ const duplication: Chore = {
421
421
  `the largest are ${top.map((clone) => `${clone.first} ↔ ${clone.second} (${clone.lines} lines)`).join(`; `)}.`,
422
422
  };
423
423
  },
424
- diagnosis: `Duplication only costs anything when the copies have to change together and only some of it does.`,
424
+ diagnosis: `Duplication only costs anything when the copies have to change together, and only some of it does.`,
425
425
  goal:
426
426
  `Report the clones where the copies genuinely have to change together. For each: cite both file:line ranges, say what the shared ` +
427
427
  `concept actually is, and name where the extraction would live. Then say explicitly which of the reported clones you are NOT ` +
428
- `recommending against generated files, deliberately repetitive tests, and lookalikes owned by different subsystems so the ` +
428
+ `recommending against: generated files, deliberately repetitive tests, and lookalikes owned by different subsystems, so the ` +
429
429
  `next reader knows the list was triaged rather than truncated.`,
430
430
  done: `Done when every clone in the report has either a named extraction or a one-line reason it should stay.`,
431
431
  };
@@ -440,7 +440,7 @@ const documentation: Chore = {
440
440
  id: `documentation-refresh`,
441
441
  title: `Document what nothing explains`,
442
442
  icon: `file-edit`,
443
- description: `Packages in this repository with no README new ones first.`,
443
+ description: `Packages in this repository with no README, new ones first.`,
444
444
  kind: `drifting`,
445
445
  criterion: `A workspace package has no README.`,
446
446
  applies: (signals) => (signals.packages.length > 0 ? undefined : `not a workspace`),
@@ -464,7 +464,7 @@ const documentation: Chore = {
464
464
  },
465
465
  diagnosis: `A package nobody can read the shape of gets worked in by guesswork, and the guesses accumulate.`,
466
466
  goal:
467
- `Follow this workspace's own documentation conventions read them first, they are not optional and they are not generic. For ` +
467
+ `Follow this workspace's own documentation conventions: read them first, they are not optional and they are not generic. For ` +
468
468
  `each undocumented package, read the package before you write a word about it, and produce the document its conventions call ` +
469
469
  `for: what the package is FOR, how it fits the system, and which files matter. Explain at the module level. Never describe code ` +
470
470
  `line by line, and never document a package you did not read.`,
@@ -496,7 +496,7 @@ const complexity: Chore = {
496
496
  id: `complexity`,
497
497
  title: `Simplify what everything waits on`,
498
498
  icon: `wave-pulse`,
499
- description: `Files that both churn and carry the repository where edits are slow and ripple outward.`,
499
+ description: `Files that both churn and carry the repository, where edits are slow and ripple outward.`,
500
500
  kind: `accruing`,
501
501
  criterion: `A file in the hotspot ranking is also a key module, or its branching is three times the median of that ranking.`,
502
502
  stance: `act`,
@@ -520,7 +520,7 @@ const complexity: Chore = {
520
520
  keyModules.has(path) ? `churns and the rest of the repository imports it` : `${branches} branch points against a median of ${middle}`;
521
521
  return {
522
522
  headline: `${plural(found.length, `file`)} where every edit is slow and ripples outward`,
523
- detail: found.map((hotspot) => `${hotspot.path} ${hotspot.commits} commits, ${reason(hotspot.path, hotspot.complexity)}`),
523
+ detail: found.map((hotspot) => `${hotspot.path}, ${hotspot.commits} commits, ${reason(hotspot.path, hotspot.complexity)}`),
524
524
  digest: digestOf(...found.map((hotspot) => hotspot.path).toSorted()),
525
525
  severity: `info`,
526
526
  why:
@@ -530,10 +530,10 @@ const complexity: Chore = {
530
530
  },
531
531
  diagnosis: `A file that changes constantly and branches heavily makes every edit near it slow and easy to get wrong.`,
532
532
  goal:
533
- `Take ONE file the worst of them and no more. Read it first. If the rest of the repository imports it, separate the stable ` +
533
+ `Take ONE file: the worst of them, and no more. Read it first. If the rest of the repository imports it, separate the stable ` +
534
534
  `contract from the churn: a narrow surface for importers, the volatile implementation private behind it. If it is simply ` +
535
- `tangled, flatten it where it stands edge cases as early returns, compound conditions behind named predicates, long chains as ` +
536
- `lookups and extract a unit only if a cohesive one falls out. Behaviour stays identical, and no re-export shims are left behind.`,
535
+ `tangled, flatten it where it stands: edge cases as early returns, compound conditions behind named predicates, long chains as ` +
536
+ `lookups, and extract a unit only if a cohesive one falls out. Behaviour stays identical, and no re-export shims are left behind.`,
537
537
  done: `Done when \`iq hotspots\` reports materially fewer branch points for that file, the repository's checks pass, and no importer changed meaning.`,
538
538
  };
539
539
 
@@ -591,14 +591,14 @@ const runtime: Chore = {
591
591
  digest: digestOf(`node:${major}`, past ? `eol` : `approaching`),
592
592
  severity: past ? `warning` : `info`,
593
593
  why:
594
- `This sandbox runs ${context.node}, and Node ${major} ${past ? `reached end of life on ${eol}` : `reaches end of life on ${eol}`} ` +
594
+ `This sandbox runs ${context.node}, and Node ${major} ${past ? `reached end of life on ${eol}` : `reaches end of life on ${eol}`}, ` +
595
595
  `${plural(pinned.length, `package`)} in ${repoLabel(context.repo)} pin a node engine range.`,
596
596
  };
597
597
  },
598
598
  diagnosis: `An unsupported runtime stops receiving security patches, so every advisory against it stays open permanently.`,
599
599
  goal:
600
600
  `Establish what actually pins this runtime: the image's own base, the workspace's useNodeVersion, and each package's engines ` +
601
- `range. Propose the smallest move to a supported LTS which of those pins have to change, in what order, and what is likely to ` +
601
+ `range. Propose the smallest move to a supported LTS, which of those pins have to change, in what order, and what is likely to ` +
602
602
  `break at that boundary. Make the pin changes that are mechanical; do NOT attempt the image rebuild itself.`,
603
603
  done: `Done when the pins name a supported release, the repository's type-check and tests pass on it, and anything needing a rebuild is named as such.`,
604
604
  };
@@ -622,7 +622,7 @@ const libraries: Chore = {
622
622
  id: `library-overlap`,
623
623
  title: `Settle on one library per job`,
624
624
  icon: `box`,
625
- description: `Two dependencies solving the same problem both shipped, both maintained, one picked at random.`,
625
+ description: `Two dependencies solving the same problem, both shipped, both maintained, one picked at random.`,
626
626
  kind: `drifting`,
627
627
  criterion: `Two or more installed dependencies do the same job.`,
628
628
  applies: (signals) => (signals.packages.length > 0 ? undefined : `not a workspace`),
@@ -762,7 +762,7 @@ const bundleWeight: Chore = {
762
762
  severity: `info`,
763
763
  why:
764
764
  `The build output in ${dir}/ of ${repoLabel(context.repo)} is ${bytesLabel(totalGzip)} gzipped across ` +
765
- `${plural(assets.length, `asset`)}, and ${largest.path} alone is ${bytesLabel(largest.gzip)} of it ${Math.round(share)}%. ` +
765
+ `${plural(assets.length, `asset`)}, and ${largest.path} alone is ${bytesLabel(largest.gzip)} of it: ${Math.round(share)}%. ` +
766
766
  `The next largest are ${ranked
767
767
  .slice(1, 4)
768
768
  .map((asset) => `${asset.path} (${bytesLabel(asset.gzip)})`)
@@ -772,7 +772,7 @@ const bundleWeight: Chore = {
772
772
  },
773
773
  diagnosis: `Everything in the first chunk is downloaded and parsed before anything renders, whether or not the visitor needed it.`,
774
774
  goal:
775
- `Find out what is actually IN the dominant chunk before proposing anything the repository's own bundler can report this, and a ` +
775
+ `Find out what is actually IN the dominant chunk before proposing anything: the repository's own bundler can report this, and a ` +
776
776
  `recommendation made without it is guesswork. Then report the split worth making: which routes or features could load on demand, ` +
777
777
  `which dependencies are pulled in wholesale for one function, and which are only used behind an interaction nobody has yet had. ` +
778
778
  `Name the boundary for each and estimate what it saves. Where the chunk is genuinely all first-paint code, say so and close it.`,
@@ -842,8 +842,8 @@ const frameworkIdiom: Chore = {
842
842
  diagnosis: `A retired idiom keeps working until the major release that drops it, and then it is an emergency inside somebody else's upgrade.`,
843
843
  goal:
844
844
  `Take ONE idiom, the one with the most files, and no more. Convert the files where the conversion is mechanical and the behaviour ` +
845
- `is provably identical. Stop at the first file that needs a design decision a class component with genuine error-boundary ` +
846
- `semantics, an NgModule that something outside the repository imports leave it, and say what it would take. Do not convert an ` +
845
+ `is provably identical. Stop at the first file that needs a design decision: a class component with genuine error-boundary ` +
846
+ `semantics, an NgModule that something outside the repository imports: leave it, and say what it would take. Do not convert an ` +
847
847
  `idiom the repository has deliberately kept: if the newest code uses it too, that is a choice, and reporting it as one is the ` +
848
848
  `useful answer.`,
849
849
  done: `Done when a re-scan reports fewer files on that idiom, the repository's type-check and tests pass, and every file you skipped has a one-line reason.`,
@@ -868,7 +868,7 @@ const componentOverlap: Chore = {
868
868
  id: `component-overlap`,
869
869
  title: `Settle on one component per job`,
870
870
  icon: `copy`,
871
- description: `Components built twice the same name in two places, or the same logic under two names.`,
871
+ description: `Components built twice, the same name in two places, or the same logic under two names.`,
872
872
  kind: `drifting`,
873
873
  criterion: `Two component files reduce to the same name, or a duplicated block spans two components.`,
874
874
  applies: needsFramework,
@@ -932,12 +932,12 @@ const componentOverlap: Chore = {
932
932
  `${pairs.length === 0 ? `` : `The clones: ${pairs.map((clone) => `${normalizePath(clone.first)} ↔ ${normalizePath(clone.second)}, ${clone.lines} lines`).join(`; `)}.`}`,
933
933
  };
934
934
  },
935
- diagnosis: `A component built twice is maintained once whichever copy the next person happens to open is the one that gets the fix.`,
935
+ diagnosis: `A component built twice is maintained once, whichever copy the next person happens to open is the one that gets the fix.`,
936
936
  goal:
937
937
  `Read every file in each group before saying anything about it; a shared name is a reason to look, not a finding on its own. For ` +
938
938
  `each group, say whether these genuinely do the same job, and if they do, name the one to keep and count the call sites that would ` +
939
- `have to move. Where the answer is that the same LOGIC is duplicated rather than the whole component the same fetch and loading ` +
940
- `state, the same form validation, the same list virtualization written twice say so, and name the hook or composable it should ` +
939
+ `have to move. Where the answer is that the same LOGIC is duplicated rather than the whole component: the same fetch and loading ` +
940
+ `state, the same form validation, the same list virtualization written twice: say so, and name the hook or composable it should ` +
941
941
  `become and where it would live. Where two components share a name and nothing else, say that too and close it: a false family is ` +
942
942
  `worth one line, and the next reader needs to know it was considered.`,
943
943
  done: `Done when every group has either a component to keep with a call-site count, a shared unit to extract with a home, or a reason it is fine.`,
@@ -991,12 +991,12 @@ const tailwindBypass: Chore = {
991
991
  .join(`, `)}.`,
992
992
  };
993
993
  },
994
- diagnosis: `Every inline colour is a place the theme cannot reach a palette change lands everywhere except the files that opted out of it.`,
994
+ diagnosis: `Every inline colour is a place the theme cannot reach, a palette change lands everywhere except the files that opted out of it.`,
995
995
  goal:
996
- `Read the theme first the Tailwind config, or the CSS that defines the tokens so you know what the scale actually offers. Then ` +
996
+ `Read the theme first: the Tailwind config, or the CSS that defines the tokens, so you know what the scale actually offers. Then ` +
997
997
  `replace the values that have a token: an exact palette match, a spacing step, a type size. Where a value is CLOSE to a token but ` +
998
998
  `not equal, do not round it silently; that is a visual change wearing a refactor's clothes. List those separately with both values ` +
999
- `and let the owner decide. Where a value has no token and should a brand colour used in nine places say that the theme is ` +
999
+ `and let the owner decide. Where a value has no token and should: a brand colour used in nine places, say that the theme is ` +
1000
1000
  `missing an entry rather than editing nine files.`,
1001
1001
  done: `Done when a re-scan reports fewer hard-coded values, nothing renders differently, and every value you left has a one-line reason.`,
1002
1002
  };
@@ -1055,7 +1055,7 @@ const survey = ({ id, title, icon, description, diagnosis, goal, done, cadenceDa
1055
1055
  detail: [`Cadence · every ${cadenceDays} days`],
1056
1056
  digest: digestOf(id, `period:${Math.floor(context.nowMs / (cadenceDays * DAY_MS))}`),
1057
1057
  severity: `info`,
1058
- why: `This is a periodic review of ${repoLabel(context.repo)}, run every ${cadenceDays} days; nothing measured it it is due because it has been that long.`,
1058
+ why: `This is a periodic review of ${repoLabel(context.repo)}, run every ${cadenceDays} days; nothing measured it, it is due because it has been that long.`,
1059
1059
  }),
1060
1060
  diagnosis,
1061
1061
  goal,
@@ -1071,11 +1071,11 @@ const patterns = survey({
1071
1071
  id: `standardize-patterns`,
1072
1072
  title: `Standardize the cross-cutting patterns`,
1073
1073
  icon: `sitemap`,
1074
- description: `Error handling, validation, logging, configuration, retries, pagination the things every file does slightly differently.`,
1074
+ description: `Error handling, validation, logging, configuration, retries, pagination, the things every file does slightly differently.`,
1075
1075
  diagnosis: `Cross-cutting concerns drift one file at a time, and the cost only shows up when someone has to work across several of them.`,
1076
1076
  goal:
1077
- `Pick the cross-cutting concerns this repository actually has error handling, input validation, logging, configuration, retries, ` +
1078
- `pagination, serialization and for each, survey how it is done. Name the dominant pattern, the outliers, and which of the ` +
1077
+ `Pick the cross-cutting concerns this repository actually has: error handling, input validation, logging, configuration, retries, ` +
1078
+ `pagination, serialization, and for each, survey how it is done. Name the dominant pattern, the outliers, and which of the ` +
1079
1079
  `outliers are deliberate. Recommend ONE convention per concern with a file to point at as the reference implementation, and ` +
1080
1080
  `estimate the size of the conversion. Do not convert anything.`,
1081
1081
  done: `Done when each concern has a named convention, a reference file, and a count of the sites that diverge from it.`,
@@ -1093,7 +1093,7 @@ const deprecated = survey({
1093
1093
  diagnosis: `A deprecated API works right up until the upgrade that removes it, and then it is an emergency during someone else's migration.`,
1094
1094
  goal:
1095
1095
  `Survey what this repository uses that its own dependencies have deprecated: read the framework and runtime versions in use, check ` +
1096
- `their deprecation notices, and search for the call sites. Include the repository's OWN deprecations anything its code marks ` +
1096
+ `their deprecation notices, and search for the call sites. Include the repository's OWN deprecations: anything its code marks ` +
1097
1097
  `as deprecated and still calls. Rank by when each one actually breaks, not by how many call sites it has, and name the ` +
1098
1098
  `replacement for each. Change nothing.`,
1099
1099
  done: `Done when every deprecation has call sites cited, a replacement named, and the release it is expected to break in.`,
@@ -1114,12 +1114,12 @@ const documentationDrift = survey({
1114
1114
  id: `documentation-drift`,
1115
1115
  title: `Re-read the documentation against the code`,
1116
1116
  icon: `file`,
1117
- description: `Whether what the documents claim is still what the code does the drift no tool can measure.`,
1117
+ description: `Whether what the documents claim is still what the code does, the drift no tool can measure.`,
1118
1118
  diagnosis: `Documentation is trusted in proportion to how recently it was true, and a document that is quietly wrong is worse than a missing one.`,
1119
1119
  goal:
1120
1120
  `Read this repository's architecture documents against the code they describe. Report every claim that is no longer true, citing the ` +
1121
- `document line and the file that contradicts it. Prioritise the claims someone would ACT on where a subsystem lives, what owns ` +
1122
- `what, which file to change over prose that has merely aged. Do not rewrite the documents; produce the list of what is wrong.`,
1121
+ `document line and the file that contradicts it. Prioritise the claims someone would ACT on, where a subsystem lives, what owns ` +
1122
+ `what, which file to change: over prose that has merely aged. Do not rewrite the documents; produce the list of what is wrong.`,
1123
1123
  done: `Done when every architecture document has been read and every false claim is listed with both sides cited.`,
1124
1124
  cadenceDays: 90,
1125
1125
  applies: (signals) => (signals.shape.docs.length > 0 ? undefined : `no architecture documents`),
@@ -1141,7 +1141,7 @@ const pipelines = survey({
1141
1141
  `Read this repository's pipeline definitions and report what it pays for repeatedly: dependency installs with no cache key, ` +
1142
1142
  `build outputs recomputed between jobs, steps that are serial for no reason, and matrix legs that duplicate each other's work. ` +
1143
1143
  `For each, name the file and step, say roughly what it costs per run, and give the change that would fix it. Where a step is slow ` +
1144
- `because it genuinely has to be, say so a pipeline that is honestly expensive is not a finding.`,
1144
+ `because it genuinely has to be, say so: a pipeline that is honestly expensive is not a finding.`,
1145
1145
  done: `Done when every finding names a file, a step, and a concrete change, and anything deliberately slow is called out as such.`,
1146
1146
  cadenceDays: 90,
1147
1147
  applies: (signals) => (signals.shape.ci.length > 0 ? undefined : `no CI pipeline`),
@@ -1151,13 +1151,13 @@ const images = survey({
1151
1151
  id: `docker-image`,
1152
1152
  title: `Slim the container image`,
1153
1153
  icon: `box`,
1154
- description: `Layer order, build context and final size what ships in the image that did not need to.`,
1154
+ description: `Layer order, build context and final size, what ships in the image that did not need to.`,
1155
1155
  diagnosis: `Image size is paid on every pull and every cold start, and layer order decides how much of a build is cache hits.`,
1156
1156
  goal:
1157
1157
  `Read this repository's Dockerfiles and report what makes the image larger or the build slower than it needs to be: layers ordered ` +
1158
1158
  `so that a source edit invalidates the dependency install, build-time toolchains left in the final stage, a build context that ships ` +
1159
1159
  `the whole repository, and package caches never cleaned. For each, cite the file and line, and name the change. Do not rewrite the ` +
1160
- `Dockerfiles an image that fails to build is a much worse problem than one that is larger than ideal.`,
1160
+ `Dockerfiles: an image that fails to build is a much worse problem than one that is larger than ideal.`,
1161
1161
  done: `Done when every finding cites a Dockerfile line and names the change, with the ones that would need a base-image swap called out separately.`,
1162
1162
  cadenceDays: 90,
1163
1163
  applies: (signals) => (signals.shape.dockerfiles.length > 0 ? undefined : `no Dockerfile`),
@@ -1191,10 +1191,10 @@ export interface ChoreKindSpec {
1191
1191
  }
1192
1192
 
1193
1193
  export const CHORE_KINDS: readonly ChoreKindSpec[] = [
1194
- { kind: `carrying`, label: `Carrying`, caption: `a risk this repository is running today someone else decides when it becomes urgent` },
1194
+ { kind: `carrying`, label: `Carrying`, caption: `a risk this repository is running today, someone else decides when it becomes urgent` },
1195
1195
  { kind: `accruing`, label: `Accruing`, caption: `cheap now, expensive later, and always getting later` },
1196
1196
  { kind: `drifting`, label: `Drifting`, caption: `the shape of the thing is diverging from the idea of it` },
1197
- { kind: `surveying`, label: `Surveying`, caption: `periodic reads with nothing measuring them due because it has been that long` },
1197
+ { kind: `surveying`, label: `Surveying`, caption: `periodic reads with nothing measuring them, due because it has been that long` },
1198
1198
  ];
1199
1199
 
1200
1200
  // Declaration order, which decides nothing but the order WITHIN a kind, the sort below is stable, so the two
@@ -13,7 +13,7 @@ describe(`digestOf`, () => {
13
13
  });
14
14
  });
15
15
 
16
- /* The anti-drift mechanism. A chore that counts things must not mint a new digest and therefore a new badge
16
+ /* The anti-drift mechanism. A chore that counts things must not mint a new digest, and therefore a new badge:
17
17
  * every time an ordinary day's work moves the number by one. Buckets widen with the count, because the difference
18
18
  * between one and two matters and the difference between four hundred and five hundred does not. */
19
19
  describe(`bucketOf`, () => {
@@ -10,7 +10,7 @@ import { composeAsk } from "./prompt.js";
10
10
  * unprompted when the registry lists a new sha, so the owner opens a finished account instead of starting one. */
11
11
  const UPDATE_INVARIANTS =
12
12
  `This turn reads and reports; it changes nothing and installs nothing. Clone into a scratch directory ` +
13
- `outside the workspace and read the diff between the two commits the installed code was approved once ` +
13
+ `outside the workspace and read the diff between the two commits: the installed code was approved once ` +
14
14
  `already, so what is between them is the whole subject. Lead with the manifest's delta: any route added to ` +
15
15
  `\`permissions.sandbox\` is reach the owner never approved and the headline whatever else changed. Then the ` +
16
16
  `code: what behaviour changed, in the owner's terms, citing file and line.`;
@@ -33,5 +33,5 @@ export const updateBrief = ({ label, url, fromRef, toRef, path }: UpdateBrief):
33
33
  diagnosis: `The manifest (intentic-extension.json at the extension root) is the contract on both sides of the diff, so its delta is readable exactly like the code's.`,
34
34
  goal: `Read the diff and say what the update actually is: the manifest delta first, then what the code now does that it did not, and what it stopped doing.`,
35
35
  invariants: UPDATE_INVARIANTS,
36
- done: `Done when you end on a recommendation the owner can act on update, update and watch something named, or stay on ${fromRef.slice(0, 7)} with the change that decided it cited by file and line.`,
36
+ done: `Done when you end on a recommendation the owner can act on, update, update and watch something named, or stay on ${fromRef.slice(0, 7)}, with the change that decided it cited by file and line.`,
37
37
  });
@@ -28,7 +28,7 @@ export const FIX_DEPS_AUTOMATION = {
28
28
  "and they failed. The payload names the project (`deps.project`), the check command (`deps.command`), how it exited " +
29
29
  "(`deps.exitCode`) and the tail of its output (`deps.logTail`); the full log is in the project's `--verify` terminal.\n\n" +
30
30
  "Re-run the check yourself to see the failure first-hand, then fix the ROOT CAUSE. That usually means updating call " +
31
- "sites, types or tests to match what actually changed never loosening or deleting the checks, pinning or downgrading " +
31
+ "sites, types or tests to match what actually changed: never loosening or deleting the checks, pinning or downgrading " +
32
32
  "dependencies just to silence them, or editing generated files. If the breakage needs a decision only the owner can " +
33
33
  "make (an intentional breaking upgrade, a license change, a dependency that should be dropped), stop and say exactly " +
34
34
  "that instead of guessing.\n\n" +
@@ -3,7 +3,7 @@ import { probeSpec } from "./probes.js";
3
3
  import { IDIOM_RULES } from "./stack.js";
4
4
 
5
5
  /* The parsers are the part of this library that faces someone else's output, so they are tested the way that
6
- * output actually arrives: real shapes, then the shapes that have historically broken things a tool that
6
+ * output actually arrives: real shapes, then the shapes that have historically broken things, a tool that
7
7
  * printed a warning line before its JSON, a version whose fields moved, an empty run. The bar for every one of
8
8
  * them is the same: recognise it, or return undefined so the runner can record a failure. Never throw, and never
9
9
  * report a clean result from output it did not understand. */
@@ -135,7 +135,7 @@ describe(`knip`, () => {
135
135
  });
136
136
  });
137
137
 
138
- // Without an `issues` array this is not knip's report, whatever else it contains and reporting zero dead code
138
+ // Without an `issues` array this is not knip's report, whatever else it contains, and reporting zero dead code
139
139
  // from a shape we do not recognise is exactly the lie the state machine exists to prevent.
140
140
  test(`a shape without an issues array is a failure`, () => {
141
141
  expect(parse(`knip`, JSON.stringify({ files: [`src/old.ts`] }))).toBeUndefined();
@@ -233,8 +233,8 @@ describe(`ui`, () => {
233
233
  });
234
234
 
235
235
  /* Every path the sweep prints wears a `./`, because every ripgrep in it is handed `.` to walk. Downstream this
236
- * would have to be remembered at each comparison jscpd's paths against the component list, a bypass against
237
- * a component so it is spent once, here, and one spelling of a path leaves the parser. */
236
+ * would have to be remembered at each comparison: jscpd's paths against the component list, a bypass against
237
+ * a component, so it is spent once, here, and one spelling of a path leaves the parser. */
238
238
  test(`strips the prefix ripgrep prints for a path it was told to walk`, () => {
239
239
  expect(parse(`ui`, sweep(`COMPONENT\t./src/Button.vue`, `BYPASS\t./src/Button.vue:3`, `IDIOM\tvue-options-api\t./src/Old.vue`))).toEqual({
240
240
  id: `ui`,
@@ -257,7 +257,7 @@ describe(`ui`, () => {
257
257
  describe(`the sweep's composed command`, () => {
258
258
  const stages = (): string[] => probeSpec(`ui`).command.split(`; `);
259
259
 
260
- /* Given no path, ripgrep searches STDIN whenever stdin is not a TTY which is exactly how a probe is spawned.
260
+ /* Given no path, ripgrep searches STDIN whenever stdin is not a TTY, which is exactly how a probe is spawned.
261
261
  * The sweep exited 0, printed its marker and matched nothing, in every repository, forever, which the marker
262
262
  * line cannot catch because the sweep really did run. It reproduces from a child process and never from an
263
263
  * interactive shell, so the command is the only place it is visible. */
@@ -300,7 +300,7 @@ describe(`bundle`, () => {
300
300
  });
301
301
 
302
302
  // The `find` prints nothing for a directory that exists but holds no assets. `available` is supposed to catch
303
- // that, and this is the second line of defence a zero-byte bundle would otherwise read as a fact.
303
+ // that, and this is the second line of defence: a zero-byte bundle would otherwise read as a fact.
304
304
  test(`a directory line with no assets is an empty build, not a failure`, () => {
305
305
  expect(parse(`bundle`, `DIR\tbuild`)).toMatchObject({ bundle: { dir: `build`, assets: [], totalBytes: 0, totalGzip: 0 } });
306
306
  });
@@ -463,7 +463,7 @@ export const PROBES: readonly ProbeSpec[] = [
463
463
  available: `find ${BUILD_DIRS.join(` `)} -maxdepth 4 -type f \\( -name '*.js' -o -name '*.mjs' -o -name '*.css' \\) 2>/dev/null | head -n 1 | grep -q .`,
464
464
  // Says what is missing AND that this never builds, because the obvious reading of "no build output" is
465
465
  // that we tried and it failed. The owner running their own build once is the whole fix.
466
- unavailable: `no build output on disk this reads the last build, it never runs one`,
466
+ unavailable: `no build output on disk, this reads the last build, it never runs one`,
467
467
  command: bundleCommand(),
468
468
  parse: parseBundle,
469
469
  },
@@ -5,7 +5,7 @@ import { componentStem, frameworksOf, IDIOM_RULES, idiomRule, UI_FRAMEWORKS, use
5
5
  *
6
6
  * Most of what is below guards a failure that CANNOT be seen by reading the table: a pattern is interpolated into
7
7
  * a shell command that runs on someone else's machine at three in the morning, so a stray quote is not a typo
8
- * anyone reviews it is a probe that dies in a workspace nobody is watching, with a shell error for a reason. */
8
+ * anyone reviews: it is a probe that dies in a workspace nobody is watching, with a shell error for a reason. */
9
9
 
10
10
  describe(`the patterns are safe to interpolate`, () => {
11
11
  // The scan wraps every pattern and glob in shell single quotes. One apostrophe inside ends the quoting and
@@ -29,7 +29,7 @@ describe(`the patterns are safe to interpolate`, () => {
29
29
 
30
30
  /* Rust's regex crate has no lookaround, and getting it means ripgrep's -P, which is a compile-time option on
31
31
  * the box the sweep happens to run on. A rule that seems to need one is asking a question about the FILE
32
- * rather than about a line which is what `absent` is. */
32
+ * rather than about a line, which is what `absent` is. */
33
33
  test(`no pattern uses a lookaround`, () => {
34
34
  for (const rule of IDIOM_RULES) {
35
35
  expect(rule.pattern, rule.id).not.toMatch(/\(\?<?[=!]/);
@@ -109,7 +109,7 @@ describe(`the name two components share`, () => {
109
109
  });
110
110
 
111
111
  /* The trap in stripping a trailing number. `H1` and `H2` are different components and reduce to the same
112
- * single letter, so the stem is only accepted when what survives is still long enough to mean something
112
+ * single letter, so the stem is only accepted when what survives is still long enough to mean something:
113
113
  * otherwise the untouched name is kept and the two stay apart. */
114
114
  test(`short names keep their digits rather than collapsing together`, () => {
115
115
  expect(componentStem(`src/type/H1.tsx`)).toBe(`h1`);
@@ -20,7 +20,7 @@ const pkg = (over: Partial<ChorePackage> = {}): ChorePackage => ({
20
20
  ...over,
21
21
  });
22
22
 
23
- // A repository that is a Node workspace with documents, a pipeline, an image and a Tailwind front-end so every
23
+ // A repository that is a Node workspace with documents, a pipeline, an image and a Tailwind front-end, so every
24
24
  // chore APPLIES by default and each applicability test can turn off exactly the one fact it is about.
25
25
  const shape = (over: Partial<ChoreShape> = {}): ChoreShape => ({
26
26
  docs: [`docs/architecture/repo.md`],
@@ -56,7 +56,7 @@ const auditProbe = (names: readonly string[]): ProbeResult =>
56
56
  const report = (over: Partial<ChoresReport> = {}): ChoresReport => ({
57
57
  repos: [{ repo: `app`, probes: [], signals: signals() }],
58
58
  ledger: [],
59
- // Verdicts are about EVIDENCE, never about work in flight a probe running does not make a chore more or
59
+ // Verdicts are about EVIDENCE, never about work in flight: a probe running does not make a chore more or
60
60
  // less due, it only makes the panel say so. Empty here because no assertion in this file should depend on it.
61
61
  running: [],
62
62
  node: `v24.18.0`,
@@ -111,7 +111,7 @@ describe(`the ledger debounces; it cannot hide`, () => {
111
111
  ...over,
112
112
  });
113
113
 
114
- test(`a run against this exact evidence leaves the chore due but settled shown, never badged`, () => {
114
+ test(`a run against this exact evidence leaves the chore due but settled: shown, never badged`, () => {
115
115
  const verdict = verdictFor({ ...withAdvisories, ledger: [ledgerEntry()] }, `security-advisories`);
116
116
  expect(verdict.state).toBe(`due`);
117
117
  expect(verdict.settled).toBe(true);
@@ -134,7 +134,7 @@ describe(`the ledger debounces; it cannot hide`, () => {
134
134
  test(`an agent reporting the findings did not hold up clears the chore until the evidence changes`, () => {
135
135
  const verdict = verdictFor({ ...withAdvisories, ledger: [ledgerEntry({ outcome: `clean` })] }, `security-advisories`);
136
136
  expect(verdict.state).toBe(`clear`);
137
- expect(verdict.headline).toBe(`Checked the findings did not hold up`);
137
+ expect(verdict.headline).toBe(`Checked, the findings did not hold up`);
138
138
  });
139
139
 
140
140
  test(`a snooze silences a due chore without hiding it, and lapses on its own`, () => {
@@ -148,8 +148,8 @@ describe(`the ledger debounces; it cannot hide`, () => {
148
148
  });
149
149
 
150
150
  /* A chore with a cadence expires its own settlement, so "we looked and chose not to act" cannot silence it
151
- * for good. Security has no cadence on purpose an advisory does not become interesting again because
152
- * ninety days passed, it becomes interesting when the advisory set changes so its settlement persists. */
151
+ * for good. Security has no cadence on purpose: an advisory does not become interesting again because
152
+ * ninety days passed, it becomes interesting when the advisory set changes, so its settlement persists. */
153
153
  test(`settlement expires with the chore's cadence, and persists for the chores that have none`, () => {
154
154
  const dependencies = choreById(`dependencies-outdated`);
155
155
  expect(dependencies?.cadenceMs).toBeGreaterThan(0);
@@ -180,7 +180,7 @@ describe(`a measurement older than the work is not evidence about the work`, ()
180
180
  test(`a turn that landed after the measurement steps the chore down from due`, () => {
181
181
  const verdict = verdictFor({ ...withAdvisories, ledger: [ledgerEntry({ ranAt: NOW - 3_600_000 })] }, `security-advisories`);
182
182
  expect(verdict.state).toBe(`stale`);
183
- // The evidence stays on the row it is what the reader checks the claim against and the claim comes off.
183
+ // The evidence stays on the row: it is what the reader checks the claim against, and the claim comes off.
184
184
  expect(verdict.detail).not.toEqual([]);
185
185
  expect(verdict.settled).toBe(false);
186
186
  });
@@ -194,7 +194,7 @@ describe(`a measurement older than the work is not evidence about the work`, ()
194
194
  });
195
195
 
196
196
  // Re-measuring is the whole cure: the same run, against evidence taken after it, is settled rather than stale.
197
- test(`re-measuring after the turn restores the verdict due, and now genuinely settled`, () => {
197
+ test(`re-measuring after the turn restores the verdict: due, and now genuinely settled`, () => {
198
198
  const remeasured = report({
199
199
  repos: [{ repo: `app`, probes: [{ ...auditProbe([`left-pad`]), ranAt: NOW - 60_000 }], signals: signals() }],
200
200
  ledger: [ledgerEntry({ ranAt: NOW - 3_600_000 })],
@@ -223,7 +223,7 @@ describe(`a measurement older than the work is not evidence about the work`, ()
223
223
  // Every measured row carries when it was taken, so no row can pass off a week-old count as this morning's.
224
224
  test(`every measured verdict says when it was measured, and the unmeasurable ones say nothing`, () => {
225
225
  expect(verdictFor(withAdvisories, `security-advisories`).measuredAt).toBe(NOW - DAY);
226
- // A survey rests on no measurement it is decided by the calendar, and has nothing to be out of date.
226
+ // A survey rests on no measurement: it is decided by the calendar, and has nothing to be out of date.
227
227
  expect(verdictFor(report(), `standardize-patterns`).measuredAt).toBeUndefined();
228
228
  // Nor does a probe that never ran.
229
229
  expect(verdictFor(report(), `security-advisories`).measuredAt).toBeUndefined();
@@ -363,7 +363,7 @@ describe(`the prompts`, () => {
363
363
  const dueVerdict = () =>
364
364
  verdictFor(report({ repos: [{ repo: `app`, probes: [auditProbe([`left-pad`])], signals: signals() }] }), `security-advisories`);
365
365
 
366
- /* A prompt that counts without NAMING sends the agent off to re-derive a list we are already holding slowly,
366
+ /* A prompt that counts without NAMING sends the agent off to re-derive a list we are already holding: slowly,
367
367
  * and against a tree that has moved since. Every measured chore names its artefacts. */
368
368
  test(`name the artefacts, not just how many there were`, () => {
369
369
  const verdict = dueVerdict();
@@ -420,7 +420,7 @@ describe(`the prompts`, () => {
420
420
  expect(verdict.prompt, verdict.chore.id).toBeTypeOf(`string`);
421
421
  expect(verdict.digest, verdict.chore.id).not.toBe(``);
422
422
  }
423
- // Each measured chore's own artefact reaches its own prompt the regression this whole test exists for.
423
+ // Each measured chore's own artefact reaches its own prompt: the regression this whole test exists for.
424
424
  const promptFor = (chore: string) => due.find((verdict) => verdict.chore.id === chore)?.prompt ?? ``;
425
425
  expect(promptFor(`security-advisories`)).toContain(`left-pad`);
426
426
  expect(promptFor(`dependencies-outdated`)).toContain(`vue 1.0.0 → 2.0.0`);
@@ -432,7 +432,7 @@ describe(`the prompts`, () => {
432
432
  });
433
433
  });
434
434
 
435
- /* APPLICABILITY whether the chore is a QUESTION worth asking of this repository, as opposed to whether the
435
+ /* APPLICABILITY, whether the chore is a QUESTION worth asking of this repository, as opposed to whether the
436
436
  * answer is yes. Every case here is one where the previous design showed a row that could never be acted on:
437
437
  * an offer to re-read documentation that was never written, to slim an image that does not exist, to tighten a
438
438
  * pipeline nobody has. Each of those teaches the reader that this list was not written by someone who looked. */
@@ -468,7 +468,7 @@ describe(`what does not apply here`, () => {
468
468
  expect(verdictFor(single, `library-overlap`).state).toBe(`not-applicable`);
469
469
  });
470
470
 
471
- // A survey has no evidence to be absent "90 days have passed" is true everywhere so without a gate it
471
+ // A survey has no evidence to be absent: "90 days have passed" is true everywhere, so without a gate it
472
472
  // fires forever in repositories where its subject does not exist. This is the regression that motivated
473
473
  // making `applies` a required field on SurveySpec rather than an optional one.
474
474
  test(`a tiny repository is not surveyed for cross-cutting patterns it cannot have`, () => {
@@ -498,7 +498,7 @@ describe(`what does not apply here`, () => {
498
498
 
499
499
  /* THE CAUSES HAVE TO GROUP, and that is a fact about the STRINGS rather than about the gates. The scope strip
500
500
  * prints one line per distinct cause with the chores it costs listed beside it, so two gates that both mean
501
- * "there is no package.json here" and say it in different words print two lines and a workspace root, where
501
+ * "there is no package.json here" and say it in different words print two lines, and a workspace root, where
502
502
  * a dozen chores are ruled out by three facts, is back to the paragraph-per-chore wall this phrasing replaced.
503
503
  * Bounded rather than enumerated: a new gate may invent a new cause, it may not invent a new sentence. */
504
504
  test(`applicability reasons are bare causes, so the ones that mean the same thing group`, () => {
@@ -515,7 +515,7 @@ describe(`what does not apply here`, () => {
515
515
  });
516
516
  });
517
517
 
518
- /* THE FRONT-END CHORES. Four chores over two probes, tested where they decide something the share that makes a
518
+ /* THE FRONT-END CHORES. Four chores over two probes, tested where they decide something: the share that makes a
519
519
  * bundle a finding, the names that make two components one component, and above all the digests, because three of
520
520
  * these four measure things that move every time anyone writes a line of markup. */
521
521
  const uiProbe = (scan: Partial<UiScan> = {}): ProbeResult =>
@@ -601,8 +601,8 @@ describe(`idioms the framework has replaced`, () => {
601
601
  expect(verdict.detail).toEqual([`3 files · the Options API → <script setup> with the Composition API`]);
602
602
  });
603
603
 
604
- /* A migration in progress is a set that changes on every commit, so digesting the file identities which is
605
- * right for the documentation chore, whose set is packages would badge continuously through exactly the
604
+ /* A migration in progress is a set that changes on every commit, so digesting the file identities, which is
605
+ * right for the documentation chore, whose set is packages: would badge continuously through exactly the
606
606
  * period someone is doing the work. The bucketed count moves on real progress and not on daily churn. */
607
607
  test(`one more file in a large migration is not news`, () => {
608
608
  const before = verdictFor(withProbes([uiProbe({ idioms: [idioms(`vue-options-api`, 40)] })]), `framework-idiom`);
@@ -668,7 +668,7 @@ describe(`components built twice`, () => {
668
668
  });
669
669
 
670
670
  /* Half a measurement would let the row claim it looked for shared logic in a repository where jscpd has never
671
- * run the "measured and found nothing" lie the unavailable state exists to prevent. */
671
+ * run: the "measured and found nothing" lie the unavailable state exists to prevent. */
672
672
  test(`without the clone sweep the chore is unavailable, not clear`, () => {
673
673
  expect(verdictFor(withProbes([components(`src/Button.vue`, `src/ui/Button.vue`)]), `component-overlap`).state).toBe(`unavailable`);
674
674
  });
@@ -692,7 +692,7 @@ describe(`hard-coded styles`, () => {
692
692
  expect(verdict.detail[0]).toBe(`src/Checkout.vue · 11 values`);
693
693
  });
694
694
 
695
- // Tailwind gates this one alone a Vue repository with no Tailwind has no theme scale to have bypassed, and
695
+ // Tailwind gates this one alone: a Vue repository with no Tailwind has no theme scale to have bypassed, and
696
696
  // a row saying so would be the surface inventing a subject.
697
697
  test(`a repository without Tailwind is not asked the question at all`, () => {
698
698
  const verdict = verdictFor(
@@ -713,7 +713,7 @@ describe(`hard-coded styles`, () => {
713
713
  });
714
714
  });
715
715
 
716
- /* THE CRITERION the rule in words, next to the evidence that met it. A row that reports a number without the
716
+ /* THE CRITERION: the rule in words, next to the evidence that met it. A row that reports a number without the
717
717
  * rule behind it is asking to be taken on trust, and the first row that turns out to be wrong costs the whole
718
718
  * list its credibility. */
719
719
  describe(`every chore says what would make it due`, () => {
@@ -102,7 +102,7 @@ const unmeasuredDetail = (needs: readonly ProbeId[], probes: ReadonlyMap<ProbeId
102
102
  if (probe.state === `unavailable`) {
103
103
  return [`${spec.title} · ${probe.reason ?? `not available in this repository`}`];
104
104
  }
105
- return [`${spec.title} · failed${probe.reason === undefined ? `` : ` ${probe.reason}`}`];
105
+ return [`${spec.title} · failed${probe.reason === undefined ? `` : `, ${probe.reason}`}`];
106
106
  });
107
107
 
108
108
  export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLedgerEntry | undefined): ChoreVerdict => {
@@ -185,7 +185,7 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
185
185
  ...base,
186
186
  state: `clear`,
187
187
  severity: `info`,
188
- headline: `Checked the findings did not hold up`,
188
+ headline: `Checked, the findings did not hold up`,
189
189
  detail: finding.detail,
190
190
  digest: finding.digest,
191
191
  measuredAt,
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { expect, test } from "vitest";
3
3
  import { currentLock } from "./contract-lock.js";
4
4
 
5
- /* The committed lock and the code must say the same thing see contract-lock.ts for what the pair buys.
5
+ /* The committed lock and the code must say the same thing: see contract-lock.ts for what the pair buys.
6
6
  *
7
7
  * This is the HALF that runs everywhere the tests run; the other half (a shrunk lock needs a declared break)
8
8
  * lives in prepass.mjs, which has git and this suite does not. */