geneseed 3.1.3 → 3.2.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 (113) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +2 -2
  3. package/SHIPPED.md +3 -3
  4. package/bin/build-driver.mjs +127 -79
  5. package/bin/geneseed-cli.mjs +42 -84
  6. package/bin/geneseed-hook.mjs +5 -2
  7. package/harness.config.json +1 -1
  8. package/js/README.md +1 -10
  9. package/js/build/emit-claude.mjs +4 -5
  10. package/js/build/emit-common.mjs +63 -114
  11. package/js/build/emit-opencode.mjs +74 -66
  12. package/js/build/generate.mjs +4 -6
  13. package/js/build/render.mjs +13 -13
  14. package/js/build/source.mjs +7 -6
  15. package/js/build/stubs.mjs +25 -33
  16. package/js/build/themes.mjs +1 -1
  17. package/js/build/version.mjs +1 -3
  18. package/js/hosts/hooks.mjs +3 -3
  19. package/js/hosts/hosts.mjs +2 -4
  20. package/js/hosts/installs.mjs +35 -28
  21. package/js/hosts/link.mjs +1 -1
  22. package/js/hosts/native.mjs +37 -19
  23. package/js/hosts/opencode.mjs +39 -28
  24. package/js/hosts/settings.mjs +33 -110
  25. package/js/inspect/checks-authoring.mjs +5 -4
  26. package/js/inspect/checks-build.mjs +1 -1
  27. package/js/inspect/diff.mjs +1 -3
  28. package/js/inspect/doctor.mjs +59 -44
  29. package/js/inspect/excludes.mjs +4 -10
  30. package/js/inspect/inventory.mjs +37 -55
  31. package/js/inspect/registry.mjs +4 -7
  32. package/js/inspect/scan.mjs +8 -15
  33. package/js/inspect/status.mjs +1 -1
  34. package/js/lib/fs.mjs +34 -0
  35. package/js/lib/json.mjs +28 -1
  36. package/js/lib/paths.mjs +0 -10
  37. package/js/lib/udiff.mjs +11 -16
  38. package/js/maintain/memory.mjs +2 -4
  39. package/js/maintain/migrate.mjs +3 -3
  40. package/js/maintain/uninstall.mjs +90 -78
  41. package/js/maintain/update.mjs +2 -2
  42. package/js/ui/anim.mjs +1 -1
  43. package/js/ui/menu.mjs +1 -1
  44. package/js/ui/tui.mjs +3 -20
  45. package/js/web/actions.mjs +230 -217
  46. package/js/web/activity.mjs +71 -89
  47. package/js/web/api.mjs +133 -224
  48. package/js/web/daemon.mjs +92 -110
  49. package/js/web/docs.mjs +66 -111
  50. package/js/web/graph.mjs +0 -0
  51. package/js/web/handler.mjs +78 -97
  52. package/js/web/jobs.mjs +124 -165
  53. package/js/web/routes.mjs +73 -152
  54. package/js/web/server.mjs +47 -82
  55. package/package.json +1 -1
  56. package/src/agents/explorer.md +2 -1
  57. package/src/doctrines/process.md +4 -2
  58. package/src/laws/universal.md +55 -43
  59. package/src/skills/clarify.md +1 -1
  60. package/src/skills/forge-mcp.md +2 -1
  61. package/src/skills/fresh-eyes.md +1 -1
  62. package/src/skills/ingest.md +2 -1
  63. package/src/skills/parallel-agents.md +1 -1
  64. package/src/skills/ponytail.md +2 -2
  65. package/src/skills/research.md +4 -2
  66. package/themes/_TEMPLATE.json +2 -0
  67. package/themes/biker.json +2 -0
  68. package/themes/commentator.json +2 -0
  69. package/themes/cyberpunk.json +2 -0
  70. package/themes/gamer.json +2 -0
  71. package/themes/imperial.json +2 -0
  72. package/themes/joker.json +2 -0
  73. package/themes/marvin.json +2 -0
  74. package/themes/mean.json +2 -0
  75. package/themes/military.json +2 -0
  76. package/themes/neutral.json +2 -0
  77. package/themes/pirate.json +2 -0
  78. package/themes/sports.json +2 -0
  79. package/themes/verstappen.json +2 -0
  80. package/themes/wizard.json +2 -0
  81. package/web/dist/assets/{Activity-B49fE_ug.js → Activity-BTCG2gMl.js} +1 -1
  82. package/web/dist/assets/{ActivityDetail-abt7hzD2.js → ActivityDetail-julsmPDB.js} +1 -1
  83. package/web/dist/assets/CatalogRow-DqS4e5XM.js +1 -0
  84. package/web/dist/assets/Diff-CIrGbiqm.js +5 -0
  85. package/web/dist/assets/Doctor-CpI53oHY.js +1 -0
  86. package/web/dist/assets/{Graph-DvYMtlvl.js → Graph-CS4tA7YS.js} +1 -1
  87. package/web/dist/assets/Harness-vRYs11kX.js +1 -0
  88. package/web/dist/assets/Laws-BjbHJrJt.js +3 -0
  89. package/web/dist/assets/Library-CjC7BAWN.js +1 -0
  90. package/web/dist/assets/{marked.esm-JKvyS0Pp.js → Markdown-kgpg9EQM.js} +15 -15
  91. package/web/dist/assets/Profile-CHr-PPf6.js +1 -0
  92. package/web/dist/assets/{Rules-CfsV1EVy.js → Rules-CsC6hfqk.js} +1 -1
  93. package/web/dist/assets/Skills-BvatJGfe.js +1 -0
  94. package/web/dist/assets/{StatusBadge-C9bZOEZk.js → StatusBadge-gRrak0nO.js} +1 -1
  95. package/web/dist/assets/{activity-Cc0n2jwx.js → activity-B0-FXZCj.js} +1 -1
  96. package/web/dist/assets/index-BnRWQbZb.js +12 -0
  97. package/web/dist/assets/index-Bx3I7cRW.js +2 -0
  98. package/web/dist/assets/index-D0f9tJlA.css +1 -0
  99. package/web/dist/assets/index-DJOhOwwR.js +1 -0
  100. package/web/dist/index.html +2 -2
  101. package/web/src/pages/Laws.jsx +42 -71
  102. package/web/dist/assets/Diff-D3_bVkih.js +0 -5
  103. package/web/dist/assets/Doctor-hXp7srsx.js +0 -1
  104. package/web/dist/assets/Harness-BIWei7N5.js +0 -1
  105. package/web/dist/assets/Laws-CxmnRdwt.js +0 -3
  106. package/web/dist/assets/Library-MjbOkHIW.js +0 -1
  107. package/web/dist/assets/Markdown-Dx_w0faR.js +0 -1
  108. package/web/dist/assets/Profile-BRFvv2gr.js +0 -1
  109. package/web/dist/assets/Skills-DArA6IHS.js +0 -1
  110. package/web/dist/assets/index-Bx3oxZ1o.css +0 -1
  111. package/web/dist/assets/index-DC3-GlCY.js +0 -1
  112. package/web/dist/assets/index-DP035wPa.js +0 -12
  113. package/web/dist/assets/index-dHi-7M-F.js +0 -2
package/js/web/docs.mjs CHANGED
@@ -1,44 +1,23 @@
1
1
  /**
2
- * The web console's Docs pages — `rituals/_web_docs.py` and the registry
3
- * `_web_core.py` builds from `docs/web/`.
2
+ * The web console's Docs pages, and the registry built from `docs/web/`.
4
3
  *
5
- * WHAT CROSSES HERE AND WHAT DOES NOT, measured before the phase was taken rather than
6
- * discovered inside it. `api_docs_page` dispatches on five KINDS, and 57 of the 59 pages
7
- * are one of the three that are pure string work: `markdown` (read, slice, strip),
8
- * `concept` (an inline body with live counts substituted) and `glossary`. Those three
9
- * crossed in P6d, `about` in P8c (four of five), and `cli` is P10c's. The two deferrals had
10
- * a reason that is not "it is long":
4
+ * `cli` docs render via `cliReference()` in `js/ui/cli.mjs`, which reads
5
+ * `js/cli-table.json` — the CLI's own metadata, since `harness.build_argparser()`'s parser
6
+ * is not something a Node twin can introspect. `bin/geneseed-cli.mjs`'s `VERBS` table reads
7
+ * the same file, so there is exactly one description of the CLI surface, not a second
8
+ * transcription of it.
11
9
  *
12
- * * `cli` walked `harness.build_argparser()` — **182 lines, 24 subparsers, 43
13
- * `add_argument` calls**, every one carrying help text, a metavar, choices and a
14
- * default. A Node twin cannot introspect it; reproducing it means transcribing the
15
- * whole CLI surface into a second table that would describe verbs `bin/geneseed-cli.mjs`
16
- * cannot even run. This port's standing rule is that a copy of a value under test stops
17
- * being the value under test, and this would be the largest copy in it. **PAID IN P10c**,
18
- * by the fix the deferral named: the parser's metadata became a FILE both implementations
19
- * read — `js/cli-table.json` since P2. `js/ui/cli.mjs` is the reader,
20
- * `tests/test_cli_reference.py` is what fails when the table and the parser part company
21
- * for as long as the parser exists, and `bin/geneseed-cli.mjs`'s `VERBS` table
22
- * — which was a SECOND transcription of the same 43 calls — reads it too, so the port
23
- * ended with one description of the CLI where it had been heading for three.
24
- * * `about` called `_update._origin_display()`, which shells out to `git remote get-url`.
25
- * `rituals/_update.py` was P8's whole subject and P6d would have needed an
26
- * `_ALLOWED_SPAWNS` row for `git` ahead of the phase that argues for it. **PAID IN P8c**:
27
- * P8a made that argument and ported `originDisplay()`, so the deferral was a one-line
28
- * removal from `NOT_PORTED_KINDS` and a row in `KIND_ROUTES`.
29
- *
30
- * So `NOT_PORTED_KINDS` declares what is left, `api_docs_page` answers 501 for it, and
10
+ * So `NOT_PORTED_KINDS` declares what is left, `apiDocsPage` answers 501 for it, and
31
11
  * `tests/unit/web_server.test.mjs` requires `PORTED_KINDS` ∪ `NOT_PORTED_KINDS` to equal
32
- * `REF_KINDS` — a FROZEN LITERAL of the five kinds the reference dispatched on, since the
33
- * reference itself is deleted. Same partition shape as the route table, which is why it is a
34
- * table here too and not two `if`s.
12
+ * `REF_KINDS` — a FROZEN LITERAL of the five kinds this daemon has ever needed to dispatch
13
+ * on. Same partition shape as the route table, which is why it is a table here too and not
14
+ * two `if`s.
35
15
  *
36
- * THE `?harness=` QUERY PARAM LANDS HERE. P6a skipped it deliberately (no caller); it is
37
- * the Docs selector, and it is the ONLY input to these endpoints that is not the checkout,
38
- * so it is also the only thing a cell can vary. Every filtering rule below is gated by
39
- * sending both values over one page.
16
+ * THE `?harness=` QUERY PARAM IS the Docs selector, and it is the ONLY input to these
17
+ * endpoints that is not the checkout itself — the one thing a test can vary. Every
18
+ * filtering rule below is exercised by sending both values over one page.
40
19
  */
41
- import { readdirSync, statSync } from 'node:fs';
20
+ import { readdirSync } from 'node:fs';
42
21
  import path from 'node:path';
43
22
 
44
23
  import { ROOT, THEMES } from '../build/source.mjs';
@@ -46,18 +25,17 @@ import { cliReference } from '../ui/cli.mjs';
46
25
  import { readJsonMaybe, readMaybe } from '../hosts/installs.mjs';
47
26
  import { resolvePath } from '../hosts/hosts.mjs';
48
27
  import { parseJson } from '../lib/json.mjs';
28
+ import { isFile } from '../lib/fs.mjs';
49
29
  import { normcase } from '../lib/paths.mjs';
50
30
  import { WHITESPACE, stripWhitespace } from '../lib/text.mjs';
51
31
  import { statusData } from '../inspect/status.mjs';
52
32
  import { originDisplay } from '../maintain/update.mjs';
53
33
  import { NotFound, deployed, resolveLinks } from './api.mjs';
54
34
 
55
- const isFile = (p) => { try { return statSync(p).isFile(); } catch { return false; } };
56
-
57
35
  const DOC_DIR = path.join(ROOT, 'docs', 'web');
58
36
 
59
37
  /**
60
- * `_web_core._doc_frontmatter` — a docs page split into its frontmatter map and its body.
38
+ * A docs page split into its frontmatter map and its body.
61
39
  *
62
40
  * Deliberately not YAML: each value is a JSON scalar (or a JSON object for `link`), which
63
41
  * covers every key these pages use and parses with the standard library. A value that is
@@ -77,23 +55,22 @@ export function docFrontmatter(text) {
77
55
  const key = (colon < 0 ? line : line.slice(0, colon)).trim();
78
56
  const raw = (colon < 0 ? '' : line.slice(colon + 1)).trim();
79
57
  if (!key || !raw) continue;
80
- // `json.loads` first, the raw string on a decode error — and `parseJson`, so a
81
- // frontmatter number keeps the int/float distinction the rest of this port protects.
58
+ // `parseJson`, so a frontmatter number keeps the int/float distinction the rest of
59
+ // this codebase protects.
82
60
  try { meta[key] = parseJson(raw); } catch { meta[key] = raw; }
83
61
  }
84
62
  return [meta, body];
85
63
  }
86
64
 
87
65
  /**
88
- * `_web_core._load_doc_groups` — the Docs registry, built from `docs/web/`.
66
+ * The Docs registry, built from `docs/web/`.
89
67
  *
90
68
  * A page whose file is missing or malformed is skipped rather than crashing the server; an
91
69
  * unreadable registry yields an empty Docs section, which the UI renders as "no pages".
92
70
  *
93
- * NOT cached at module load, where the Python builds `DOC_GROUPS` once at import. The
94
- * difference is invisible to a request — `docs/web/` cannot change under a running daemon
95
- * any more than `dist/` can — and a module-level constant would freeze whatever `ROOT` was
96
- * when the corpus first imported this file.
71
+ * NOT cached at module load. The difference is invisible to a request — `docs/web/` cannot
72
+ * change under a running daemon any more than `dist/` can — and a module-level constant
73
+ * would freeze whatever `ROOT` was when this file was first imported.
97
74
  */
98
75
  export function docGroups() {
99
76
  const groups = readJsonMaybe(path.join(DOC_DIR, '_groups.json'));
@@ -118,7 +95,7 @@ export function docGroups() {
118
95
  const out = [];
119
96
  for (const g of groups) {
120
97
  const entry = byId.get(g.id);
121
- // `sorted(key=lambda t: t[0])` is STABLE, so equal orders keep filename order.
98
+ // STABLE sort: equal orders keep filename order.
122
99
  entry.pages = entry.pages
123
100
  .map((pair, i) => [pair[0], i, pair[1]])
124
101
  .sort((a, b) => (a[0] - b[0]) || (a[1] - b[1]))
@@ -129,8 +106,8 @@ export function docGroups() {
129
106
  }
130
107
 
131
108
  /**
132
- * `_web_core.GLOSSARY_KEYS` — the neutral term, the theme key whose value renames it, and
133
- * the one-line description. A key of `null` marks a term no theme renames.
109
+ * The neutral term, the theme key whose value renames it, and the one-line description. A
110
+ * key of `null` marks a term no theme renames.
134
111
  */
135
112
  const GLOSSARY_KEYS = [
136
113
  // The three tiers, in constitutional order and before the entities — a reader who does not
@@ -170,11 +147,9 @@ const GLOSSARY_KEYS = [
170
147
  // ---- counts substituted into concept bodies -------------------------------------------
171
148
 
172
149
  /**
173
- * `_web_docs._doc_counts`.
174
- *
175
- * Exported so the tier tokens can be gated on the day they are ADDED rather than on the day a
176
- * page first spends one. Only one page carries `{N_LAWS}` today; a test that could only reach
177
- * these through a rendered body would leave four of the seven untested until Task 6.2.
150
+ * Exported so the tier tokens can be gated on the day they are ADDED rather than on the day
151
+ * a page first spends one. Only one page carries `{N_LAWS}` today; a test that could only
152
+ * reach these through a rendered body would leave most of them untested.
178
153
  */
179
154
  export function docCounts(state) {
180
155
  const inv = state.inventory;
@@ -204,7 +179,6 @@ export function docCounts(state) {
204
179
  };
205
180
  }
206
181
 
207
- /** `_web_docs._sub_counts`. */
208
182
  function subCounts(state, body) {
209
183
  if (!body.includes('{N_')) return body;
210
184
  let out = body;
@@ -226,7 +200,6 @@ const HARNESS_CLOSE_RE = /^\s*<!--\s*\/harness\s*-->\s*$/;
226
200
  */
227
201
  const HARNESS_HINT_RE = /<!--\s*harness:/;
228
202
 
229
- /** `_web_docs._norm_harness`. */
230
203
  export function normHarness(value, state) {
231
204
  const v = (value || '').trim().toLowerCase();
232
205
  if (HARNESSES.includes(v)) return v;
@@ -234,7 +207,7 @@ export function normHarness(value, state) {
234
207
  }
235
208
 
236
209
  /**
237
- * `_web_docs._harness_blocks_balanced` — every open has a matching close, with no nesting.
210
+ * Every open has a matching close, with no nesting.
238
211
  *
239
212
  * A marker inside a ``` fence is example text, not a marker (the same rule `sliceSection`
240
213
  * uses). Its purpose is to FAIL OPEN: an unbalanced marker leaves the body untouched, so a
@@ -262,7 +235,6 @@ export function harnessBlocksBalanced(lines) {
262
235
  return !open;
263
236
  }
264
237
 
265
- /** `_web_docs._strip_harness_blocks`. */
266
238
  export function stripHarnessBlocks(body, harnessName) {
267
239
  if (!HARNESS_HINT_RE.test(body)) return body;
268
240
  const lines = splitLines(body);
@@ -287,9 +259,9 @@ export function stripHarnessBlocks(body, harnessName) {
287
259
  }
288
260
 
289
261
  /**
290
- * `str.splitlines()` — which is NOT `split('\n')`: it drops a single trailing newline
291
- * instead of yielding a final empty string, and that difference reaches the output here
292
- * because the result is re-joined with `\n`.
262
+ * NOT a bare `split('\n')`: this drops a single trailing newline instead of yielding a
263
+ * final empty string, and that difference reaches the output here because the result is
264
+ * re-joined with `\n`.
293
265
  */
294
266
  function splitLines(s) {
295
267
  const parts = s.split('\n');
@@ -297,7 +269,7 @@ function splitLines(s) {
297
269
  return parts;
298
270
  }
299
271
 
300
- /** `_web_docs._visible_groups` — pure; the registry is never mutated. */
272
+ /** Pure; the registry is never mutated. */
301
273
  function visibleGroups(harnessName) {
302
274
  const groups = [];
303
275
  for (const g of docGroups()) {
@@ -316,14 +288,11 @@ const SLUG_DASH_RE = /-+/g;
316
288
  const HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
317
289
 
318
290
  /**
319
- * `_web_docs._slugify_heading` — the same rules the frontend's `slug()` uses, so a
320
- * registry `anchor` written against a heading matches the id the renderer assigns.
291
+ * The same rules the frontend's `slug()` uses, so a registry `anchor` written against a
292
+ * heading matches the id the renderer assigns.
321
293
  *
322
- * `WHITESPACE` and `stripWhitespace` rather than `\s` and `trim()`, and the corpus in
323
- * `tests/test_pure_function_parity.py` is what made that necessary rather than tidy: the
324
- * two languages' whitespace classes differ at U+FEFF and at U+001C-U+001F, and a heading
325
- * carrying one slugs differently on each side. See `WHITESPACE`'s docblock for the measured
326
- * set.
294
+ * `WHITESPACE` and `stripWhitespace` rather than `\s` and `trim()` — see `WHITESPACE`'s own
295
+ * docblock for the measured set of characters where this matters.
327
296
  */
328
297
  export function slugifyHeading(text) {
329
298
  let s = stripWhitespace(text.toLowerCase()).replace(SLUG_STRIP_RE, '');
@@ -332,7 +301,7 @@ export function slugifyHeading(text) {
332
301
  }
333
302
 
334
303
  /**
335
- * `_web_docs._slice_section` — the body trimmed to one heading's section.
304
+ * The body trimmed to one heading's section.
336
305
  *
337
306
  * Returns `[body, ok]`; `ok === false` (with the ORIGINAL body) when the anchor is
338
307
  * missing, so the caller falls back to the whole document. An H1 slice stops at the first
@@ -372,7 +341,6 @@ export function sliceSection(body, anchor) {
372
341
 
373
342
  // ---- the endpoints -----------------------------------------------------------------------
374
343
 
375
- /** `_web_docs._find_doc_page`. */
376
344
  function findDocPage(pageId) {
377
345
  for (const g of docGroups()) {
378
346
  for (const p of g.pages) if (p.id === pageId) return p;
@@ -381,9 +349,9 @@ function findDocPage(pageId) {
381
349
  }
382
350
 
383
351
  /**
384
- * `_web_docs._read_doc_source` — a markdown file relative to ROOT, guarded against escapes
385
- * the same way the catalog is. A GET carries no token, so `rel` comes out of the registry
386
- * but the guard is what makes that safe to say.
352
+ * A markdown file relative to ROOT, guarded against escapes the same way the catalog is. A
353
+ * GET carries no token, so `rel` comes out of the registry, but the guard is what makes
354
+ * that safe to say.
387
355
  */
388
356
  function readDocSource(rel) {
389
357
  const target = resolvePath(path.join(ROOT, rel));
@@ -397,7 +365,7 @@ function within(child, parent) {
397
365
  return p.length <= c.length && p.every((seg, i) => c[i] === seg);
398
366
  }
399
367
 
400
- /** `_web_docs._glossary` — the deployed theme's words beside the neutral ones. */
368
+ /** The deployed theme's words beside the neutral ones. */
401
369
  function glossary(state) {
402
370
  const load = (theme) => {
403
371
  const doc = readJsonMaybe(path.join(THEMES, `${theme}.json`));
@@ -423,12 +391,11 @@ function glossary(state) {
423
391
  }
424
392
 
425
393
  /**
426
- * `_web_docs._about` — the About page: a version line, the deployed install, and the links.
394
+ * The About page: a version line, the deployed install, and the links.
427
395
  *
428
396
  * `repo` is the install's OWN git origin (where updates come from), and `repo_is_github` is
429
- * what gates the github-shaped deep links in the UI. Both come from `originDisplay()`, which
430
- * is the one thing P6d could not have: it shells `git remote get-url`, and P8a is the phase
431
- * that argued for the `git` row in `_ALLOWED_SPAWNS`.
397
+ * what gates the github-shaped deep links in the UI. Both come from `originDisplay()`,
398
+ * which shells out to `git remote get-url` (the `git` row in `ALLOWED_SPAWNS`).
432
399
  *
433
400
  * `version` IS NOT WHAT IT LOOKS LIKE, and it is the shape this page has always had rather than
434
401
  * a shortcut here. It reads a `version` key off the status snapshot and THERE IS NO SUCH KEY —
@@ -436,8 +403,8 @@ function glossary(state) {
436
403
  * field is `{}`, always, and `sd.version || {}` reproduces the EXPRESSION rather than the
437
404
  * constant: if the key ever appears, the page gains it without another edit.
438
405
  *
439
- * THERE IS NO INTERPRETER FIELD, and the absence is deliberate — see `apiSetup`, which spells
440
- * the same absence for the same reason.
406
+ * THERE IS NO INTERPRETER FIELD, and the absence is deliberate — see `apiSetup` in
407
+ * `api.mjs`, which spells the same absence for the same reason.
441
408
  *
442
409
  * `statusData()` is called for one dead field, and deliberately: this page has always paid that
443
410
  * cost, and skipping it would answer faster in a way no gate measures and no user asked for.
@@ -453,41 +420,32 @@ function about(state) {
453
420
  target: String(state.target),
454
421
  root: String(ROOT),
455
422
  repo: od.url,
456
- // `githubSlug`, not `github_slug`. `OriginDisplay`'s JS twin keeps the Python field in
457
- // camelCase the way every other ported record does, and the RESPONSE key is snake_case
458
- // because it is the wire format — the two spellings meet exactly here. Reading the wire
459
- // name off the record gives `undefined`, which is a perfectly plausible `false`: the About
460
- // page silently drops every github deep link and nothing else changes. Caught by comparing
461
- // the two payloads by hand before a cell existed; `docs/the-about-page-...` is the cell.
423
+ // `githubSlug`, not `github_slug` — the record itself is camelCase like every other
424
+ // object in this codebase; the RESPONSE key below is snake_case because that is the
425
+ // wire format. Reading the wrong spelling off the record gives `undefined`, which is a
426
+ // perfectly plausible `false`: the About page would silently drop every github deep
427
+ // link and nothing else would change.
462
428
  repo_is_github: Boolean(od.githubSlug),
463
429
  license: 'MIT',
464
430
  };
465
431
  }
466
432
 
467
433
  /**
468
- * EMPTY SINCE P10c, AND KEPT DECLARED — the third partition in this port to reach that state,
434
+ * EMPTY, AND KEPT DECLARED — the third partition in this codebase to reach that state,
469
435
  * after `NOT_PORTED_POST` and `NOT_PORTED_ACTIONS`.
470
436
  *
471
- * `tests/test_web_server.py` cross-checks this set against the kinds
472
- * `_web_docs.api_docs_page` dispatches on by `ast`, and that check is at its STRONGEST when
473
- * the set is empty: it then says every kind the reference dispatches on is answered here. A
474
- * sixth kind added to the reference still cannot quietly fall through to the `NotFound` at
475
- * the bottom, and the next unported kind has a place to be declared rather than reading as a
476
- * typo'd page id.
437
+ * `tests/unit/web_server.test.mjs` cross-checks this set against the kinds `apiDocsPage`
438
+ * dispatches on, and that check is at its STRONGEST when the set is empty: it says every
439
+ * kind this daemon can dispatch on is answered here. A sixth kind added later still cannot
440
+ * quietly fall through to the `NotFound` at the bottom, and it has a place to be declared
441
+ * rather than reading as a typo'd page id.
477
442
  *
478
- * WHAT DIED WITH THE LAST ROW is the 501 arm's only reachable target. `test_web_server.py`'s
479
- * running probe used to GET `/api/docs/page/cli` and require 501; with the set empty no page
480
- * has an unported kind and none can be borrowed. That probe is now the positive control for
481
- * `cli` (200), and the claim that the 501 arm still works rests on the declaration check
482
- * alone — strictly weaker, discipline #7, and said out loud there as it is here.
483
- *
484
- * `about` left in P8c (`_origin_display` and the `git` spawn behind it, not the page).
485
- * `cli` left in P10c, and its blocker was neither: it was that `harness.build_argparser()`
486
- * cannot be introspected from Node at all. See `js/ui/cli.mjs`.
443
+ * THE 501 ARM HAS NO REACHABLE TARGET TODAY: with this set empty, no page names an
444
+ * unported kind. Its correctness rests on the declaration check above, not on any request
445
+ * that actually reaches it — worth knowing before "simplifying" it away as dead code.
487
446
  */
488
447
  export const NOT_PORTED_KINDS = new Set();
489
448
 
490
- /** `_web_docs.api_docs`. */
491
449
  export function apiDocs(state, harnessName = null) {
492
450
  const hn = normHarness(harnessName, state);
493
451
  const groups = visibleGroups(hn).map((g) => ({
@@ -499,8 +457,6 @@ export function apiDocs(state, harnessName = null) {
499
457
  }
500
458
 
501
459
  /**
502
- * `_web_docs.api_docs_page`.
503
- *
504
460
  * The lookup deliberately ignores a page's own `harness` tag: a deep link to a page the
505
461
  * active harness hides still resolves, and the client redirects it out of view.
506
462
  */
@@ -521,10 +477,9 @@ export function apiDocsPage(state, pageId, harnessName = null) {
521
477
  /**
522
478
  * The three kinds that cross, as a TABLE and not three `if`s.
523
479
  *
524
- * `tests/test_web_server.py` requires `Object.keys(KIND_ROUTES) ∪ NOT_PORTED_KINDS` to
525
- * equal the kinds `_web_docs.api_docs_page` dispatches on, and M23 is why that reads the
526
- * table the dispatcher actually consults rather than a list beside it: a gate on a
527
- * declaration cannot see a dispatcher that stopped using it.
480
+ * `tests/unit/web_server.test.mjs` requires `Object.keys(KIND_ROUTES) ∪ NOT_PORTED_KINDS` to
481
+ * equal the kinds `apiDocsPage` dispatches on — this table is what the dispatcher actually
482
+ * consults, so a declaration-only check cannot see a dispatcher that stopped using it.
528
483
  */
529
484
  const KIND_ROUTES = {
530
485
  markdown: (state, pageId, page, hn) => {
@@ -552,8 +507,8 @@ const KIND_ROUTES = {
552
507
  about: (state, pageId, page) => ({
553
508
  id: pageId, title: page.title, kind: 'about', ...about(state),
554
509
  }),
555
- // P6d's deferral, paid in P10c. The row is one line because the work was not in the page:
556
- // it was in making `harness.build_argparser()`'s metadata a file both implementations read.
510
+ // The row is one line because the work was not in the page: it was in making the CLI's
511
+ // metadata (`js/cli-table.json`) a file this reads directly.
557
512
  cli: (state, pageId, page) => ({
558
513
  id: pageId, title: page.title, kind: 'cli', ...cliReference(),
559
514
  }),
package/js/web/graph.mjs CHANGED
Binary file