clearotron 0.4.0-beta.0 → 0.4.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/.env.example +9 -0
  2. package/INSTALL.md +4 -31
  3. package/README.md +5 -5
  4. package/bin/brandowner.mjs +1 -1
  5. package/bin/onboard.mjs +18 -74
  6. package/bin/start.mjs +58 -14
  7. package/bin/update.mjs +10 -2
  8. package/build-info.json +2 -2
  9. package/demo/full-country-search/run/_driver/search-policy.json +1 -1
  10. package/demo/full-country-search/run/status.json +1 -1
  11. package/demo/global-preliminary-search/run/_driver/search-policy.json +1 -1
  12. package/demo/global-preliminary-search/run/status.json +1 -1
  13. package/demo/knockout-search/run/_driver/search-policy.json +1 -1
  14. package/demo/knockout-search/run/status.json +1 -1
  15. package/demo/multi-country-focus-search/run/_driver/search-policy.json +1 -1
  16. package/demo/multi-country-focus-search/run/status.json +1 -1
  17. package/docs/README.md +1 -0
  18. package/docs/SECURITY-OWASP.md +298 -0
  19. package/docs/SECURITY.md +18 -13
  20. package/docs/architecture/02-architecture.md +5 -5
  21. package/docs/architecture/03-run-lifecycle.md +28 -29
  22. package/docs/architecture/04-configuration-reference.md +5 -10
  23. package/docs/architecture/05-config-governance.md +5 -7
  24. package/docs/architecture/06-operations-runbook.md +0 -12
  25. package/docs/architecture/07-quality-and-audit.md +12 -13
  26. package/docs/architecture/09-security-and-data.md +9 -4
  27. package/driver/CHANGELOG.md +70 -0
  28. package/driver/ask-ledger.mjs +4 -2
  29. package/driver/authority-trees.mjs +90 -8
  30. package/driver/band-shape.mjs +13 -13
  31. package/driver/blind-frame-model.mjs +1 -1
  32. package/driver/case-law-ledger.mjs +19 -3
  33. package/driver/claim-liveness.mjs +30 -1
  34. package/driver/clearance-variants-record.mjs +10 -3
  35. package/driver/common-law-receipts.mjs +64 -14
  36. package/driver/commonlaw-carry.mjs +2 -2
  37. package/driver/compare.mjs +2 -2
  38. package/driver/connotation-search.mjs +33 -104
  39. package/driver/contract-arm2-baseline.json +6 -5
  40. package/driver/contract-e3-backlog.mjs +62 -62
  41. package/driver/contract-vocabulary.mjs +38 -31
  42. package/driver/coverage-form-io.mjs +13 -1
  43. package/driver/coverage-form.mjs +42 -4
  44. package/driver/coverage-ledger.mjs +3 -5
  45. package/driver/cross-check-wait.mjs +43 -0
  46. package/driver/declination-call.mjs +34 -10
  47. package/driver/declination-tool.mjs +27 -15
  48. package/driver/deferral-row.mjs +139 -0
  49. package/driver/degraded-parts.mjs +377 -0
  50. package/driver/deliver-trigger.sh +1 -1
  51. package/driver/demo-container.mjs +4 -1
  52. package/driver/dev-portal.mjs +5 -5
  53. package/driver/dispatch-record.mjs +2 -2
  54. package/driver/driver.config.mjs +193 -96
  55. package/driver/e2e/README.md +1 -1
  56. package/driver/effective-scope.mjs +58 -2
  57. package/driver/engine/anthropic-agent.mjs +103 -27
  58. package/driver/engine/auth.mjs +2 -2
  59. package/driver/engine/child-record.mjs +11 -0
  60. package/driver/engine/cli-version.mjs +4 -3
  61. package/driver/engine/common.mjs +17 -2
  62. package/driver/engine/deny-authority-write.mjs +3 -1
  63. package/driver/engine/engine-env.mjs +174 -0
  64. package/driver/engine/engine-spawn.mjs +179 -0
  65. package/driver/engine/mcp/band-server.mjs +1 -1
  66. package/driver/engine/mcp/clarivate-server.mjs +2 -1
  67. package/driver/engine/mcp/codex-config.mjs +112 -3
  68. package/driver/engine/mcp/corsearch-server.mjs +1 -0
  69. package/driver/engine/mcp/declination-server.mjs +9 -8
  70. package/driver/engine/mcp/dispositions-server.mjs +2 -1
  71. package/driver/engine/mcp/euipo-server.mjs +1 -0
  72. package/driver/engine/mcp/fetch-server.mjs +14 -11
  73. package/driver/engine/mcp/free-tier-server.mjs +1 -1
  74. package/driver/engine/mcp/gather-config.mjs +23 -11
  75. package/driver/engine/mcp/perplexity-server.mjs +97 -17
  76. package/driver/engine/mcp/probe-server.mjs +43 -21
  77. package/driver/engine/mcp/public-fetch.mjs +195 -0
  78. package/driver/engine/mcp/recording-server.mjs +79 -14
  79. package/driver/engine/mcp/signa-server.mjs +1 -0
  80. package/driver/engine/mcp/supplemental.mjs +14 -14
  81. package/driver/engine/mcp/unit-note-server.mjs +50 -1
  82. package/driver/engine/mcp/uspto-local-server.mjs +1 -0
  83. package/driver/engine/openai-agent.mjs +109 -28
  84. package/driver/engine/probe.mjs +152 -19
  85. package/driver/enqueue-schema.mjs +5 -5
  86. package/driver/feedback-issues.mjs +1 -1
  87. package/driver/feedback-store.mjs +1 -1
  88. package/driver/findings-model.mjs +67 -16
  89. package/driver/flag-snapshot.mjs +1 -1
  90. package/driver/floor-duty.mjs +23 -7
  91. package/driver/form-neighbourhood.mjs +52 -17
  92. package/driver/frame-diff-model.mjs +3 -3
  93. package/driver/framework-method.mjs +304 -0
  94. package/driver/gateway.mjs +49 -21
  95. package/driver/hand-off-exits.mjs +129 -0
  96. package/driver/jx-lanes.mjs +1 -1
  97. package/driver/jx.mjs +8 -5
  98. package/driver/knockout-assess-record.mjs +7 -1
  99. package/driver/knockout-frame-record.mjs +96 -34
  100. package/driver/knockout-review-record.mjs +7 -2
  101. package/driver/log.mjs +2 -2
  102. package/driver/matter-frame-record.mjs +31 -2
  103. package/driver/methodology-witness.mjs +6 -3
  104. package/driver/named-band.mjs +1 -1
  105. package/driver/owner-use-check.mjs +45 -10
  106. package/driver/package.json +1 -1
  107. package/driver/partial-payload-baseline.json +4 -0
  108. package/driver/phase0.mjs +2 -2
  109. package/driver/pipeline-knockout.mjs +256 -129
  110. package/driver/pipeline.mjs +417 -614
  111. package/driver/placement-carry.mjs +22 -3
  112. package/driver/placement-form-io.mjs +18 -4
  113. package/driver/placement-form.mjs +53 -4
  114. package/driver/placement-union.mjs +32 -5
  115. package/driver/portal-report.mjs +3 -3
  116. package/driver/portal-service.mjs +6 -5
  117. package/driver/portal-static.mjs +11 -18
  118. package/driver/predelivery-lint.mjs +10 -10
  119. package/driver/profile-page.html +5 -5
  120. package/driver/profiles.mjs +5 -5
  121. package/driver/progress.mjs +6 -4
  122. package/driver/provider-usage.mjs +25 -2
  123. package/driver/publish/index.mjs +97 -25
  124. package/driver/publish/knockout.mjs +136 -14
  125. package/driver/publish/profiles-page.mjs +2 -0
  126. package/driver/publish/publish-inputs.mjs +10 -4
  127. package/driver/publish/render-knockout.mjs +48 -21
  128. package/driver/publish/render.mjs +13 -8
  129. package/driver/publish/report-data.mjs +2 -0
  130. package/driver/publish/search-depth.mjs +77 -4
  131. package/driver/publish/templates/report.css +1 -1
  132. package/driver/publish/xlsx.mjs +34 -15
  133. package/driver/reasoning-tripwires.mjs +2 -160
  134. package/driver/recall-receipt.mjs +27 -0
  135. package/driver/recall-reconciliation.mjs +1 -1
  136. package/driver/record-carry.mjs +14 -14
  137. package/driver/recording-agreement.mjs +2 -2
  138. package/driver/reference-score.mjs +169 -47
  139. package/driver/register-count.mjs +61 -4
  140. package/driver/register-digest-record.mjs +14 -1
  141. package/driver/register-plan.mjs +161 -80
  142. package/driver/register-records.mjs +37 -4
  143. package/driver/registration-scripts.mjs +22 -0
  144. package/driver/registry-fidelity.mjs +4 -4
  145. package/driver/repair-composers.mjs +23 -7
  146. package/driver/repair-contract.mjs +1 -1
  147. package/driver/repairs.mjs +5 -2
  148. package/driver/reviewer-open-points.mjs +33 -0
  149. package/driver/rule-shape.mjs +8 -8
  150. package/driver/run-economics.mjs +3 -3
  151. package/driver/run-integrity.mjs +2 -2
  152. package/driver/runner.mjs +17 -5
  153. package/driver/scope-facts.mjs +89 -12
  154. package/driver/score-redaction.mjs +247 -0
  155. package/driver/screen-gate.mjs +1 -1
  156. package/driver/search-policy.mjs +17 -0
  157. package/driver/skills/README.md +2 -2
  158. package/driver/skills/blind-frame/SKILL.md +2 -2
  159. package/driver/skills/clearance-common-law/SKILL.md +29 -36
  160. package/driver/skills/clearance-common-law/perplexity-prompts.md +8 -30
  161. package/driver/skills/clearance-register/SKILL.md +15 -16
  162. package/driver/skills/clearance-register/digest.md +15 -31
  163. package/driver/skills/clearance-register/register-recipes.md +14 -56
  164. package/driver/skills/clearance-register/unit.md +37 -48
  165. package/driver/skills/clearance-search/SKILL.md +6 -8
  166. package/driver/skills/clearance-search/delivery-contract.md +0 -7
  167. package/driver/skills/clearance-search/firm-wide-reasoning.md +5 -4
  168. package/driver/skills/clearance-search/phase2-execution.md +5 -6
  169. package/driver/skills/clearance-search/report-prose.md +5 -5
  170. package/driver/skills/clearance-search/risk-framework-triage.md +2 -2
  171. package/driver/skills/clearance-search/synthesis-rules.md +9 -9
  172. package/driver/skills/clearance-search/template-formatting.md +2 -2
  173. package/driver/skills/clearance-variants/SKILL.md +8 -8
  174. package/driver/skills/clearance-variants/transliteration-scripts.md +2 -2
  175. package/driver/skills/frame-diff/SKILL.md +2 -2
  176. package/driver/skills/knockout-assess/SKILL.md +40 -10
  177. package/driver/skills/knockout-frame/SKILL.md +42 -10
  178. package/driver/skills/matter-frame/SKILL.md +12 -5
  179. package/driver/skills/matter-frame/watchlist-reference.md +1 -1
  180. package/driver/skills/narrative-refutation/SKILL.md +5 -3
  181. package/driver/skills/placement-inquiry/SKILL.md +5 -1
  182. package/driver/stage-context.mjs +27 -6
  183. package/driver/stages-knockout.mjs +92 -79
  184. package/driver/stages.mjs +111 -68
  185. package/driver/stray-artifacts.mjs +9 -5
  186. package/driver/suite-census.json +696 -150
  187. package/driver/synthesis-record.mjs +26 -4
  188. package/driver/systemd/README.md +5 -6
  189. package/driver/systemd/clearotron-client-mcp.service +24 -0
  190. package/driver/systemd/clearotron-mcp-face.service +24 -0
  191. package/driver/systemd/clearotron-portal.service +24 -0
  192. package/driver/systemd/clearotron-worker.service +29 -0
  193. package/driver/tokens.mjs +10 -9
  194. package/driver/turnaround-bands.mjs +1 -1
  195. package/driver/unit-inventory.mjs +24 -43
  196. package/driver/variant-manifest-model.mjs +22 -3
  197. package/driver/verify-knockout.mjs +163 -4
  198. package/driver/verify.mjs +42 -16
  199. package/driver/web-grid.mjs +150 -0
  200. package/driver/whatif-memo-run.mjs +1 -1
  201. package/driver/withheld-families.mjs +113 -4
  202. package/driver/worker-heartbeat.mjs +13 -2
  203. package/mcp-server/CHANGELOG.md +8 -0
  204. package/mcp-server/README.md +1 -1
  205. package/mcp-server/lib/knockout.mjs +1 -1
  206. package/mcp-server/lib/ops.mjs +10 -10
  207. package/mcp-server/lib/scrub.mjs +1 -1
  208. package/mcp-server/lib/trace.mjs +3 -2
  209. package/mcp-server/package.json +1 -1
  210. package/mcp-server/server.mjs +3 -3
  211. package/package.json +1 -1
  212. package/portal-ui/dist/assets/{index-DVtz44vH.js → index-BPAUjcI0.js} +3 -2
  213. package/portal-ui/dist/assets/{index-5CCwiJG7.css → index-D2wrw9cH.css} +18 -2
  214. package/portal-ui/dist/assets/plus-jakarta-sans-BUCHxqJ-.woff2 +0 -0
  215. package/portal-ui/dist/index.html +2 -15
  216. package/portal-ui/package.json +1 -1
  217. package/providers/_shared/README.md +1 -1
  218. package/providers/_shared/answer-memory.mjs +199 -0
  219. package/providers/_shared/enumerate.mjs +155 -50
  220. package/providers/_shared/execute-plan.mjs +62 -16
  221. package/providers/_shared/ledger-path.mjs +1 -1
  222. package/providers/_shared/ledger.mjs +48 -6
  223. package/providers/_shared/plan-guards.mjs +7 -0
  224. package/providers/_shared/script-form.mjs +24 -5
  225. package/providers/_shared/term-shape.mjs +5 -5
  226. package/providers/clarivate/src/capabilities.js +11 -0
  227. package/providers/clarivate/src/core.js +193 -18
  228. package/providers/corsearch/README.md +1 -2
  229. package/providers/corsearch/src/core.js +2 -2
  230. package/providers/jx-subclass/lookup.mjs +1 -1
  231. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  232. package/providers/oauth-mcp-bridge/README.md +8 -7
  233. package/providers/oauth-mcp-bridge/bridge.mjs +21 -5
  234. package/providers/oauth-mcp-bridge/package.json +1 -1
  235. package/providers/oauth-mcp-bridge/systemd/courtlistener-mcp.service +1 -1
  236. package/providers/oauth-mcp-bridge/warm-server.mjs +16 -5
  237. package/providers/perplexity/src/core.js +246 -21
  238. package/providers/signa/src/capabilities.js +24 -0
  239. package/providers/signa/src/core.js +93 -25
  240. package/scripts/README.md +2 -1
  241. package/scripts/added-reference-check.mjs +2 -1
  242. package/scripts/backup-recall-stores.mjs +5 -5
  243. package/scripts/demo-evidence.mjs +2 -1
  244. package/scripts/deprecate-below.mjs +114 -2
  245. package/scripts/drive-env-check.mjs +2 -1
  246. package/scripts/e2e-first-time.mjs +469 -0
  247. package/scripts/e2e-scenario-ops.mjs +441 -0
  248. package/scripts/e2e.mjs +104 -16
  249. package/scripts/env-audit.mjs +8 -0
  250. package/scripts/freeze-example-run.mjs +7 -7
  251. package/scripts/generated-files-are-current.mjs +2 -1
  252. package/scripts/hand-off-exits-probe.mjs +75 -0
  253. package/scripts/import-cycle-check.mjs +2 -1
  254. package/scripts/merge-shape-check.mjs +2 -1
  255. package/scripts/package-size-budget.mjs +2 -1
  256. package/scripts/record-carry-probe.mjs +1 -1
  257. package/scripts/release-entry-catch-up.mjs +77 -8
  258. package/scripts/release-install-check.mjs +4 -1
  259. package/scripts/release-note-required.mjs +139 -9
  260. package/scripts/release-rehearsal-version.mjs +60 -0
  261. package/scripts/release-sbom.mjs +104 -0
  262. package/scripts/release-visible-check.mjs +7 -5
  263. package/scripts/render-check.mjs +5 -2
  264. package/scripts/report-offline-render-check.mjs +169 -0
  265. package/scripts/score.mjs +79 -8
  266. package/scripts/strip-titles-and-attributions.mjs +2 -1
  267. package/scripts/strip-tracker-citations.mjs +2 -1
  268. package/scripts/test-full.mjs +2 -1
  269. package/scripts/test-run.mjs +10 -3
  270. package/scripts/third-party-notices.mjs +3 -1
  271. package/scripts/travelling-predicates.mjs +1 -1
  272. package/scripts/writing-standard-check.mjs +2 -1
  273. package/shared/brand-fonts.mjs +59 -0
  274. package/shared/brand.mjs +6 -6
  275. package/shared/browser-temp-root.mjs +3 -2
  276. package/shared/doctrine-overlay.mjs +1 -1
  277. package/shared/driver-dir.mjs +47 -11
  278. package/shared/fonts/OFL-fira-code.txt +93 -0
  279. package/shared/fonts/OFL-plus-jakarta-sans.txt +93 -0
  280. package/shared/fonts/README.md +37 -0
  281. package/shared/fonts/fira-code.woff2 +0 -0
  282. package/shared/fonts/plus-jakarta-sans.woff2 +0 -0
  283. package/shared/names-in-force.mjs +2 -0
  284. package/shared/npm-cli.mjs +23 -0
  285. package/shared/os-advice.mjs +14 -2
  286. package/shared/path-seps.mjs +42 -0
  287. package/shared/process-table.mjs +71 -3
  288. package/shared/reference-guard-classes.mjs +30 -6
  289. package/shared/root-doc-commands.mjs +6 -2
  290. package/shared/running-start.mjs +24 -7
  291. package/shared/scope.mjs +2 -2
  292. package/shared/wsl.mjs +1 -1
  293. package/driver/known-conflicts.mjs +0 -327
@@ -44,6 +44,7 @@
44
44
  // "what a public tree must not acquire" is one definition and one imitation of it, and the imitation
45
45
  // is whichever the reader did not run.
46
46
  import { execFileSync } from "node:child_process";
47
+ import { isEntrypoint } from "../shared/is-entrypoint.mjs";
47
48
  import {
48
49
  CLASSES, offendingClasses, isProse, isScannable,
49
50
  withoutColourValues, withoutLinkTargets,
@@ -110,4 +111,4 @@ function main() {
110
111
  process.exit(1);
111
112
  }
112
113
 
113
- if (import.meta.url === `file://${process.argv[1]}`) main();
114
+ if (isEntrypoint(import.meta.url)) main();
@@ -4,11 +4,11 @@
4
4
  // Copy every agent's recall store into the archive pool, so the engine's memory of confirmed
5
5
  // conflicts survives the loss of a workspace.
6
6
  //
7
- // WHY THIS EXISTS. `<studioRoot>/_known-conflicts/<mark>.json` is what stops the engine re-litigating
8
- // a conflict a lawyer already confirmed, and what carries a remembered opposition window into the
9
- // deadline-carry clamp. It lives only in the live workspace: no code copies it anywhere, and the
10
- // agent-state git backup does not reach it. One lost disk and every prior confirmed conflict is
11
- // forgotten silently — the next run on that mark reads clean and nothing says a memory used to exist.
7
+ // WHY THIS EXISTS. `<studioRoot>/_known-conflicts/<mark>.json` was the engine's memory of conflicts
8
+ // its delivered reports confirmed. No run reads or writes it since the recall store was removed
9
+ // (2026-09-24), and the files were left on disk untouched, so what they hold is now a record rather
10
+ // than an input. It lives only in the live workspace: no code copies it anywhere, and the agent-state
11
+ // git backup does not reach it. One lost disk and that record is gone with nothing saying it existed.
12
12
  //
13
13
  // WHY THE POOL AND NOT GIT. These files name real client matters. The agent-state repo is pushed to
14
14
  // GitHub nightly, so backing them up THERE would widen exactly the exposure the 2026-07 audit found
@@ -22,6 +22,7 @@ import { execFileSync } from "node:child_process";
22
22
  import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from "node:fs";
23
23
  import { dirname, join } from "node:path";
24
24
  import { fileURLToPath } from "node:url";
25
+ import { isEntrypoint } from "../shared/is-entrypoint.mjs";
25
26
 
26
27
  const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
27
28
  export const MANIFEST = join(ROOT, "demo", "MANIFEST.json");
@@ -53,7 +54,7 @@ export function mixedPaths(files) {
53
54
  return files.filter((f) => !f.startsWith("demo/"));
54
55
  }
55
56
 
56
- if (import.meta.url === `file://${process.argv[1]}`) {
57
+ if (isEntrypoint(import.meta.url)) {
57
58
  const apply = process.argv.includes("--apply");
58
59
 
59
60
  if (apply) {
@@ -2,9 +2,11 @@
2
2
  // SPDX-License-Identifier: AGPL-3.0-only
3
3
  // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
4
4
  //
5
- // deprecate-below.mjs — put one warning on every published version below a named stable.
5
+ // deprecate-below.mjs — put one warning on every published version below a named stable, or on a named
6
+ // range of published versions.
6
7
  //
7
8
  // node scripts/deprecate-below.mjs --below 0.3.1 [--dry-run]
9
+ // node scripts/deprecate-below.mjs --range "0.2.0..0.4.0-beta.0 except 0.3.3" [--dry-run]
8
10
  //
9
11
  // ── WHY THIS IS A SCRIPT IN CI AND NOT SOMETHING A PERSON RUNS ──────────────────────────────────────
10
12
  //
@@ -70,6 +72,56 @@ export function compareVersions(a, b) {
70
72
  return 0;
71
73
  }
72
74
 
75
+ // ── THE RANGE MODE: A NAMED SET OF VERSIONS, ONE SENTENCE, NO REASON GIVEN ──────────────────────────
76
+ //
77
+ // Some versions have to be warned that are not simply "everything below a stable": a stretch of betas
78
+ // and stables with one version held out because an install still runs it. The range says exactly that,
79
+ // inclusive at both ends in npm's own order, and the exceptions are named one by one.
80
+ //
81
+ // ITS SENTENCE IS FIXED HERE AND NOT TYPED AT DISPATCH. It is public text on every version page, so it
82
+ // is the owner's sentence, approved word for word, and a dispatch box is no place to redraft it.
83
+ //
84
+ // IT REPLACES AN OLDER MESSAGE IN THE RANGE, unlike the mode above. A range is warned for one reason, so
85
+ // every version in it reads the same; a version that already says exactly this is left alone.
86
+ //
87
+ // IT NEVER TOUCHES A VERSION A DIST-TAG POINTS AT. `latest` and `beta` are what `npm install` resolves
88
+ // to, and a warning on the version a reader is told to install is worse than none.
89
+ export const RANGE_MESSAGE = "This version is no longer supported. Please upgrade to the latest version of clearotron.";
90
+
91
+ const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?$/;
92
+
93
+ /** Read "<from>..<through>[ except <v>[,<v>…]]". Throws on anything else. */
94
+ export function parseRangeSpec(spec) {
95
+ const m = String(spec ?? "").trim().match(/^(\S+)\.\.(\S+?)(?:\s+except\s+(\S+))?$/);
96
+ if (!m) throw new Error(`"${spec}" is not a range: write <from>..<through>, optionally followed by " except <v>,<v>"`);
97
+ const [, from, through, ex = ""] = m;
98
+ const except = ex ? ex.split(",").map((v) => v.trim()).filter(Boolean) : [];
99
+ for (const v of [from, through, ...except]) if (!VERSION.test(v)) throw new Error(`"${v}" is not a version`);
100
+ if (compareVersions(from, through) > 0) throw new Error(`${from} is above ${through}: the range is empty`);
101
+ return { from, through, except };
102
+ }
103
+
104
+ /**
105
+ * The published versions a range names, in order: from and through inclusive, the exceptions out.
106
+ * Throws, before anything is written, when a bound is not published, an exception lies outside the
107
+ * range or is not published, or a selected version is one a dist-tag points at.
108
+ */
109
+ export function selectRange(all, { from, through, except }, distTags = {}) {
110
+ for (const b of [from, through]) if (!all.includes(b)) throw new Error(`${b} is not a published version of ${PKG}`);
111
+ const inRange = (v) => compareVersions(v, from) >= 0 && compareVersions(v, through) <= 0;
112
+ for (const e of except) {
113
+ if (!all.includes(e)) throw new Error(`the exception ${e} is not a published version of ${PKG}`);
114
+ if (!inRange(e)) throw new Error(`the exception ${e} lies outside ${from}..${through}`);
115
+ }
116
+ const picked = all.filter((v) => inRange(v) && !except.includes(v)).sort(compareVersions);
117
+ const tagged = Object.entries(distTags).filter(([, v]) => picked.includes(v));
118
+ if (tagged.length) {
119
+ throw new Error(`the range takes ${tagged.map(([t, v]) => `${v} (${t})`).join(", ")}, which a dist-tag points at; `
120
+ + "move the tag or hold the version out");
121
+ }
122
+ return picked;
123
+ }
124
+
73
125
  const npm = (...args) => execFileSync("npm", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
74
126
 
75
127
  /** Every published version, and the deprecation message each carries. Read once, as a list. */
@@ -130,9 +182,11 @@ async function main() {
130
182
  const argv = process.argv.slice(2);
131
183
  const at = (flag) => { const i = argv.indexOf(flag); return i >= 0 ? argv[i + 1] : null; };
132
184
  const DRY = argv.includes("--dry-run");
185
+ const range = at("--range");
186
+ if (range) return deprecateRange(range, DRY);
133
187
  const below = at("--below");
134
188
  if (!below) {
135
- console.error("deprecate-below: --below <version> is required — the stable everything under it points at.");
189
+ console.error("deprecate-below: --below <version> or --range \"<from>..<through> except <v>\" is required.");
136
190
  return 2;
137
191
  }
138
192
  const MESSAGE = `This version is superseded. Please upgrade to ${below} or later: npm install ${PKG}@${below}`;
@@ -201,4 +255,62 @@ async function main() {
201
255
  return failed ? 1 : 0;
202
256
  }
203
257
 
258
+ /** The range mode's run: select, write the fixed sentence where it is not already, read each back. */
259
+ async function deprecateRange(spec, DRY) {
260
+ let picked;
261
+ try {
262
+ const parsed = parseRangeSpec(spec);
263
+ const all = published();
264
+ if (!all.length) throw new Error("the registry listed no versions");
265
+ const distTags = JSON.parse(npm("view", PKG, "dist-tags", "--json"));
266
+ picked = selectRange(all, parsed, distTags);
267
+ console.log(`deprecate-below: ${all.length} published version(s); ${picked.length} in ${spec}.`);
268
+ } catch (e) {
269
+ console.error(`deprecate-below: refusing — ${e.message}. Nothing was written.`);
270
+ return 2;
271
+ }
272
+ let done = 0, skipped = 0, failed = 0;
273
+ const wrote = [];
274
+ for (const v of picked) {
275
+ let existing;
276
+ try { existing = deprecationOf(v); }
277
+ catch (e) {
278
+ console.error(` ${v}: could NOT be read (${String(e.message).split("\n")[0]}) — left alone.`);
279
+ failed += 1;
280
+ continue;
281
+ }
282
+ if (existing === RANGE_MESSAGE) { console.log(` ${v}: already carries this message`); skipped += 1; continue; }
283
+ const was = existing ? ` (replacing "${existing.slice(0, 60)}")` : "";
284
+ if (DRY) { console.log(` ${v}: would deprecate${was}`); done += 1; continue; }
285
+ try {
286
+ npm("deprecate", `${PKG}@${v}`, RANGE_MESSAGE);
287
+ } catch (e) {
288
+ console.error(` ${v}: deprecate FAILED — ${String(e.stderr ?? e.message).split("\n")[0]}`);
289
+ failed += 1;
290
+ continue;
291
+ }
292
+ console.log(` ${v}: deprecate accepted${was}`);
293
+ wrote.push(v);
294
+ }
295
+ let unconfirmed = [];
296
+ if (wrote.length && !DRY) {
297
+ // A replaced message is confirmed only once THIS sentence is served, not merely any sentence.
298
+ const r = await confirmWrites({
299
+ versions: wrote,
300
+ read: (v) => (deprecationOf(v) === RANGE_MESSAGE ? RANGE_MESSAGE : null),
301
+ sleep: (ms) => new Promise((res) => setTimeout(res, ms)),
302
+ });
303
+ unconfirmed = r.unconfirmed;
304
+ done += r.confirmed.length;
305
+ for (const v of r.confirmed) console.log(` ${v}: serving the message`);
306
+ for (const v of unconfirmed) {
307
+ console.error(` ${v}: WRITE ACCEPTED, not yet served after ${Math.round(r.waitedMs / 1000)}s — read it from `
308
+ + "outside before treating it as undone; re-running is safe and reports it as already carrying the message.");
309
+ }
310
+ }
311
+ console.log(`deprecate-below: ${done} ${DRY ? "would be " : ""}deprecated, ${skipped} already carried this message, `
312
+ + `${unconfirmed.length} accepted but not yet served, ${failed} failed.`);
313
+ return failed ? 1 : 0;
314
+ }
315
+
204
316
  if (process.argv[1] && process.argv[1].endsWith("deprecate-below.mjs")) process.exit(await main());
@@ -54,6 +54,7 @@
54
54
  import { readFileSync, readdirSync } from "node:fs";
55
55
  import { join, dirname, basename } from "node:path";
56
56
  import { fileURLToPath } from "node:url";
57
+ import { isEntrypoint } from "../shared/is-entrypoint.mjs";
57
58
 
58
59
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
59
60
  const TEST_DIR = join(ROOT, "driver", "test");
@@ -138,7 +139,7 @@ export function scan(dir = TEST_DIR, read = (p) => readFileSync(p, "utf8"), list
138
139
  return { scanned: files.length, inClass };
139
140
  }
140
141
 
141
- if (import.meta.url === `file://${process.argv[1]}` || basename(process.argv[1] ?? "") === "drive-env-check.mjs") {
142
+ if (isEntrypoint(import.meta.url) || basename(process.argv[1] ?? "") === "drive-env-check.mjs") {
142
143
  let r;
143
144
  try { r = scan(); }
144
145
  catch (e) {