geneseed 3.1.3 → 3.3.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 (150) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +24 -14
  3. package/SETUP.md +38 -7
  4. package/SHIPPED.md +6 -5
  5. package/adapters/bob/README.md +40 -10
  6. package/adapters/claude-code/README.md +5 -0
  7. package/adapters/claude-code/settings.json +8 -1
  8. package/adapters/copilot/README.md +50 -24
  9. package/adapters/opencode/README.md +5 -1
  10. package/adapters/opencode/plugins/geneseed-guard.js +40 -6
  11. package/bin/build-driver.mjs +143 -89
  12. package/bin/geneseed-cli.mjs +63 -106
  13. package/bin/geneseed-hook.mjs +26 -16
  14. package/docs/candidate-hosts.md +74 -0
  15. package/docs/extending.md +6 -5
  16. package/docs/token-footprint.md +32 -21
  17. package/docs/web-ui.md +5 -13
  18. package/harness.config.json +1 -1
  19. package/js/README.md +5 -15
  20. package/js/build/emit-claude.mjs +26 -12
  21. package/js/build/emit-common.mjs +63 -114
  22. package/js/build/emit-opencode.mjs +74 -66
  23. package/js/build/generate.mjs +4 -6
  24. package/js/build/render.mjs +50 -32
  25. package/js/build/source.mjs +29 -8
  26. package/js/build/stubs.mjs +25 -33
  27. package/js/build/themes.mjs +1 -1
  28. package/js/build/version.mjs +1 -3
  29. package/js/cli-table.json +84 -0
  30. package/js/hosts/hooks.mjs +243 -81
  31. package/js/hosts/hosts.mjs +57 -5
  32. package/js/hosts/installs.mjs +35 -28
  33. package/js/hosts/link.mjs +2 -2
  34. package/js/hosts/mcp.mjs +3 -1
  35. package/js/hosts/native.mjs +54 -19
  36. package/js/hosts/opencode.mjs +39 -28
  37. package/js/hosts/settings.mjs +206 -115
  38. package/js/inspect/checks-authoring.mjs +5 -4
  39. package/js/inspect/checks-build.mjs +1 -1
  40. package/js/inspect/diff.mjs +1 -3
  41. package/js/inspect/doctor.mjs +59 -44
  42. package/js/inspect/excludes.mjs +5 -11
  43. package/js/inspect/inventory.mjs +38 -56
  44. package/js/inspect/registry.mjs +4 -7
  45. package/js/inspect/scan.mjs +8 -15
  46. package/js/inspect/status.mjs +51 -3
  47. package/js/lib/fs.mjs +34 -0
  48. package/js/lib/json.mjs +28 -1
  49. package/js/lib/paths.mjs +0 -10
  50. package/js/lib/udiff.mjs +11 -16
  51. package/js/maintain/memory.mjs +2 -4
  52. package/js/maintain/migrate.mjs +3 -3
  53. package/js/maintain/uninstall.mjs +127 -88
  54. package/js/maintain/update.mjs +4 -4
  55. package/js/ui/anim.mjs +1 -1
  56. package/js/ui/menu.mjs +1 -1
  57. package/js/ui/tui.mjs +3 -20
  58. package/js/web/actions.mjs +230 -217
  59. package/js/web/activity.mjs +71 -89
  60. package/js/web/api.mjs +130 -226
  61. package/js/web/daemon.mjs +92 -110
  62. package/js/web/docs.mjs +59 -126
  63. package/js/web/handler.mjs +94 -123
  64. package/js/web/jobs.mjs +116 -178
  65. package/js/web/routes.mjs +47 -192
  66. package/js/web/server.mjs +47 -82
  67. package/package.json +1 -1
  68. package/registry.json +14 -0
  69. package/src/AGENT.md.tmpl +55 -16
  70. package/src/agents/explorer.md +2 -1
  71. package/src/doctrines/README.md +3 -3
  72. package/src/doctrines/process.md +4 -2
  73. package/src/doctrines/rigor.md +13 -0
  74. package/src/laws/universal.md +133 -43
  75. package/src/ontology/universal.md +38 -13
  76. package/src/skills/_template.md +5 -1
  77. package/src/skills/bruno-collection-generator.md +63 -0
  78. package/src/skills/bruno-test-writer.md +62 -0
  79. package/src/skills/clarify.md +1 -1
  80. package/src/skills/crash-course.md +1 -0
  81. package/src/skills/decode.md +1 -0
  82. package/src/skills/drill.md +1 -0
  83. package/src/skills/feynman.md +1 -0
  84. package/src/skills/forge-mcp.md +2 -1
  85. package/src/skills/fresh-eyes.md +1 -1
  86. package/src/skills/herdr.md +1 -0
  87. package/src/skills/ingest.md +2 -1
  88. package/src/skills/learning-path.md +1 -0
  89. package/src/skills/opencode-theme.md +1 -0
  90. package/src/skills/parallel-agents.md +1 -1
  91. package/src/skills/ponytail.md +2 -2
  92. package/src/skills/research.md +4 -2
  93. package/themes/_TEMPLATE.json +5 -0
  94. package/themes/biker.json +5 -0
  95. package/themes/commentator.json +5 -0
  96. package/themes/cyberpunk.json +5 -0
  97. package/themes/gamer.json +5 -0
  98. package/themes/imperial.json +5 -0
  99. package/themes/joker.json +5 -0
  100. package/themes/marvin.json +5 -0
  101. package/themes/mean.json +5 -0
  102. package/themes/military.json +5 -0
  103. package/themes/neutral.json +5 -0
  104. package/themes/pirate.json +5 -0
  105. package/themes/sports.json +5 -0
  106. package/themes/verstappen.json +5 -0
  107. package/themes/wizard.json +5 -0
  108. package/web/dist/assets/Activity-BfVcLrda.js +1 -0
  109. package/web/dist/assets/ActivityDetail-CQXccGPl.js +1 -0
  110. package/web/dist/assets/CatalogRow-DALEQImZ.js +1 -0
  111. package/web/dist/assets/Diff-BIjxXx46.js +5 -0
  112. package/web/dist/assets/Doctor-CJLbgsrw.js +1 -0
  113. package/web/dist/assets/FilterInput-CkUCk_lY.js +1 -0
  114. package/web/dist/assets/GreenhouseView-DfC5IILJ.js +2 -0
  115. package/web/dist/assets/Harness-Dm5CCU-v.js +1 -0
  116. package/web/dist/assets/JournalView-Cpu2eI4s.js +1 -0
  117. package/web/dist/assets/Laws-DDDVFuQz.js +3 -0
  118. package/web/dist/assets/Library-Dao3T9na.js +1 -0
  119. package/web/dist/assets/{marked.esm-JKvyS0Pp.js → Markdown-C__nuWX5.js} +15 -15
  120. package/web/dist/assets/OperatorHudView-DEpCtAjP.js +2 -0
  121. package/web/dist/assets/Profile-BlXtUlh2.js +1 -0
  122. package/web/dist/assets/{Rules-CfsV1EVy.js → Rules-DIre6gxR.js} +1 -1
  123. package/web/dist/assets/Skills-rY13ZPhM.js +1 -0
  124. package/web/dist/assets/{StatusBadge-C9bZOEZk.js → StatusBadge-wW_RS_my.js} +1 -1
  125. package/web/dist/assets/{activity-Cc0n2jwx.js → activity-DpYPKNrA.js} +1 -1
  126. package/web/dist/assets/index-BEZvPvpF.js +1 -0
  127. package/web/dist/assets/index-DNQE5ydy.js +10 -0
  128. package/web/dist/assets/index-DzdQjvM1.css +1 -0
  129. package/web/dist/assets/index-PmQGRS1X.js +2 -0
  130. package/web/dist/assets/jobBuckets-DfTUJUxR.js +1 -0
  131. package/web/dist/assets/lawCats-DUS9UTH4.js +1 -0
  132. package/web/dist/index.html +2 -2
  133. package/web/src/pages/Laws.jsx +119 -130
  134. package/js/web/graph.mjs +0 -0
  135. package/web/dist/assets/Activity-B49fE_ug.js +0 -1
  136. package/web/dist/assets/ActivityDetail-abt7hzD2.js +0 -1
  137. package/web/dist/assets/Diff-D3_bVkih.js +0 -5
  138. package/web/dist/assets/Doctor-hXp7srsx.js +0 -1
  139. package/web/dist/assets/Graph-DvYMtlvl.js +0 -1
  140. package/web/dist/assets/Harness-BIWei7N5.js +0 -1
  141. package/web/dist/assets/Laws-CxmnRdwt.js +0 -3
  142. package/web/dist/assets/Library-MjbOkHIW.js +0 -1
  143. package/web/dist/assets/Markdown-Dx_w0faR.js +0 -1
  144. package/web/dist/assets/Profile-BRFvv2gr.js +0 -1
  145. package/web/dist/assets/Skills-DArA6IHS.js +0 -1
  146. package/web/dist/assets/index-Bx3oxZ1o.css +0 -1
  147. package/web/dist/assets/index-DC3-GlCY.js +0 -1
  148. package/web/dist/assets/index-DP035wPa.js +0 -12
  149. package/web/dist/assets/index-dHi-7M-F.js +0 -2
  150. package/web/dist/assets/roman-DkYN0LX9.js +0 -1
package/js/web/docs.mjs CHANGED
@@ -1,44 +1,21 @@
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`.
10
+ * `tests/unit/web_server.test.mjs` holds `KINDS` against a written-out list of the five
11
+ * kinds this daemon dispatches on — same declaration-vs-dispatch shape as the route table,
12
+ * which is why it is a table here too and not five `if`s.
29
13
  *
30
- * So `NOT_PORTED_KINDS` declares what is left, `api_docs_page` answers 501 for it, and
31
- * `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.
35
- *
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.
14
+ * THE `?harness=` QUERY PARAM IS the Docs selector, and it is the ONLY input to these
15
+ * endpoints that is not the checkout itself — the one thing a test can vary. Every
16
+ * filtering rule below is exercised by sending both values over one page.
40
17
  */
41
- import { readdirSync, statSync } from 'node:fs';
18
+ import { readdirSync } from 'node:fs';
42
19
  import path from 'node:path';
43
20
 
44
21
  import { ROOT, THEMES } from '../build/source.mjs';
@@ -46,18 +23,17 @@ import { cliReference } from '../ui/cli.mjs';
46
23
  import { readJsonMaybe, readMaybe } from '../hosts/installs.mjs';
47
24
  import { resolvePath } from '../hosts/hosts.mjs';
48
25
  import { parseJson } from '../lib/json.mjs';
26
+ import { isFile } from '../lib/fs.mjs';
49
27
  import { normcase } from '../lib/paths.mjs';
50
28
  import { WHITESPACE, stripWhitespace } from '../lib/text.mjs';
51
29
  import { statusData } from '../inspect/status.mjs';
52
30
  import { originDisplay } from '../maintain/update.mjs';
53
31
  import { NotFound, deployed, resolveLinks } from './api.mjs';
54
32
 
55
- const isFile = (p) => { try { return statSync(p).isFile(); } catch { return false; } };
56
-
57
33
  const DOC_DIR = path.join(ROOT, 'docs', 'web');
58
34
 
59
35
  /**
60
- * `_web_core._doc_frontmatter` — a docs page split into its frontmatter map and its body.
36
+ * A docs page split into its frontmatter map and its body.
61
37
  *
62
38
  * Deliberately not YAML: each value is a JSON scalar (or a JSON object for `link`), which
63
39
  * covers every key these pages use and parses with the standard library. A value that is
@@ -77,23 +53,22 @@ export function docFrontmatter(text) {
77
53
  const key = (colon < 0 ? line : line.slice(0, colon)).trim();
78
54
  const raw = (colon < 0 ? '' : line.slice(colon + 1)).trim();
79
55
  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.
56
+ // `parseJson`, so a frontmatter number keeps the int/float distinction the rest of
57
+ // this codebase protects.
82
58
  try { meta[key] = parseJson(raw); } catch { meta[key] = raw; }
83
59
  }
84
60
  return [meta, body];
85
61
  }
86
62
 
87
63
  /**
88
- * `_web_core._load_doc_groups` — the Docs registry, built from `docs/web/`.
64
+ * The Docs registry, built from `docs/web/`.
89
65
  *
90
66
  * A page whose file is missing or malformed is skipped rather than crashing the server; an
91
67
  * unreadable registry yields an empty Docs section, which the UI renders as "no pages".
92
68
  *
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.
69
+ * NOT cached at module load. The difference is invisible to a request — `docs/web/` cannot
70
+ * change under a running daemon any more than `dist/` can — and a module-level constant
71
+ * would freeze whatever `ROOT` was when this file was first imported.
97
72
  */
98
73
  export function docGroups() {
99
74
  const groups = readJsonMaybe(path.join(DOC_DIR, '_groups.json'));
@@ -118,7 +93,7 @@ export function docGroups() {
118
93
  const out = [];
119
94
  for (const g of groups) {
120
95
  const entry = byId.get(g.id);
121
- // `sorted(key=lambda t: t[0])` is STABLE, so equal orders keep filename order.
96
+ // STABLE sort: equal orders keep filename order.
122
97
  entry.pages = entry.pages
123
98
  .map((pair, i) => [pair[0], i, pair[1]])
124
99
  .sort((a, b) => (a[0] - b[0]) || (a[1] - b[1]))
@@ -129,8 +104,8 @@ export function docGroups() {
129
104
  }
130
105
 
131
106
  /**
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.
107
+ * The neutral term, the theme key whose value renames it, and the one-line description. A
108
+ * key of `null` marks a term no theme renames.
134
109
  */
135
110
  const GLOSSARY_KEYS = [
136
111
  // The three tiers, in constitutional order and before the entities — a reader who does not
@@ -170,11 +145,9 @@ const GLOSSARY_KEYS = [
170
145
  // ---- counts substituted into concept bodies -------------------------------------------
171
146
 
172
147
  /**
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.
148
+ * Exported so the tier tokens can be gated on the day they are ADDED rather than on the day
149
+ * a page first spends one. Only one page carries `{N_LAWS}` today; a test that could only
150
+ * reach these through a rendered body would leave most of them untested.
178
151
  */
179
152
  export function docCounts(state) {
180
153
  const inv = state.inventory;
@@ -204,7 +177,6 @@ export function docCounts(state) {
204
177
  };
205
178
  }
206
179
 
207
- /** `_web_docs._sub_counts`. */
208
180
  function subCounts(state, body) {
209
181
  if (!body.includes('{N_')) return body;
210
182
  let out = body;
@@ -226,7 +198,6 @@ const HARNESS_CLOSE_RE = /^\s*<!--\s*\/harness\s*-->\s*$/;
226
198
  */
227
199
  const HARNESS_HINT_RE = /<!--\s*harness:/;
228
200
 
229
- /** `_web_docs._norm_harness`. */
230
201
  export function normHarness(value, state) {
231
202
  const v = (value || '').trim().toLowerCase();
232
203
  if (HARNESSES.includes(v)) return v;
@@ -234,7 +205,7 @@ export function normHarness(value, state) {
234
205
  }
235
206
 
236
207
  /**
237
- * `_web_docs._harness_blocks_balanced` — every open has a matching close, with no nesting.
208
+ * Every open has a matching close, with no nesting.
238
209
  *
239
210
  * A marker inside a ``` fence is example text, not a marker (the same rule `sliceSection`
240
211
  * uses). Its purpose is to FAIL OPEN: an unbalanced marker leaves the body untouched, so a
@@ -262,7 +233,6 @@ export function harnessBlocksBalanced(lines) {
262
233
  return !open;
263
234
  }
264
235
 
265
- /** `_web_docs._strip_harness_blocks`. */
266
236
  export function stripHarnessBlocks(body, harnessName) {
267
237
  if (!HARNESS_HINT_RE.test(body)) return body;
268
238
  const lines = splitLines(body);
@@ -287,9 +257,9 @@ export function stripHarnessBlocks(body, harnessName) {
287
257
  }
288
258
 
289
259
  /**
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`.
260
+ * NOT a bare `split('\n')`: this drops a single trailing newline instead of yielding a
261
+ * final empty string, and that difference reaches the output here because the result is
262
+ * re-joined with `\n`.
293
263
  */
294
264
  function splitLines(s) {
295
265
  const parts = s.split('\n');
@@ -297,7 +267,7 @@ function splitLines(s) {
297
267
  return parts;
298
268
  }
299
269
 
300
- /** `_web_docs._visible_groups` — pure; the registry is never mutated. */
270
+ /** Pure; the registry is never mutated. */
301
271
  function visibleGroups(harnessName) {
302
272
  const groups = [];
303
273
  for (const g of docGroups()) {
@@ -316,14 +286,11 @@ const SLUG_DASH_RE = /-+/g;
316
286
  const HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
317
287
 
318
288
  /**
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.
289
+ * The same rules the frontend's `slug()` uses, so a registry `anchor` written against a
290
+ * heading matches the id the renderer assigns.
321
291
  *
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.
292
+ * `WHITESPACE` and `stripWhitespace` rather than `\s` and `trim()` — see `WHITESPACE`'s own
293
+ * docblock for the measured set of characters where this matters.
327
294
  */
328
295
  export function slugifyHeading(text) {
329
296
  let s = stripWhitespace(text.toLowerCase()).replace(SLUG_STRIP_RE, '');
@@ -332,7 +299,7 @@ export function slugifyHeading(text) {
332
299
  }
333
300
 
334
301
  /**
335
- * `_web_docs._slice_section` — the body trimmed to one heading's section.
302
+ * The body trimmed to one heading's section.
336
303
  *
337
304
  * Returns `[body, ok]`; `ok === false` (with the ORIGINAL body) when the anchor is
338
305
  * missing, so the caller falls back to the whole document. An H1 slice stops at the first
@@ -372,7 +339,6 @@ export function sliceSection(body, anchor) {
372
339
 
373
340
  // ---- the endpoints -----------------------------------------------------------------------
374
341
 
375
- /** `_web_docs._find_doc_page`. */
376
342
  function findDocPage(pageId) {
377
343
  for (const g of docGroups()) {
378
344
  for (const p of g.pages) if (p.id === pageId) return p;
@@ -381,9 +347,9 @@ function findDocPage(pageId) {
381
347
  }
382
348
 
383
349
  /**
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.
350
+ * A markdown file relative to ROOT, guarded against escapes the same way the catalog is. A
351
+ * GET carries no token, so `rel` comes out of the registry, but the guard is what makes
352
+ * that safe to say.
387
353
  */
388
354
  function readDocSource(rel) {
389
355
  const target = resolvePath(path.join(ROOT, rel));
@@ -397,7 +363,7 @@ function within(child, parent) {
397
363
  return p.length <= c.length && p.every((seg, i) => c[i] === seg);
398
364
  }
399
365
 
400
- /** `_web_docs._glossary` — the deployed theme's words beside the neutral ones. */
366
+ /** The deployed theme's words beside the neutral ones. */
401
367
  function glossary(state) {
402
368
  const load = (theme) => {
403
369
  const doc = readJsonMaybe(path.join(THEMES, `${theme}.json`));
@@ -423,12 +389,11 @@ function glossary(state) {
423
389
  }
424
390
 
425
391
  /**
426
- * `_web_docs._about` — the About page: a version line, the deployed install, and the links.
392
+ * The About page: a version line, the deployed install, and the links.
427
393
  *
428
394
  * `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`.
395
+ * what gates the github-shaped deep links in the UI. Both come from `originDisplay()`,
396
+ * which shells out to `git remote get-url` (the `git` row in `ALLOWED_SPAWNS`).
432
397
  *
433
398
  * `version` IS NOT WHAT IT LOOKS LIKE, and it is the shape this page has always had rather than
434
399
  * a shortcut here. It reads a `version` key off the status snapshot and THERE IS NO SUCH KEY —
@@ -436,8 +401,8 @@ function glossary(state) {
436
401
  * field is `{}`, always, and `sd.version || {}` reproduces the EXPRESSION rather than the
437
402
  * constant: if the key ever appears, the page gains it without another edit.
438
403
  *
439
- * THERE IS NO INTERPRETER FIELD, and the absence is deliberate — see `apiSetup`, which spells
440
- * the same absence for the same reason.
404
+ * THERE IS NO INTERPRETER FIELD, and the absence is deliberate — see `apiSetup` in
405
+ * `api.mjs`, which spells the same absence for the same reason.
441
406
  *
442
407
  * `statusData()` is called for one dead field, and deliberately: this page has always paid that
443
408
  * cost, and skipping it would answer faster in a way no gate measures and no user asked for.
@@ -453,41 +418,16 @@ function about(state) {
453
418
  target: String(state.target),
454
419
  root: String(ROOT),
455
420
  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.
421
+ // `githubSlug`, not `github_slug` — the record itself is camelCase like every other
422
+ // object in this codebase; the RESPONSE key below is snake_case because that is the
423
+ // wire format. Reading the wrong spelling off the record gives `undefined`, which is a
424
+ // perfectly plausible `false`: the About page would silently drop every github deep
425
+ // link and nothing else would change.
462
426
  repo_is_github: Boolean(od.githubSlug),
463
427
  license: 'MIT',
464
428
  };
465
429
  }
466
430
 
467
- /**
468
- * EMPTY SINCE P10c, AND KEPT DECLARED — the third partition in this port to reach that state,
469
- * after `NOT_PORTED_POST` and `NOT_PORTED_ACTIONS`.
470
- *
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.
477
- *
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`.
487
- */
488
- export const NOT_PORTED_KINDS = new Set();
489
-
490
- /** `_web_docs.api_docs`. */
491
431
  export function apiDocs(state, harnessName = null) {
492
432
  const hn = normHarness(harnessName, state);
493
433
  const groups = visibleGroups(hn).map((g) => ({
@@ -499,8 +439,6 @@ export function apiDocs(state, harnessName = null) {
499
439
  }
500
440
 
501
441
  /**
502
- * `_web_docs.api_docs_page`.
503
- *
504
442
  * The lookup deliberately ignores a page's own `harness` tag: a deep link to a page the
505
443
  * active harness hides still resolves, and the client redirects it out of view.
506
444
  */
@@ -510,21 +448,16 @@ export function apiDocsPage(state, pageId, harnessName = null) {
510
448
  const hn = normHarness(harnessName, state);
511
449
  const render = KIND_ROUTES[page.kind];
512
450
  if (render !== undefined) return render(state, pageId, page, hn);
513
- if (NOT_PORTED_KINDS.has(page.kind)) {
514
- const e = new Error(`not ported yet: docs kind '${page.kind}'`);
515
- e.notPorted = true;
516
- throw e;
517
- }
518
451
  throw new NotFound(pageId);
519
452
  }
520
453
 
521
454
  /**
522
- * The three kinds that cross, as a TABLE and not three `if`s.
455
+ * The five kinds, as a TABLE and not five `if`s.
523
456
  *
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.
457
+ * `tests/unit/web_server.test.mjs` holds `Object.keys(KIND_ROUTES)` against a written-out
458
+ * list of the kinds `apiDocsPage` must dispatch on — this table is what the dispatcher
459
+ * actually consults, so a declaration-only check cannot see a dispatcher that stopped
460
+ * using it.
528
461
  */
529
462
  const KIND_ROUTES = {
530
463
  markdown: (state, pageId, page, hn) => {
@@ -552,11 +485,11 @@ const KIND_ROUTES = {
552
485
  about: (state, pageId, page) => ({
553
486
  id: pageId, title: page.title, kind: 'about', ...about(state),
554
487
  }),
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.
488
+ // The row is one line because the work was not in the page: it was in making the CLI's
489
+ // metadata (`js/cli-table.json`) a file this reads directly.
557
490
  cli: (state, pageId, page) => ({
558
491
  id: pageId, title: page.title, kind: 'cli', ...cliReference(),
559
492
  }),
560
493
  };
561
494
 
562
- export const PORTED_KINDS = Object.keys(KIND_ROUTES);
495
+ export const KINDS = Object.keys(KIND_ROUTES);