clearotron 0.3.2-beta.11 → 0.3.2-beta.13

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 (122) hide show
  1. package/.env.example +1 -1
  2. package/CONTRIBUTING.md +6 -5
  3. package/INSTALL.md +3 -4
  4. package/README.md +2 -1
  5. package/bin/clearotron.mjs +14 -0
  6. package/bin/connect.mjs +68 -3
  7. package/bin/example.mjs +12 -1
  8. package/bin/key.mjs +6 -1
  9. package/bin/onboard.mjs +23 -3
  10. package/bin/passphrase.mjs +4 -2
  11. package/bin/start.mjs +18 -6
  12. package/build-info.json +2 -2
  13. package/docs/CLIENT-MCP.md +6 -6
  14. package/docs/DELIVERY.md +3 -3
  15. package/docs/ONBOARDING.md +1 -1
  16. package/docs/PORTAL.md +3 -3
  17. package/docs/RELEASES.md +1 -1
  18. package/docs/SECURITY.md +1 -1
  19. package/docs/architecture/04-configuration-reference.md +4 -4
  20. package/docs/architecture/05-config-governance.md +10 -10
  21. package/docs/architecture/06-operations-runbook.md +3 -3
  22. package/docs/architecture/07-quality-and-audit.md +1 -1
  23. package/docs/architecture/08-development-guide.md +2 -2
  24. package/docs/architecture/09-security-and-data.md +1 -1
  25. package/docs/decisions/0002-no-dark-functionality.md +1 -1
  26. package/docs/decisions/0006-what-the-public-repository-carries.md +3 -3
  27. package/driver/CHANGELOG.md +29 -0
  28. package/driver/ask-ledger.mjs +1 -1
  29. package/driver/band-shape.mjs +1 -1
  30. package/driver/bundled-demos.mjs +2 -2
  31. package/driver/card-budget.mjs +2 -2
  32. package/driver/case-law-ledger.mjs +2 -2
  33. package/driver/connotation-search.mjs +5 -5
  34. package/driver/contract-e3-backlog.mjs +13 -13
  35. package/driver/contract-vocabulary.mjs +5 -5
  36. package/driver/coverage-form.mjs +2 -2
  37. package/driver/demo-container.mjs +26 -2
  38. package/driver/disposition-tool.mjs +2 -2
  39. package/driver/engine/CONTRACT.md +3 -3
  40. package/driver/engine/mcp/gather-config.mjs +1 -1
  41. package/driver/engine/mcp/recording-server.mjs +1 -1
  42. package/driver/engine/mcp/supplemental.mjs +22 -5
  43. package/driver/engine/openai-agent.mjs +2 -2
  44. package/driver/findings-model.mjs +21 -6
  45. package/driver/gateway.mjs +1 -1
  46. package/driver/knockout-next-step.mjs +72 -0
  47. package/driver/named-band.mjs +1 -1
  48. package/driver/package.json +1 -1
  49. package/driver/pipeline-knockout.mjs +27 -1
  50. package/driver/pipeline.mjs +4 -4
  51. package/driver/placement-union.mjs +1 -1
  52. package/driver/portal-mcp-client.mjs +1 -1
  53. package/driver/portal-report.mjs +25 -3
  54. package/driver/portal-service.mjs +25 -13
  55. package/driver/predelivery-lint.mjs +9 -4
  56. package/driver/progress.mjs +37 -1
  57. package/driver/publish/render-knockout.mjs +14 -9
  58. package/driver/publish/render.mjs +6 -6
  59. package/driver/record-discard.mjs +1 -1
  60. package/driver/register-availability.mjs +1 -1
  61. package/driver/register-count.mjs +1 -1
  62. package/driver/register-digest-record.mjs +1 -1
  63. package/driver/register-plan.mjs +81 -11
  64. package/driver/report-card-record.mjs +2 -2
  65. package/driver/result-noun-fields.mjs +3 -1
  66. package/driver/roster-verdict.mjs +2 -2
  67. package/driver/search-policy.mjs +1 -1
  68. package/driver/skeptic-record.mjs +1 -1
  69. package/driver/stages-knockout.mjs +1 -1
  70. package/driver/stages.mjs +1 -1
  71. package/driver/suite-census.json +72 -24
  72. package/driver/systemd/README.md +1 -1
  73. package/driver/unit-inventory.mjs +2 -2
  74. package/driver/verify-knockout.mjs +0 -27
  75. package/driver/verify.mjs +1 -1
  76. package/mcp-server/CHANGELOG.md +8 -0
  77. package/mcp-server/CONNECT.md +8 -8
  78. package/mcp-server/lib/runs.mjs +1 -1
  79. package/mcp-server/package.json +1 -1
  80. package/mcp-server/serve.mjs +27 -0
  81. package/package.json +1 -1
  82. package/portal-ui/dist/assets/{index-CtvwLCti.css → index-7Lq-dXDV.css} +12 -9
  83. package/portal-ui/dist/assets/{index-DXSRxPV_.js → index-w8GFZftk.js} +110 -69
  84. package/portal-ui/dist/index.html +2 -2
  85. package/portal-ui/package.json +1 -1
  86. package/providers/free-tier/src/capabilities.js +2 -2
  87. package/providers/jx/src/core.js +3 -3
  88. package/providers/jx/src/turn-envelope.mjs +1 -1
  89. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  90. package/providers/oauth-mcp-bridge/package.json +1 -1
  91. package/providers/perplexity/README.md +1 -1
  92. package/providers/signa/src/capabilities.js +2 -2
  93. package/providers/signa/src/core.js +2 -2
  94. package/providers/uspto-local/src/core.js +1 -1
  95. package/providers/uspto-local/src/sync.js +1 -1
  96. package/scripts/README.md +2 -6
  97. package/scripts/ask-ai-render-check.mjs +26 -1
  98. package/scripts/citation-anchor-report.mjs +1 -1
  99. package/scripts/citation-line-check.mjs +3 -3
  100. package/scripts/dead-names.mjs +17 -16
  101. package/scripts/e2e.mjs +1 -1
  102. package/scripts/env-audit.mjs +7 -1
  103. package/scripts/env-classify.mjs +1 -1
  104. package/scripts/pack-publishable.mjs +1 -1
  105. package/scripts/release-artifact-seal.mjs +2 -2
  106. package/scripts/repo-writes.mjs +42 -0
  107. package/scripts/report-sections-render-check.mjs +245 -0
  108. package/scripts/report-theme-render-check.mjs +31 -3
  109. package/scripts/settings-render-check.mjs +15 -8
  110. package/scripts/strip-tracker-citations.mjs +4 -4
  111. package/scripts/test-run.mjs +108 -6
  112. package/shared/browser-temp-root.mjs +10 -3
  113. package/shared/client-door.mjs +38 -6
  114. package/shared/connect-clients.mjs +2 -0
  115. package/shared/identifier-scan.mjs +2 -2
  116. package/shared/invocation.mjs +1 -1
  117. package/shared/parent-watch.mjs +33 -0
  118. package/shared/reference-guard-classes.mjs +5 -3
  119. package/shared/running-start.mjs +14 -3
  120. package/shared/scope.mjs +22 -3
  121. package/shared/stdio-connect.mjs +39 -18
  122. package/shared/writing-standard-classes.mjs +2 -3
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "a-browser-run-gets-its-own-temp-root.test.mjs": {
67
67
  "tests": 12,
68
- "asserts": 29,
68
+ "asserts": 31,
69
69
  "skips": 0,
70
70
  "todos": 0
71
71
  },
@@ -195,9 +195,15 @@
195
195
  "skips": 0,
196
196
  "todos": 0
197
197
  },
198
+ "a-conditional-with-nothing-stated-prints-no-conditional-line.test.mjs": {
199
+ "tests": 2,
200
+ "asserts": 6,
201
+ "skips": 0,
202
+ "todos": 0
203
+ },
198
204
  "a-connect-line-says-where-it-runs.test.mjs": {
199
205
  "tests": 11,
200
- "asserts": 48,
206
+ "asserts": 56,
201
207
  "skips": 0,
202
208
  "todos": 0
203
209
  },
@@ -255,6 +261,12 @@
255
261
  "skips": 0,
256
262
  "todos": 0
257
263
  },
264
+ "a-demo-leaves-no-temp-copies.test.mjs": {
265
+ "tests": 2,
266
+ "asserts": 6,
267
+ "skips": 0,
268
+ "todos": 0
269
+ },
258
270
  "a-demo-report-declares-itself.test.mjs": {
259
271
  "tests": 5,
260
272
  "asserts": 15,
@@ -279,6 +291,12 @@
279
291
  "skips": 0,
280
292
  "todos": 0
281
293
  },
294
+ "a-demo-stops-when-whatever-started-it-is-gone.test.mjs": {
295
+ "tests": 3,
296
+ "asserts": 11,
297
+ "skips": 0,
298
+ "todos": 0
299
+ },
282
300
  "a-depth-check-that-read-nothing-is-on-the-investigate-list.test.mjs": {
283
301
  "tests": 3,
284
302
  "asserts": 8,
@@ -555,6 +573,12 @@
555
573
  "skips": 1,
556
574
  "todos": 0
557
575
  },
576
+ "a-next-step-the-model-writes-comes-off-at-publish.test.mjs": {
577
+ "tests": 12,
578
+ "asserts": 28,
579
+ "skips": 0,
580
+ "todos": 0
581
+ },
558
582
  "a-note-the-release-refuses-is-refused-on-its-own-pull-request.test.mjs": {
559
583
  "tests": 2,
560
584
  "asserts": 3,
@@ -837,6 +861,12 @@
837
861
  "skips": 0,
838
862
  "todos": 0
839
863
  },
864
+ "a-running-demo-connects-by-its-folder.test.mjs": {
865
+ "tests": 4,
866
+ "asserts": 18,
867
+ "skips": 0,
868
+ "todos": 0
869
+ },
840
870
  "a-save-that-cannot-commit-says-so.test.mjs": {
841
871
  "tests": 4,
842
872
  "asserts": 19,
@@ -1510,8 +1540,8 @@
1510
1540
  "todos": 0
1511
1541
  },
1512
1542
  "client-door.test.mjs": {
1513
- "tests": 38,
1514
- "asserts": 163,
1543
+ "tests": 39,
1544
+ "asserts": 168,
1515
1545
  "skips": 1,
1516
1546
  "todos": 0
1517
1547
  },
@@ -1827,6 +1857,12 @@
1827
1857
  "skips": 0,
1828
1858
  "todos": 0
1829
1859
  },
1860
+ "dead-names.test.mjs": {
1861
+ "tests": 5,
1862
+ "asserts": 8,
1863
+ "skips": 2,
1864
+ "todos": 0
1865
+ },
1830
1866
  "declared-park-excuses-proof-not-judgment.test.mjs": {
1831
1867
  "tests": 9,
1832
1868
  "asserts": 34,
@@ -2062,8 +2098,8 @@
2062
2098
  "todos": 0
2063
2099
  },
2064
2100
  "doctor-refuses-what-cannot-run.test.mjs": {
2065
- "tests": 8,
2066
- "asserts": 20,
2101
+ "tests": 9,
2102
+ "asserts": 22,
2067
2103
  "skips": 0,
2068
2104
  "todos": 0
2069
2105
  },
@@ -2513,7 +2549,7 @@
2513
2549
  },
2514
2550
  "findings-model.test.mjs": {
2515
2551
  "tests": 68,
2516
- "asserts": 257,
2552
+ "asserts": 258,
2517
2553
  "skips": 0,
2518
2554
  "todos": 0
2519
2555
  },
@@ -2812,8 +2848,8 @@
2812
2848
  "todos": 0
2813
2849
  },
2814
2850
  "knockout-structured-read.test.mjs": {
2815
- "tests": 14,
2816
- "asserts": 63,
2851
+ "tests": 12,
2852
+ "asserts": 57,
2817
2853
  "skips": 0,
2818
2854
  "todos": 0
2819
2855
  },
@@ -3004,8 +3040,8 @@
3004
3040
  "todos": 0
3005
3041
  },
3006
3042
  "no-test-writes-inside-the-checkout.test.mjs": {
3007
- "tests": 17,
3008
- "asserts": 38,
3043
+ "tests": 21,
3044
+ "asserts": 58,
3009
3045
  "skips": 0,
3010
3046
  "todos": 0
3011
3047
  },
@@ -3305,7 +3341,7 @@
3305
3341
  },
3306
3342
  "portal-local-login.test.mjs": {
3307
3343
  "tests": 28,
3308
- "asserts": 111,
3344
+ "asserts": 113,
3309
3345
  "skips": 0,
3310
3346
  "todos": 0
3311
3347
  },
@@ -3340,8 +3376,8 @@
3340
3376
  "todos": 0
3341
3377
  },
3342
3378
  "portal-service.test.mjs": {
3343
- "tests": 130,
3344
- "asserts": 701,
3379
+ "tests": 131,
3380
+ "asserts": 712,
3345
3381
  "skips": 1,
3346
3382
  "todos": 0
3347
3383
  },
@@ -3862,8 +3898,8 @@
3862
3898
  "todos": 0
3863
3899
  },
3864
3900
  "render.test.mjs": {
3865
- "tests": 161,
3866
- "asserts": 670,
3901
+ "tests": 162,
3902
+ "asserts": 675,
3867
3903
  "skips": 0,
3868
3904
  "todos": 0
3869
3905
  },
@@ -4486,8 +4522,8 @@
4486
4522
  "todos": 0
4487
4523
  },
4488
4524
  "status-and-stop-see-a-foreground-start.test.mjs": {
4489
- "tests": 6,
4490
- "asserts": 25,
4525
+ "tests": 7,
4526
+ "asserts": 30,
4491
4527
  "skips": 0,
4492
4528
  "todos": 0
4493
4529
  },
@@ -4959,6 +4995,12 @@
4959
4995
  "skips": 0,
4960
4996
  "todos": 0
4961
4997
  },
4998
+ "the-house-element-stays-out-of-what-folds-in-later.test.mjs": {
4999
+ "tests": 6,
5000
+ "asserts": 22,
5001
+ "skips": 0,
5002
+ "todos": 0
5003
+ },
4962
5004
  "the-identifier-sweep-fires-on-its-sentinels.test.mjs": {
4963
5005
  "tests": 10,
4964
5006
  "asserts": 36,
@@ -5327,7 +5369,7 @@
5327
5369
  },
5328
5370
  "the-sign-in-door-folds-its-administrator-help.test.mjs": {
5329
5371
  "tests": 5,
5330
- "asserts": 21,
5372
+ "asserts": 23,
5331
5373
  "skips": 0,
5332
5374
  "todos": 0
5333
5375
  },
@@ -5343,9 +5385,15 @@
5343
5385
  "skips": 0,
5344
5386
  "todos": 0
5345
5387
  },
5388
+ "the-status-says-which-step-the-run-is-in-now.test.mjs": {
5389
+ "tests": 3,
5390
+ "asserts": 8,
5391
+ "skips": 0,
5392
+ "todos": 0
5393
+ },
5346
5394
  "the-summary-carries-structure.test.mjs": {
5347
- "tests": 13,
5348
- "asserts": 35,
5395
+ "tests": 14,
5396
+ "asserts": 37,
5349
5397
  "skips": 0,
5350
5398
  "todos": 0
5351
5399
  },
@@ -6034,7 +6082,7 @@
6034
6082
  },
6035
6083
  "root-mcp-json-connects.test.mjs": {
6036
6084
  "tests": 3,
6037
- "asserts": 13,
6085
+ "asserts": 14,
6038
6086
  "skips": 0,
6039
6087
  "todos": 0
6040
6088
  },
@@ -6446,8 +6494,8 @@
6446
6494
  "todos": 0
6447
6495
  },
6448
6496
  "reportSections.test.ts": {
6449
- "tests": 9,
6450
- "asserts": 27,
6497
+ "tests": 13,
6498
+ "asserts": 41,
6451
6499
  "skips": 0,
6452
6500
  "todos": 0
6453
6501
  },
@@ -33,7 +33,7 @@ missing one and writes nothing, because a unit with one placeholder left starts
33
33
  worse than one that does not start. Which units need it is **derived from the files**, not from a list —
34
34
  and `driver/unit-inventory.mjs` declares them, with a test asserting the two agree in both directions.
35
35
 
36
- These are for a **server, not a laptop**. On a workstation there is nothing to install: `npx clearotron start` supervises the
36
+ These are for a **server, not a laptop**. On a workstation there is nothing to install: `clearotron start` supervises the
37
37
  same loop for you, and`node driver/runner.mjs --watch` is what to run by hand if you started with
38
38
  `--no-worker` (`INSTALL.md` §5) — one foreground process that polls for queued jobs and for parked runs whose
39
39
  window has elapsed.
@@ -45,8 +45,8 @@
45
45
  // docs/architecture/05-config-governance.md, tier 2). Getting that wrong replaces working auth with
46
46
  // placeholders that look configured, and it is not work to do beside a running round.
47
47
  //
48
- // So the untracked units are declared as untracked, WITH THE REASON, which is exactly what 's
49
- // acceptance asks for: "either has a tracked file it is compared against, or is named here with the
48
+ // So the untracked units are declared as untracked, WITH THE REASON, which is exactly what the
49
+ // requirement asks for: "either has a tracked file it is compared against, or is named here with the
50
50
  // reason it does not".
51
51
  //
52
52
  // ── THE CORRECTION THIS FILE NEEDED ITSELF ───────────────────────────────────────────────────────
@@ -29,25 +29,6 @@ const nameKey = (s) => norm(s).toLowerCase().replace(/\s+/g, " ");
29
29
 
30
30
  // ── Tone guards (the skill's rules, mechanical subset) ───────────────────────────────────────────────
31
31
  export const BANNED_TONE_RE = /\b(extremely difficult|most dangerous|massive|enormous)\b/i;
32
-
33
- /**
34
- * A RECOMMENDATION IS A SHAPE, AND THIS SCREEN MAY NOT WRITE ONE (owner, 2026-09-18).
35
- *
36
- * The screen states findings and a rating; what to do with the name is the reading lawyer's. Two
37
- * instructions that asked for a conclusion were cut first, and a re-run then wrote one anyway, under a
38
- * heading of its own: "Practical next step — <NAME> is not knocked out at this screening depth. Advance
39
- * it to clearance…". Cutting an instruction stops the engine asking; only a refusal stops the offer.
40
- *
41
- * TWO SHAPES, because both reached a reader: a block the writer heads itself, and a closing line that
42
- * moves the name on. WHAT THIS CANNOT SEE: the same conclusion as ordinary prose — "the remaining work
43
- * here is a full search" — and no pattern can. It is a floor under the doctrine, never a substitute.
44
- *
45
- * The false positives were measured rather than assumed: the degraded note this lane REQUIRES reads
46
- * "Manual verification recommended" and matches neither, and a register record "proceeding to
47
- * registration" matches neither — only proceeding *to clearance* does.
48
- */
49
- export const RECOMMENDATION_RE = /^[ \t]*(?:[*_]{0,2}|#{1,6}\s*)(?:practical\s+|immediate\s+|suggested\s+)?(?:next steps?|what to do\b|what happens next|recommendations?|our recommendation)/im;
50
- export const ADVANCE_RE = /\b(?:advance|move|take|send)\s+(?:it|this|the name|[A-Z][A-Z0-9'’-]{2,})\s+(?:on\s+)?(?:to|into|through)\s+(?:a\s+)?(?:full\s+)?clearance\b|\bproceeds?\s+to\s+(?:a\s+)?(?:full\s+)?clearance\b|\bshould\s+(?:proceed|advance|be advanced|move)\b|\bwe\s+recommend\b/i;
51
32
  export const QUANT_CLAIM_RE = /(\b\d[\d,.]*\s*(?:M|million|billion|k|thousand)?\s*(?:streams|downloads|copies sold|units sold)\b)|(\$\s?\d[\d,.]*\s*(?:M|million|billion|k)?\s*(?:in\s+)?(?:annual\s+)?(?:revenue|sales))/i;
52
33
 
53
34
  // ── URL normalization for the receipts gate ──────────────────────────────────────────────────────────
@@ -505,14 +486,6 @@ export const validators = {
505
486
  const claim = [prose, c.chunkSummary].filter(Boolean).join("\n").match(REGISTER_CLAIM_RE);
506
487
  if (claim)
507
488
  return { ok: false, reason: `knockout_register_claim:${m.name}: "${claim[0].trim()}" — this turn cannot see the register lane and must not describe it. The report states register coverage in code, from the run's own count sidecar; a summary that says the registers were not run, above a table of counts that were, is the contradiction this rule closes. Delete the clause` };
508
- // THE PER-NAME READ IS SWEPT TOO, and until now it was not: the checks below read the finding
509
- // fields, while `assessment` was validated for length and structure alone — which is exactly the
510
- // field the conclusion arrived in. Refused HERE so the stage re-asks with the line quoted, rather
511
- // than a reader meeting it.
512
- const read = [m.assessment, prose].filter(Boolean).join("\n");
513
- const recommended = read.match(RECOMMENDATION_RE) ?? read.match(ADVANCE_RE);
514
- if (recommended)
515
- return { ok: false, reason: `mark "${m.name}": "${recommended[0].trim()}" — this screen states findings and a rating; what to do with the name, and whether it advances, is the reading lawyer's. Delete the sentence: the report's own closing caveat already says what the screen is` };
516
489
  const banned = prose.match(BANNED_TONE_RE);
517
490
  if (banned) return { ok: false, reason: `mark "${m.name}": banned tone "${banned[0]}" — measured tone only (the band colour carries urgency)` };
518
491
  const quant = prose.match(QUANT_CLAIM_RE);
package/driver/verify.mjs CHANGED
@@ -1375,7 +1375,7 @@ function connotationViolations(content, queryCount, opts = {}, dispositionsPath
1375
1375
  // patch — two families emitting a bare `form_damaged` would be indistinguishable there and the repair
1376
1376
  // would rewrite the wrong form.
1377
1377
  //
1378
- // THE SHAPE IS BUILT AROUND THREE CONSUMERS, each of which fails SILENTLY on a mismatch (/):
1378
+ // THE SHAPE IS BUILT AROUND THREE CONSUMERS, each of which fails SILENTLY on a mismatch:
1379
1379
  // - The CAUSE CENSUS IS FRONT-LOADED, `no_ruling=<n>`, before any named list: repairs.mjs CENSUS_RE
1380
1380
  // sums exactly that, pipeline.mjs shows the failing model `tok.slice(0, 160)`, and a census that fell
1381
1381
  // off the end of a slice would read as a missing quantity — i.e. as converged.
@@ -1,5 +1,13 @@
1
1
  # trademark-artifacts-mcp
2
2
 
3
+ ## 0.3.2-beta.13
4
+
5
+ No changes in this release.
6
+
7
+ ## 0.3.2-beta.12
8
+
9
+ No changes in this release.
10
+
3
11
  ## 0.3.2-beta.11
4
12
 
5
13
  No changes in this release.
@@ -5,7 +5,7 @@ MCP-capable app. Two ways in, and they are not variants of each other:
5
5
 
6
6
  | | **Local** | **Hosted** |
7
7
  |---|---|---|
8
- | What it is | your app spawns `server.mjs` from your own clone | you were given a URL by whoever operates the engine |
8
+ | What it is | your app spawns `serve.mjs` from your own clone | you were given a URL by whoever operates the engine |
9
9
  | Who it is for | anyone who cloned this repository | a customer or colleague reading someone else's runs |
10
10
  | Setup | copy-paste below, no account, no credential | paste the URL, sign in |
11
11
  | Reach | everything, including the write verbs | read-only, scoped to your own matters |
@@ -46,7 +46,7 @@ One command. Substitute your clone's path and your workspace root:
46
46
  ```sh
47
47
  claude mcp add trademark-artifacts --scope user \
48
48
  -e CLEAROTRON_WORK_DIR=/path/to/your/workspace \
49
- -- node /path/to/clearotron/mcp-server/server.mjs
49
+ -- node /path/to/clearotron/mcp-server/serve.mjs
50
50
  ```
51
51
 
52
52
  Check it: `claude mcp list` prints `trademark-artifacts: … - √ Connected`.
@@ -64,7 +64,7 @@ PowerShell 5.1, write the separator as `"--"`: that shell drops a bare `--`. Rem
64
64
  "mcpServers": {
65
65
  "trademark-artifacts": {
66
66
  "command": "node",
67
- "args": ["/path/to/clearotron/mcp-server/server.mjs"],
67
+ "args": ["/path/to/clearotron/mcp-server/serve.mjs"],
68
68
  "env": { "CLEAROTRON_WORK_DIR": "/path/to/your/workspace" }
69
69
  }
70
70
  }
@@ -82,7 +82,7 @@ Codex reads `~/.codex/config.toml`:
82
82
  ```toml
83
83
  [mcp_servers.trademark-artifacts]
84
84
  command = "node"
85
- args = ["/path/to/clearotron/mcp-server/server.mjs"]
85
+ args = ["/path/to/clearotron/mcp-server/serve.mjs"]
86
86
  env = { CLEAROTRON_WORK_DIR = "/path/to/your/workspace" }
87
87
  ```
88
88
 
@@ -94,13 +94,13 @@ is a value written to a file.
94
94
 
95
95
  ## Any other MCP host
96
96
 
97
- The contract is the same three things every time: run `node mcp-server/server.mjs`, over stdio, with
97
+ The contract is the same three things every time: run `node mcp-server/serve.mjs`, over stdio, with
98
98
  `CLEAROTRON_WORK_DIR` in its environment.
99
99
 
100
100
  ```json
101
101
  {
102
102
  "command": "node",
103
- "args": ["/path/to/clearotron/mcp-server/server.mjs"],
103
+ "args": ["/path/to/clearotron/mcp-server/serve.mjs"],
104
104
  "env": { "CLEAROTRON_WORK_DIR": "…", "CLEAROTRON_REPORTS_DIR": "…" }
105
105
  }
106
106
  ```
@@ -125,7 +125,7 @@ read them. Spawn the server as that user:
125
125
  ```json
126
126
  {
127
127
  "command": "sudo",
128
- "args": ["-u", "<operator>", "node", "/path/to/clearotron/mcp-server/server.mjs"]
128
+ "args": ["-u", "<operator>", "node", "/path/to/clearotron/mcp-server/serve.mjs"]
129
129
  }
130
130
  ```
131
131
 
@@ -133,7 +133,7 @@ That needs a one-time NOPASSWD rule, or the stdio handshake hangs on a password
133
133
 
134
134
  ```
135
135
  # /etc/sudoers.d/trademark-artifacts-mcp (chmod 0440)
136
- <caller> ALL=(<operator>) NOPASSWD: /usr/bin/node /path/to/clearotron/mcp-server/server.mjs
136
+ <caller> ALL=(<operator>) NOPASSWD: /usr/bin/node /path/to/clearotron/mcp-server/serve.mjs
137
137
  ```
138
138
 
139
139
  ## What to ask it
@@ -40,7 +40,7 @@ export function unreadableRunsReason({ workSet, workRoot, workExists, poolSet })
40
40
  if (workSet || workExists || poolSet) return null;
41
41
  return `no searches can be read here: CLEAROTRON_WORK_DIR is unset and ${workRoot} does not exist, `
42
42
  + "and CLEAROTRON_REPORTS_DIR is unset. Set them to the install's directories — `npx clearotron doctor` "
43
- + "prints where an install keeps them.";
43
+ + "prints where an install keeps them, and mcp-server/CONNECT.md says how to connect this server.";
44
44
  }
45
45
 
46
46
  // Every workspace-<agent>/studio/clearance-search root under the live workspace root.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trademark-artifacts-mcp",
3
- "version": "0.3.2-beta.11",
3
+ "version": "0.3.2-beta.13",
4
4
  "license": "AGPL-3.0-only",
5
5
  "private": true,
6
6
  "description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
4
+ //
5
+ // serve.mjs — the MCP server over stdio, with the Node check in front of it. The connect lines name
6
+ // this file.
7
+ //
8
+ // server.mjs cannot check the Node it runs on. An ES module's imports are all loaded before any of its
9
+ // code runs, so on a Node below the floor the server fails while loading, with an error that names a
10
+ // module and says nothing about the version. Measured on Node 18.20.8: "SyntaxError: The requested
11
+ // module 'node:util' does not provide an export named 'parseEnv'". On a Windows laptop running the
12
+ // connect line through WSL, the distribution's own Node 18 met "SyntaxError: Unexpected token 'with'"
13
+ // (2026-09-19). This file imports only what an old Node can load, refuses in one plain line, and only
14
+ // then loads the server.
15
+ import { fileURLToPath } from "node:url";
16
+ import { nodeFloorVerdict, nodeFloorRefusal } from "../shared/node-floor.mjs"; // one floor, read from package.json
17
+
18
+ const floor = nodeFloorVerdict();
19
+ if (!floor.ok) {
20
+ console.error(`clearotron: ${nodeFloorRefusal(floor)}`);
21
+ process.exit(1);
22
+ }
23
+
24
+ // THE SERVER STARTS AS THE ENTRY IT WOULD HAVE BEEN. It starts its stdio transport, and reads the
25
+ // install's settings, only when it is the process's entry file, so the entry is handed over first.
26
+ process.argv[1] = fileURLToPath(new URL("./server.mjs", import.meta.url));
27
+ await import("./server.mjs");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clearotron",
3
3
  "type": "module",
4
- "version": "0.3.2-beta.11",
4
+ "version": "0.3.2-beta.13",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
7
7
  "type": "git",
@@ -254,19 +254,19 @@ html.anon-on [data-anon='mark']:not(.anon-clear) {
254
254
  border-bottom: 1px solid var(--border-hairline);
255
255
  }
256
256
 
257
- /* THE HEADER'S SECOND ROW: which read you are in, then where you are inside it.
257
+ /* THE HEADER'S LOWER ROWS: which read you are in, then where you are inside it.
258
258
  The Reads strip used to sit BELOW this box and the report's own section breadcrumb inside the frame,
259
259
  so neither stayed on screen — the frame is sized to its content, which leaves a `position:sticky` bar
260
260
  in there pinned to nothing. Both are rows of the pinned header now (owner, 2026-09-18).
261
- It scrolls sideways rather than wrapping to a third line: a header that grows taller with the number of
262
- sections is a header that eats the document it is heading. */
263
- .report-nav {
264
- display: flex; align-items: center; gap: 10px; margin-top: 9px;
261
+ THE SECTIONS HAVE A ROW OF THEIR OWN, UNDER THE READS (owner, 2026-09-19). Side by side, sixteen reads
262
+ wrapped into two ragged columns while the strip floated beside them at mid-height. The reads wrap; the
263
+ sections scroll sideways rather than wrapping, so the strip stays one line however narrow the screen. */
264
+ .report-nav { display: flex; flex-direction: column; align-items: stretch; gap: 8px; margin-top: 9px; }
265
+ .report-sections {
266
+ display: flex; align-items: center; gap: 2px;
265
267
  overflow-x: auto; scrollbar-width: none;
266
268
  }
267
- .report-nav::-webkit-scrollbar { display: none; }
268
- .report-nav-sep { flex: none; width: 1px; align-self: stretch; background: var(--border-hairline); }
269
- .report-sections { display: flex; align-items: center; gap: 2px; }
269
+ .report-sections::-webkit-scrollbar { display: none; }
270
270
  .report-section {
271
271
  flex: none; display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
272
272
  font: inherit; font-size: 12.5px; font-weight: 600; letter-spacing: .01em;
@@ -277,8 +277,11 @@ html.anon-on [data-anon='mark']:not(.anon-clear) {
277
277
  .report-section-dot {
278
278
  width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid currentColor; box-sizing: border-box;
279
279
  }
280
+ /* PROGRESS: every section reached has a filled dot, the rest stay hollow; the one in view also sits on
281
+ the sunken pill, so it can be told from the ones behind it. */
282
+ .report-section[data-reached='true'] { color: var(--text-strong); }
283
+ .report-section[data-reached='true'] .report-section-dot { background: var(--accent); border-color: var(--accent); }
280
284
  .report-section[aria-current='true'] { color: var(--text-strong); background: var(--surface-sunken); }
281
- .report-section[aria-current='true'] .report-section-dot { border-color: var(--accent); border-width: 3px; }
282
285
 
283
286
  /* The breadcrumb above it: one step back as a quiet button, then where the reader is. Compound selectors
284
287
  throughout this block, because the button and row rules they adjust are declared later in the file. */