@polycode-projects/the-mechanical-code-talker 1.9.2 → 1.10.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 (80) hide show
  1. package/README.md +441 -202
  2. package/bin/tmct.mjs +126 -1
  3. package/package.json +4 -2
  4. package/src/answer-variants.mjs +8 -36
  5. package/src/ask-browser-entry.mjs +5 -23
  6. package/src/ask-browser.bundle.js +1 -2
  7. package/src/ask-nlp.mjs +9 -23
  8. package/src/ask-vocab.mjs +139 -589
  9. package/src/ask.mjs +627 -1729
  10. package/src/chat.mjs +1684 -2872
  11. package/src/cli-args.mjs +14 -28
  12. package/src/codegraph.mjs +236 -644
  13. package/src/completions/complete.mjs +18 -62
  14. package/src/completions/graph-adapter.mjs +14 -60
  15. package/src/completions/group.mjs +12 -68
  16. package/src/completions/infer.mjs +38 -126
  17. package/src/completions/prune.mjs +17 -70
  18. package/src/completions/rank.mjs +16 -69
  19. package/src/completions/search.mjs +8 -31
  20. package/src/concept.mjs +32 -88
  21. package/src/conformance.mjs +11 -15
  22. package/src/corpus/conceptnet.mjs +31 -89
  23. package/src/corpus/templates.mjs +19 -45
  24. package/src/corpus/unknown-ingest.mjs +31 -92
  25. package/src/embed.mjs +10 -22
  26. package/src/extensions.mjs +50 -154
  27. package/src/finish.mjs +35 -91
  28. package/src/grammar/ace.mjs +16 -40
  29. package/src/grammar/assert.mjs +1 -1
  30. package/src/grammar/lexicon-core.json +1 -1
  31. package/src/grammar/lexicon.mjs +9 -27
  32. package/src/graph-merge.mjs +2 -3
  33. package/src/hash.mjs +6 -14
  34. package/src/index.mjs +6 -10
  35. package/src/init.mjs +38 -125
  36. package/src/interpret/fuzzy.mjs +10 -29
  37. package/src/interpret/merge.mjs +9 -27
  38. package/src/interpret/normalize.mjs +137 -585
  39. package/src/interpret/pipeline.mjs +23 -71
  40. package/src/interpret/strategies/ace.mjs +7 -31
  41. package/src/interpret/strategies/constructions.mjs +14 -41
  42. package/src/interpret/strategies/grammar.mjs +21 -60
  43. package/src/interpret/strategies/keywords.mjs +42 -131
  44. package/src/interpret/strategies/noise-strip.mjs +18 -89
  45. package/src/memory/bias.mjs +11 -54
  46. package/src/memory/blocks.mjs +18 -69
  47. package/src/memory/core.mjs +171 -591
  48. package/src/memory/fold.mjs +0 -0
  49. package/src/memory/inspect.mjs +7 -25
  50. package/src/memory/shacl.mjs +10 -39
  51. package/src/memory/trust.mjs +26 -127
  52. package/src/memory-ask-browser-entry.mjs +7 -30
  53. package/src/memory-ask-browser.bundle.js +1 -1
  54. package/src/paraphrase.mjs +20 -53
  55. package/src/planning.mjs +15 -157
  56. package/src/prose-nlp.mjs +4 -17
  57. package/src/prose.mjs +19 -67
  58. package/src/providers/bootstrap.mjs +1 -2
  59. package/src/providers/fixture.mjs +1 -2
  60. package/src/providers/graph-service.mjs +28 -59
  61. package/src/repository-interface.mjs +6 -8
  62. package/src/router/drive.mjs +183 -0
  63. package/src/router/goal-reasoner.mjs +66 -231
  64. package/src/router/guardrail.mjs +20 -58
  65. package/src/router/planner.mjs +15 -46
  66. package/src/router/registry.mjs +13 -43
  67. package/src/router/resolver.mjs +46 -131
  68. package/src/router/results.mjs +231 -0
  69. package/src/schema-docs.mjs +10 -27
  70. package/src/server-http.mjs +10 -19
  71. package/src/server.mjs +22 -28
  72. package/src/sessions.mjs +15 -30
  73. package/src/source-slice.mjs +5 -7
  74. package/src/source.mjs +10 -20
  75. package/src/syllogise.mjs +187 -575
  76. package/src/telemetry.mjs +3 -3
  77. package/src/toml-config.mjs +4 -4
  78. package/src/tui/app.mjs +9 -19
  79. package/src/viz.mjs +66 -123
  80. package/src/wink-model.mjs +10 -24
package/src/telemetry.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // telemetry.mjs — OPT-IN, fire-and-forget query telemetry (PLAN_TMCT_TELEMETRY.md).
1
+ // telemetry.mjs — OPT-IN, fire-and-forget query telemetry.
2
2
  //
3
3
  // The measurement contract is absolute: telemetry is OFF by default and the OFF path
4
4
  // must be BYTE-IDENTICAL — no file, no stdout/stderr change, ~zero cost. createTelemetry
@@ -16,8 +16,8 @@ import { uuidv7 } from "./uuid.mjs";
16
16
 
17
17
  /** Field names whose VALUES are (or embed) raw source and must never be logged. `body` is
18
18
  * the Repository Interface's own field name for real source text (snippet()/context()'s
19
- * source-capable body sections, PLAN item 2/3) — without it, raw source read via the
20
- * now-source-capable createGraphService could leak straight into a telemetry log. */
19
+ * source-capable body sections) — without it, raw source read via a source-capable
20
+ * createGraphService could leak straight into a telemetry log. */
21
21
  const DROP_KEYS = new Set(["text", "content", "snippet", "body"]);
22
22
  /** String fields longer than this are truncated — except query.raw (the user's own
23
23
  * question, which is the correlation key and is not file content). */
@@ -88,14 +88,14 @@ export async function normalizeConfig(raw, { configDir } = {}) {
88
88
  cfg.outRoot = resolve(dir, String(src.out_root));
89
89
  }
90
90
 
91
- // `tmct init` onboarding keys (ROADMAP Phase 8). Sparse like the rest: only a
91
+ // `tmct init` onboarding keys. Sparse like the rest: only a
92
92
  // key actually present appears, so "unset" stays distinguishable from "set to
93
93
  // the default". `graph_file` is resolved against configDir to match outRoot.
94
94
  if (src.graph_file !== undefined) {
95
95
  cfg.graphFile = resolve(dir, String(src.graph_file));
96
96
  }
97
- // `graph_files` (array, multi-graph the CLI/config unification batch):
98
- // sits ALONGSIDE `graph_file`, never replaces it. A single-element array is
97
+ // `graph_files` (array, multi-graph): sits ALONGSIDE `graph_file`, never
98
+ // replaces it. A single-element array is
99
99
  // legal but `graph_file` stays the byte-identical single-graph path most
100
100
  // repos use; `graph_files` only matters once it names more than one file.
101
101
  if (src.graph_files !== undefined) {
@@ -161,7 +161,7 @@ export async function normalizeConfig(raw, { configDir } = {}) {
161
161
  // DEFAULT_RETENTION) is applied where the value is actually CONSUMED
162
162
  // (snapshotMemory), not injected here.
163
163
  //
164
- // [memory] backend — the storage-backend seam (PLAN_SEED.md §6, createSession's
164
+ // [memory] backend — the storage-backend seam (createSession's
165
165
  // `memoryBackend` option / TMCT_MEMORY_BACKEND env): "default" (the flat
166
166
  // OWL-labelled JSON file under .tmct/memory/), "memory" (in-process only,
167
167
  // nothing on disk), or "sqlite" (a local SQLite file). Written by `tmct init
package/src/tui/app.mjs CHANGED
@@ -1,26 +1,16 @@
1
- // tui/app.mjs — the Ink full-screen chat shell (the default on a TTY).
1
+ // tui/app.mjs — the Ink full-screen chat shell (the default on a TTY): an
2
+ // alternate-screen layout with a scrolling transcript pane, a bottom input line, and a
3
+ // status bar (repo, module count, session id).
2
4
  //
3
- // The claude-code feel: an alternate-screen, full-height layout with a scrolling
4
- // transcript pane (each visitor line echoed under the prompt it was typed at,
5
- // the answer below it), a bottom input line carrying the live `tmct> ` /
6
- // `tmct(label)> ` focus prompt, and a thin status bar (repo · module count ·
7
- // session id · an honest "no graph — starting empty" when bootstrapping).
5
+ // Every turn goes through the SAME createSession sink (src/chat.mjs) the plain readline
6
+ // shell uses transcript log, sidecar, graph upsert, and memory side-write are
7
+ // byte-identical to `--plain`; only the screen drawing differs.
8
8
  //
9
- // EVERY turn goes through the SAME createSession sink (src/chat.mjs) the plain
10
- // readline shell uses the transcript log, structured sidecar, per-turn graph
11
- // upsert and memory side-write are byte-identical to `--plain`; only the
12
- // screen drawing differs. Slash-commands work unchanged (they're session.turn's
13
- // job); `/exit` and a conversational "bye" end the session; Ctrl+C exits
14
- // cleanly through the same close() (Ink's exitOnCtrlC → waitUntilExit → close).
15
- //
16
- // Library decision (ROADMAP Phase 1 shell work): Ink 7 + React 19 — plain Node
17
- // ESM, no JSX/build step (React.createElement throughout). OpenTUI was ruled
18
- // out for now: @opentui/core depends on Bun FFI (bun-ffi-structs / a native Zig
19
- // renderer), so it doesn't run under plain Node; revisit when it does.
9
+ // Library decision: Ink 7 + React 19, no JSX/build step. OpenTUI was ruled out:
10
+ // @opentui/core depends on Bun FFI, so it doesn't run under plain Node.
20
11
  //
21
12
  // The view-model is PURE and exported (statusText, appendTurn, transcriptLines,
22
- // wrapLines) so node:test exercises it without a terminal; the component tree
23
- // is thin glue over it.
13
+ // wrapLines) so node:test exercises it without a terminal.
24
14
 
25
15
  import React, { useEffect, useState } from "react";
26
16
  import { render, Box, Text, useApp, useInput, useStdout } from "ink";
package/src/viz.mjs CHANGED
@@ -1,15 +1,12 @@
1
1
  // viz.mjs — `tmct viz`: a real, navigable, self-contained HTML graph view over
2
- // the memory graph (PLAN_BREADTH_FIRST_NLU.md §5, design per PLAN_VIZ.md), now
3
- // with a real "Ask the graph" chat panel running tmct's OWN engine client-side
4
- // (§5 follow-on, operator directive 2026-07-11 — precedent: seonix's own
5
- // site/viz.mjs + scripts/build-ask-bundle.mjs, adapted here to bundle tmct's
6
- // ask.mjs directly rather than an external package import).
2
+ // the memory graph, with a real "Ask the graph" chat panel running tmct's OWN
3
+ // engine client-side (adapted to bundle tmct's ask.mjs directly rather than
4
+ // an external package import).
7
5
  //
8
6
  // Three pure/impure-separated pieces, mirroring src/syllogise.mjs's shape:
9
7
  // - computeVizGraph(repoDir, {focus}) — I/O (loadMemory) + graph traversal,
10
8
  // reusing spiralExpand/mostRecentIndividual/MEMORY_SPIRAL_EXPAND_KINDS/
11
- // buildVizNodesAndEdges exactly as PLAN_VIZ.md's own traversal work
12
- // already generalized them for this. No new traversal logic here.
9
+ // buildVizNodesAndEdges. No new traversal logic here.
13
10
  // - renderVizHtml({nodes, edges, focus, payload, askBundle}) — a pure
14
11
  // string-builder: one complete <!doctype html> document, graph data
15
12
  // JSON-embedded inline, the real ask-engine bundle inlined verbatim, no
@@ -27,66 +24,35 @@ import { readFile } from "node:fs/promises";
27
24
  import { fileURLToPath } from "node:url";
28
25
  import { dirname, join } from "node:path";
29
26
 
30
- // PLAN_VIZ_MEMORY.md's page-size strategy: seonix's own three-cap default (200)
31
- // is the starting point, raised from the code-graph-only SPIRAL_NODE_LIMIT_DEFAULT
32
- // (12 — a MID-tier digest breadth, not a graph-viewer breadth) now that Bug 2's
33
- // fix makes real concept-relation edges walkable, and re-tuned against a real
34
- // `init:large`-seeded repo this session (see HANDOVER.md for the measured numbers).
27
+ // The page-size strategy: a three-cap default (200 base, tuned up to 300 here),
28
+ // raised from the code-graph-only SPIRAL_NODE_LIMIT_DEFAULT (12 — a MID-tier
29
+ // digest breadth, not a graph-viewer breadth) now that real concept-relation
30
+ // edges are walkable.
35
31
  export const VIZ_NODE_LIMIT_DEFAULT = 300;
36
- export const VIZ_HUB_DEGREE_DEFAULT = 40; // seonix's own default, ported verbatim (PLAN_VIZ_MEMORY.md)
37
- export const VIZ_DEPTH_DEFAULT = 3; // mirrors spiralExpand's own SPIRAL_DEPTH_DEFAULT — named here so client-side re-walks embed/reuse the SAME resolved value, never spiralExpand's smaller code-graph defaults
38
-
39
- // edgeKindsFor now lives in codegraph.mjs (re-exported here for existing
40
- // importers) it moved so the browser bundle's client-side re-walk (which
41
- // can't import viz.mjs itself, a real-fs-I/O module) can share the SAME
42
- // kind-combination logic instead of a second hand-rolled copy. See
43
- // codegraph.mjs's own doc comment on edgeKindsFor for the full reasoning.
32
+ export const VIZ_HUB_DEGREE_DEFAULT = 40;
33
+ export const VIZ_DEPTH_DEFAULT = 3; // mirrors spiralExpand's own SPIRAL_DEPTH_DEFAULT
34
+
35
+ // edgeKindsFor lives in codegraph.mjs; re-exported here so the browser bundle's
36
+ // client-side re-walk (which can't import this fs-touching module) can share it.
44
37
  export { edgeKindsFor };
45
38
 
46
- /** Load the memory graph under `repoDir`, walk it from a seed, and enrich each
47
- * walked node with the real label/class/timestamp data a renderer needs.
48
- * Seed precedence: an explicit `--focus <id>`, then `--term <word>` (Bug 2's
49
- * companion seed strategy resolves via `normFactTerm` to the synthetic
50
- * `term:<word>` node deriveFactTermGraph materializes, so `tmct viz --term
51
- * dog` reaches "dog"'s WHOLE concept neighbourhood every Fact mentioning
52
- * it, and everything THOSE facts connect to without hunting for a raw
53
- * `fact:<hash>` id first; a term that matches no Fact falls through to the
54
- * default below rather than seeding a lone phantom node), then
55
- * `mostRecentIndividual` by default.
56
- *
57
- * Bug 2 fix: the walk runs over BOTH the existing meta/provenance kinds
58
- * (`MEMORY_SPIRAL_EXPAND_KINDS`) AND the real concept-relation kinds
59
- * (`deriveFactTermGraph`'s per-predicate + link kinds) together by default
60
- * (`edgeKindMode: "both"`) — a click on "dog" now reaches "animal"/"tail"/
61
- * "bark" the same way asking about it in chat would surface those same
62
- * facts, not just its provenance chain. `edgeKindMode: "meta"` reproduces
63
- * today's exact byte-identical provenance-only walk (never deleted, just no
64
- * longer the only option); `"relation"` isolates the concept view alone.
65
- *
66
- * `hubDegree` (seonix's third cap, PLAN_VIZ_MEMORY.md): stop expanding
67
- * THROUGH a node with more than this many connections (still shows the hub
68
- * itself) — without it a common hypernym could swallow the whole node
69
- * budget in one hop. `VIZ_HUB_DEGREE_DEFAULT` applies when omitted.
70
- *
71
- * Returns `{nodes, edges, focus, payload, legend}` — `focus` is the seed id
72
- * actually used (null when the graph is empty and no seed could be picked);
73
- * `payload` is the FULL raw graph (every individual, not just the walked
74
- * subset) — the embedded "Ask the graph" panel queries the whole graph and
75
- * can re-walk from a new focus, never just the initially rendered subgraph,
76
- * mirroring seonix's own "never the depth-limited display sub-graph"
77
- * precedent; `legend` is `pickLegendDimension`'s precomputed output over the
78
- * walked node set. Never throws on a missing/empty memory dir: loadMemory's
79
- * own ENOENT fallback (emptyMemory()) already degrades to zero individuals,
80
- * which this function turns into `{nodes: [], edges: [], focus: null,
81
- * payload, legend: null}`. */
39
+ /** Load the memory graph under `repoDir`, walk it from a seed, and enrich each walked node
40
+ * with the real label/class/timestamp data a renderer needs.
41
+ * Seed precedence: `--focus <id>`, then `--term <word>` (via deriveFactTermGraph's
42
+ * synthetic `term:<word>` node, reaching the term's whole concept neighbourhood), then
43
+ * `mostRecentIndividual`.
44
+ * `edgeKindMode`: "both" (default) walks meta/provenance kinds AND real concept-relation
45
+ * kinds together; "meta" is provenance-only; "relation" isolates the concept view.
46
+ * `hubDegree`: stop expanding THROUGH a node with more connections than this (still shows
47
+ * the hub itself), so a common hypernym can't swallow the whole node budget in one hop.
48
+ * Returns `{nodes, edges, focus, payload, legend}` `payload` is the FULL raw graph (the
49
+ * "Ask the graph" panel can re-walk from a new focus over it). Never throws on a
50
+ * missing/empty memory dir. */
82
51
  export async function computeVizGraph(repoDir, { focus, term, depth, nodeLimit, hubDegree, edgeKindMode = "both" } = {}) {
83
52
  const payload = await loadMemory(repoDir);
84
53
  const graph = parseEntities(payload);
85
- // walkOpts: the RESOLVED options actually used (defaults filled in) always
86
- // present, even on an empty/seedless graph, so renderVizHtml can embed them
87
- // for the client-side re-walk (recentre/edge-kind toggle) to stay consistent
88
- // with whatever this page was generated with, rather than silently falling
89
- // back to spiralExpand's own much-smaller code-graph defaults (nodeLimit 12).
54
+ // Always resolved (defaults filled in), so renderVizHtml can embed it for a
55
+ // client-side re-walk to stay consistent with how this page was generated.
90
56
  const walkOpts = {
91
57
  depth: depth != null ? depth : VIZ_DEPTH_DEFAULT,
92
58
  nodeLimit: nodeLimit != null ? nodeLimit : VIZ_NODE_LIMIT_DEFAULT,
@@ -122,12 +88,9 @@ export async function computeVizGraph(repoDir, { focus, term, depth, nodeLimit,
122
88
  return { nodes, edges, focus: seedId, payload, legend, walkOpts };
123
89
  }
124
90
 
125
- /** Read the checked-in browser ask-engine bundle (scripts/build-ask-bundle.mjs's
126
- * output, `src/ask-browser.bundle.js`) the one I/O `renderVizHtml` itself
127
- * stays free of, keeping it a pure string-builder. Returns `""` (never
128
- * throws) if the bundle hasn't been built yet — `renderVizHtml` renders a
129
- * graph-only page with an honest "chat unavailable" note in that case,
130
- * rather than a broken page. */
91
+ /** Read the checked-in browser ask-engine bundle (`src/ask-browser.bundle.js`). Returns
92
+ * `""`, never throws, if the bundle hasn't been built renderVizHtml then renders a
93
+ * graph-only page with an honest "chat unavailable" note. */
131
94
  export async function readAskBundle() {
132
95
  try {
133
96
  const here = dirname(fileURLToPath(import.meta.url));
@@ -137,13 +100,9 @@ export async function readAskBundle() {
137
100
  }
138
101
  }
139
102
 
140
- /** Read the checked-in browser MEMORY-ask-engine bundle
141
- * (scripts/build-ask-bundle.mjs's second output,
142
- * `src/memory-ask-browser.bundle.js`) Bug 1 fix's real memory-graph answer
143
- * engine (chat.mjs's factAnswer), alongside the pre-existing code-graph
144
- * ask.mjs bundle readAskBundle() already reads. Same never-throws contract:
145
- * `""` when the bundle hasn't been built yet, so renderVizHtml degrades to
146
- * whichever engine (if any) IS present rather than a broken page. */
103
+ /** Read the checked-in browser memory-ask-engine bundle (`src/memory-ask-browser.bundle.js`)
104
+ * — the real memory-graph answer engine (chat.mjs's factAnswer). Same never-throws
105
+ * contract as readAskBundle(). */
147
106
  export async function readMemoryAskBundle() {
148
107
  try {
149
108
  const here = dirname(fileURLToPath(import.meta.url));
@@ -169,22 +128,12 @@ function embedJson(value) {
169
128
  }
170
129
 
171
130
  /** Render one complete, self-contained `<!doctype html>` document for
172
- * `{nodes, edges, focus, payload, askBundle}` (computeVizGraph's return
173
- * shape, plus the ask-engine bundle text from readAskBundle()): the graph
174
- * data JSON-embedded inline, the real ask.mjs engine inlined verbatim
175
- * (`askBundle`, adapter-less no wink model, ~220KB, still answers via the
176
- * curated + bounded-fuzzy tiers, exactly test/ask-nlp.test.mjs's own proven
177
- * "viewer bundle without wink" boundary), inline <style>, inline vanilla-JS
178
- * implementing a concentric ring layout keyed on hop (PLAN_VIZ.md §4 —
179
- * seed/newest at the centre, each ring one hop further out), paint-order-by-
180
- * hop with a lightness/opacity falloff for the depth read, pan (drag) + zoom
181
- * (wheel), click-a-node for details, a depth stepper + per-class visibility
182
- * filters (operator directive, seonix precedent), and a real "Ask the graph"
183
- * chat panel — a query resolves via the SAME ask() the CLI ships, re-walks
184
- * the graph from the resolved entity (focus-follows-answer), and a node's
185
- * class/label in the detail panel are click-to-query affordances. Pure
186
- * string building — no fs/network, no external <script src>, no CDN, no
187
- * fonts. */
131
+ * `{nodes, edges, focus, payload, askBundle}` (computeVizGraph's return shape, plus the
132
+ * ask-engine bundle text from readAskBundle()): graph data JSON-embedded inline, the real
133
+ * ask.mjs engine inlined verbatim, inline <style>, inline vanilla-JS implementing a
134
+ * concentric ring layout keyed on hop, pan/zoom, click-a-node details, visibility filters,
135
+ * and a real "Ask the graph" chat panel (focus-follows-answer). Pure string building — no
136
+ * fs/network, no external <script src>, no CDN, no fonts. */
188
137
  export function renderVizHtml({ nodes, edges, focus, payload, askBundle, memoryAskBundle, legend, walkOpts }) {
189
138
  const graphJson = embedJson({ nodes, edges, focus });
190
139
  const payloadJson = embedJson(payload || { individuals: [], objectProperties: [] });
@@ -316,7 +265,7 @@ ${hasMemChat ? `<script>\n${memoryAskBundle}\n</script>` : ""}
316
265
  var hasEngine = typeof tmctViz !== "undefined";
317
266
  var hasMemEngine = typeof tmctMemoryAsk !== "undefined";
318
267
  var FULL_GRAPH = hasEngine ? tmctViz.parseEntities(PAYLOAD) : null;
319
- // The term-relation view (Bug 2 fix) over the FULL graph, computed once —
268
+ // The term-relation view over the FULL graph, computed once —
320
269
  // recentre()/edge-kind-toggle re-walks reuse it rather than re-deriving it
321
270
  // per click. hasEngine-gated: the walk/legend exports only exist in the
322
271
  // ask-browser bundle, not the memory-ask one.
@@ -369,8 +318,7 @@ ${hasMemChat ? `<script>\n${memoryAskBundle}\n</script>` : ""}
369
318
  }
370
319
  renderTypeFilters();
371
320
 
372
- // ---- legend-as-filter (PLAN_VIZ_MEMORY.md "Auto-picking the filter/legend
373
- // dimension"): LEGEND.primary names the server's auto-picked dimension
321
+ // ---- legend-as-filter: LEGEND.primary names the server's auto-picked dimension
374
322
  // (class/predicate/provenance, scored by normalized Shannon entropy over
375
323
  // the INITIAL walk); the dropdown lets a user switch dimension without
376
324
  // regenerating the page. Bucket COUNTS are always recomputed live over the
@@ -466,8 +414,8 @@ ${hasMemChat ? `<script>\n${memoryAskBundle}\n</script>` : ""}
466
414
 
467
415
  // degree over the CURRENTLY displayed edge set (hub-hide/beam-prune are
468
416
  // display-time filters, distinct from the generation-time hubDegree cap
469
- // which only stops the WALK expanding through a hub — both useful, see
470
- // PLAN_VIZ_MEMORY.md's Controls section). Memoized: draw() runs on every
417
+ // which only stops the WALK expanding through a hub — both useful).
418
+ // Memoized: draw() runs on every
471
419
  // pan/zoom/hover mousemove, and both draw() and visibleNodeIds() (which
472
420
  // draw() itself calls) each need it — recomputing an O(edges) map twice per
473
421
  // frame during a drag is real, avoidable per-frame cost. Invalidated by
@@ -602,12 +550,11 @@ ${hasMemChat ? `<script>\n${memoryAskBundle}\n</script>` : ""}
602
550
  return { x: (sx * dpr - cx) / (view.scale * dpr), y: (sy * dpr - cy) / (view.scale * dpr) };
603
551
  }
604
552
 
605
- // Label-density modes (PLAN_VIZ_MEMORY.md Controls): "smart" (seonix's own
606
- // default) draws a label only for the focus/selection/direct-neighbours/
607
- // top-20-by-degree; everything else labels on hover only. "all"/"name-source"
608
- // always draw (name-source appends the Fact's own provenance prefix — trust
609
- // tier is a first-class concept here unlike seonix's code graph, so this
610
- // variant has no seonix equivalent). "none" draws no labels at all.
553
+ // Label-density modes: "smart" (the default) draws a label only for the
554
+ // focus/selection/direct-neighbours/top-20-by-degree; everything else
555
+ // labels on hover only. "all"/"name-source" always draw (name-source
556
+ // appends the Fact's own provenance prefix — trust tier is a first-class
557
+ // concept here). "none" draws no labels at all.
611
558
  var hoverId = null;
612
559
  canvas.addEventListener("mousemove", function (ev) {
613
560
  if (labelMode !== "smart" || dragging) return;
@@ -747,7 +694,7 @@ ${hasMemChat ? `<script>\n${memoryAskBundle}\n</script>` : ""}
747
694
  }
748
695
  document.getElementById("resetview").addEventListener("click", function () { fitToVisible(); draw(); });
749
696
 
750
- // ---- recentre: RE-WALK the FULL graph (via TERM_GRAPH — Bug 2's augmented
697
+ // ---- recentre: RE-WALK the FULL graph (via TERM_GRAPH — the augmented
751
698
  // view, so a recentre reaches real concept-relation edges the same way
752
699
  // generation-time computeVizGraph does) from a new seed via the real,
753
700
  // bundled spiralExpand (byte-identical to the CLI's own walk — never a
@@ -832,14 +779,13 @@ ${hasMemChat ? `<script>\n${memoryAskBundle}\n</script>` : ""}
832
779
  document.getElementById("panelClose").addEventListener("click", function () {
833
780
  panel.classList.remove("show"); selectedId = null; draw();
834
781
  });
835
- // Class badge: a click-to-query affordance (operator directive) isolate
836
- // this class in the type-filter row (a REAL "show all of that kind
837
- // currently in view" action; ask.mjs has no generic "list all X of class
838
- // Y" shape for memory-graph classes — that richer machinery lives in
839
- // chat.mjs's factAnswer cascade, out of this bundle's ask.mjs-only scope,
840
- // see PLAN_BREADTH_FIRST_NLU.md §5's own build notes) AND fire a real
841
- // "where is X mentioned" query on the class name — an honest attempt,
842
- // may miss, never faked.
782
+ // Class badge: a click-to-query affordance isolate this class in the
783
+ // type-filter row (a REAL "show all of that kind currently in view"
784
+ // action; ask.mjs has no generic "list all X of class Y" shape for
785
+ // memory-graph classes — that richer machinery lives in chat.mjs's
786
+ // factAnswer cascade, out of this bundle's ask.mjs-only scope) AND fire a
787
+ // real "where is X mentioned" query on the class name — an honest
788
+ // attempt, may miss, never faked.
843
789
  var classLink = document.getElementById("classLink");
844
790
  if (classLink) classLink.addEventListener("click", function () {
845
791
  typeFiltersEl.querySelectorAll("label.typechk").forEach(function (lbl) {
@@ -889,20 +835,19 @@ ${hasMemChat ? `<script>\n${memoryAskBundle}\n</script>` : ""}
889
835
  });
890
836
 
891
837
  // ---- Ask the graph: TWO real engines over the FULL graph, never just the
892
- // currently-displayed subgraph (Bug 1 fix, PLAN_VIZ_MEMORY.md) tried in
893
- // order per query:
838
+ // currently-displayed subgraph tried in order per query:
894
839
  // 1. tmctMemoryAsk.factAnswer (src/chat.mjs's REAL memory-graph answer
895
840
  // engine, the same one 'npm run chat' uses) — a Fact/definition-shaped
896
841
  // question ("what is a dog", "what is a horse used for") answers HERE,
897
- // which the code-graph engine below always missed on (Bug 1's whole
898
- // point). Given an in-memory Backend-B handle carrying the page's own
899
- // embedded PAYLOAD — ZERO fs I/O (see memory-ask-browser-entry.mjs's own
900
- // doc comment) — and envelope:null/miss:true, the exact documented
901
- // "no parse pipeline available" bootstrap path that arms factAnswer's
902
- // own bare-question regex fallbacks.
842
+ // which the code-graph engine below cannot. Given an in-memory
843
+ // Backend-B handle carrying the page's own embedded PAYLOAD — ZERO fs
844
+ // I/O (see memory-ask-browser-entry.mjs's own doc comment) — and
845
+ // envelope:null/miss:true, the exact documented "no parse pipeline
846
+ // available" bootstrap path that arms factAnswer's own bare-question
847
+ // regex fallbacks.
903
848
  // 2. tmctViz.ask (tmct's code-graph query engine) — generic "where is X
904
- // mentioned" navigation and code-graph queries, unchanged from before
905
- // this session. Only reached when (1) is unavailable or didn't hit.
849
+ // mentioned" navigation and code-graph queries. Only reached when (1)
850
+ // is unavailable or didn't hit.
906
851
  // A resolved answer's own target re-centres the view — focus follows the
907
852
  // answer — for EITHER engine. --------------------------------------------
908
853
  var askInput = document.getElementById("askq");
@@ -988,9 +933,7 @@ ${hasMemChat ? `<script>\n${memoryAskBundle}\n</script>` : ""}
988
933
  askOut.innerHTML = '<div class="q">&quot;' + esc(query) + '&quot;</div>' + esc(t.content) + canon;
989
934
  // Focus-follows-answer: frame EVERY real match this answer resolved to
990
935
  // (envelope.matches is already the full candidate list ask.mjs itself
991
- // ranked previously only matches[0] recentred, silently dropping the
992
- // rest of a multi-match answer's own result set), never a guess beyond
993
- // what the engine itself actually returned.
936
+ // ranked), never a guess beyond what the engine itself actually returned.
994
937
  var targetIds = (envelope.matches || []).map(function (m) { return m.id; }).filter(Boolean);
995
938
  frameQueryResult(targetIds);
996
939
  draw();
@@ -1,27 +1,13 @@
1
- // wink-model.mjs — the ONE place tmct loads the wink-nlp engine + model.
1
+ // wink-model.mjs — the ONE place tmct loads the wink-nlp engine + model, shared by
2
+ // ask-nlp.mjs and prose-nlp.mjs.
2
3
  //
3
- // Two adapters sit on top of this leaf loader: ask-nlp.mjs (lemma/POS tier for the
4
- // ask engine) and prose-nlp.mjs (lemma layer for the prose index). They used to
5
- // each carry their own `createRequire(import.meta.url)` block the same ~six lines
6
- // twice, and both Node-only. That duplication is single-sourced here, and the
7
- // Node-only limitation is lifted with a browser seam, WITHOUT eagerly bundling the
8
- // ~1 MB model into anything.
4
+ // A static `import "wink-nlp"` would drag the ~1 MB model into the base/viewer bundle.
5
+ // Instead, `registerWinkModel` lets a browser/bundler entry hand in an already-imported
6
+ // `{ winkNLP, model }` pair once; Node instead resolves lazily via `createRequire`.
9
7
  //
10
- // Why a registration seam instead of a static `import "wink-nlp"`:
11
- // - The whole architecture keeps the model OUT of the base/viewer bundle; a static
12
- // import would drag it in. `wink-eng-lite-web-model` is already the *browser*
13
- // build, so the model can run in the page — what was missing is a load path a
14
- // bundler can satisfy without a Node `require`. That path is `registerWinkModel`:
15
- // a browser/bundler entry imports wink with its own `import` and hands the pair
16
- // in ONCE, before any lemma/POS use. Node needs nothing — it falls back to
17
- // `createRequire`. This is the Phase-8 browser-mode unblocker the dependency
18
- // audit called for (a wiring fix; the model was always browser-capable).
19
- //
20
- // The loader stays SYNCHRONOUS (the adapters and their callers are sync): the
21
- // browser host registers up front; Node resolves lazily via createRequire. Failure
22
- // is cached as null — a checkout without the optional deps, or a page that never
23
- // registered a model, simply runs adapter-less (lemma/POS tiers honestly off), it
24
- // never throws.
8
+ // The loader stays synchronous. Failure is cached as null — a checkout without the
9
+ // optional deps, or a page that never registered a model, runs adapter-less rather
10
+ // than throwing.
25
11
 
26
12
  import { createRequire } from "node:module";
27
13
 
@@ -50,8 +36,8 @@ export function loadWinkModel() {
50
36
  return cached;
51
37
  }
52
38
 
53
- /** Node fallback: resolve wink through the module system (never a guessed path),
54
- * exactly as the two adapters did inline before. CJS deps, so `createRequire`. */
39
+ /** Node fallback: resolve wink through the module system (never a guessed path).
40
+ * CJS deps, so `createRequire`. */
55
41
  function nodeRequireWink() {
56
42
  const require = createRequire(import.meta.url);
57
43
  return {