@polycode-projects/the-mechanical-code-talker 1.8.20 → 1.9.1

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.
package/README.md CHANGED
@@ -334,12 +334,23 @@ reasoning). Design detail and the full fact-count tables are in `archive/PLAN_SE
334
334
  ### Memory backends
335
335
 
336
336
  The default memory backend writes an OWL-labelled JSON file under `.tmct/`.
337
- Two more exist for a library caller who doesn't want that: `runChat({
338
- memoryBackend: "memory" })` keeps taught facts in the process only, nothing
339
- written to disk; `runChat({ memoryBackend: "sqlite" })` persists them to a
340
- local SQLite file instead. `TMCT_MEMORY_BACKEND=memory|sqlite` does the same
341
- from the environment. There's no CLI flag yetthis is a library-level
342
- option for now, newer and less exercised than the default backend.
337
+ Two more exist: `memory` keeps taught facts in the process only, nothing
338
+ written to disk; `sqlite` persists them to a local SQLite file instead
339
+ (`.tmct/memory/graph.sqlite`).
340
+
341
+ Pick one at init time and it sticks`tmct init --memory-backend sqlite`
342
+ writes the choice into `tmct.toml`, and every later `tmct chat` in that repo
343
+ uses it with no flag needed:
344
+
345
+ ```bash
346
+ tmct init --memory-backend sqlite # writes [memory] backend = "sqlite" to tmct.toml
347
+ tmct chat # picks it up automatically
348
+ tmct chat --memory-backend memory # override for just this session
349
+ ```
350
+
351
+ Precedence is `--memory-backend` flag > `TMCT_MEMORY_BACKEND` env > tmct.toml's
352
+ `[memory] backend` > the default. A library caller sets the same thing
353
+ directly: `runChat({ memoryBackend: "sqlite" })`.
343
354
 
344
355
  Teaching isn't limited to the ACE grammar's fixed shapes. Tell tmct an
345
356
  arbitrary fact, like "margo really eats ribs", and it mints a fact you can
@@ -411,6 +422,10 @@ Inside the chat: `/help` lists commands, `/memory` inspects what tmct remembers
411
422
  (grouped by OWL class, with provenance and any contradictions), `/exit` leaves.
412
423
  `TMCT_GRAPH_FILE` overrides the graph location.
413
424
 
425
+ `tmct --help` (or `npm run help` from a clone of this repo) is the full,
426
+ up-to-date flag reference for every subcommand — a bare `npm run` only lists
427
+ script names, so `npm run help` is the documented way in from there.
428
+
414
429
  `tmct init` is the onboarding surface for the repository interface below: it
415
430
  creates the `.tmct/` directory, writes the externalized `tmct.toml`
416
431
  configuration, seeds the default persona, and records provenance. A host
@@ -435,17 +450,23 @@ tmct init --lexicon <name|path> # activate a lexicon bundle (never
435
450
  tmct init --graph <path> [--graph <path> …] # set tmct.toml's graph_file/graph_files
436
451
  tmct init --config <path> # write to an alternate tmct.toml location
437
452
  tmct init --persona-size medium|large # grow the default persona (Small is default)
453
+ tmct init --memory-backend default|memory|sqlite # write tmct.toml's [memory] backend —
454
+ # every later `tmct chat` here uses it
438
455
 
439
456
  tmct import --corpus <id|path> # activate+seed into an ALREADY-initialized
440
457
  tmct import --ontology <name|path> # repo — any combination of these flags in
441
458
  tmct import --lexicon <name|path> # one call. --graph is a DIFFERENT, purely
442
459
  tmct import --graph <path> # additive op: it appends to graph_files,
443
460
  # never activates an extensions bundle.
461
+ tmct import --memory-backend default|memory|sqlite # same knob as `tmct init`, for a
462
+ # repo that's already initialized
444
463
 
445
464
  tmct chat --graph <path> [--graph <path> …] # explicit graph file(s) — multiple merge
446
465
  # (ids that collide across graphs are
447
466
  # auto-prefixed; see src/graph-merge.mjs)
448
467
  tmct chat --config <path> # an alternate tmct.toml (a file or a dir)
468
+ tmct chat --memory-backend default|memory|sqlite # override tmct.toml's backend for
469
+ # just this session
449
470
  tmct serve --graph <path> --config <path> # same two flags, for the HTTP endpoint
450
471
  ```
451
472
 
package/ROADMAP.md CHANGED
@@ -32,7 +32,7 @@ getting silently traded away by inherited caution:
32
32
 
33
33
  ## What's next (feature-shaped — see `HANDOVER.md` for the current task-level list)
34
34
 
35
- - **`PLAN_BREADTH_FIRST_NLU.md`'s own remaining scope** — all six tracks shipped (entity-tie
35
+ - **`archive/PLAN_BREADTH_FIRST_NLU.md`'s own remaining scope** — all six tracks shipped (entity-tie
36
36
  ambiguity, router candidate enrichment, `tmct viz` + its embedded chat panel, template-coverage
37
37
  harness, alternates-on-hits, canonical representation for the ask/teach lanes — all now in "Current
38
38
  capability surface" above). Two named items are satisfied and closed, per their own track's
package/bin/tmct.mjs CHANGED
@@ -44,6 +44,9 @@ Usage:
44
44
  TMCT_NARRATE=1; toggle mid-session with /narrate on|off)
45
45
  [--plain] force the plain readline shell (the default when
46
46
  stdin/stdout is not a terminal)
47
+ [--memory-backend <default|memory|sqlite>] storage backend for taught facts this
48
+ session (CLI flag > TMCT_MEMORY_BACKEND env > tmct.toml's
49
+ [memory] backend > "default", the flat .tmct/ JSON file)
47
50
  tmct memory [--repo <abs>] what tmct remembers: facts, utterances, sessions,
48
51
  [--config <path>] folded blocks (the /memory chat command, from the shell)
49
52
  [--verbose]
@@ -66,11 +69,15 @@ Usage:
66
69
  activates human-large.jsonl (~13,600 facts total,
67
70
  with genuine multi-hop hypernym chains) — additive
68
71
  size tiers of the SAME bundle, not separate personas
72
+ [--memory-backend <default|memory|sqlite>] write tmct.toml's [memory] backend
73
+ (same flag name as \`tmct chat\`) — a later \`tmct chat\`
74
+ in this repo picks it up with no flag needed
69
75
  tmct import [--repo <abs>] activate+seed into an ALREADY-initialized repo (any
70
76
  [--corpus <id|path>] combination of these flags in one call). --graph is a
71
77
  [--ontology <name|path>] DIFFERENT operation from the others: it APPENDS to
72
78
  [--lexicon <name|path>] tmct.toml's graph_files array (multi-graph growth),
73
79
  [--graph <path>] never an extensions-bundle activation.
80
+ [--memory-backend <default|memory|sqlite>] same knob as \`tmct init\`
74
81
  [--config <path>]
75
82
  tmct extend --validate <dir> validate a third-party extension pack's declared
76
83
  [--config <path>] resources (corpus/lexicon/templates) before activating
@@ -80,8 +87,18 @@ Usage:
80
87
  [--config <path>] bounded, low-trust, retractable entailed facts (never on the chat path)
81
88
  tmct viz [--repo <abs>] write one self-contained, navigable HTML file rendering the
82
89
  [--focus <id>] memory graph: pan/zoom, click a node for its label/class/
83
- [--output <path>] timestamps. Seeds from the most recently created individual
84
- [--config <path>] by default; --output defaults to graph.html in the cwd.
90
+ [--term <word>] timestamps. Seeds from the most recently created individual
91
+ [--depth <n>] by default (--focus <id> or --term <word> override it);
92
+ [--limit <n>] --output defaults to graph.html in the cwd.
93
+ [--hub-degree <n>] --depth = max arcs (hops) from the focus node (default 3);
94
+ [--edge-kind <mode>] --limit = spiral length, total nodes walked (default 300);
95
+ [--output <path>] --hub-degree = stop expanding THROUGH a node above N
96
+ [--config <path>] connections, still shows it (default 40); --edge-kind =
97
+ meta|relation|both (default both) — which edge kinds the
98
+ walk follows (provenance-only, concept-relations-only, or
99
+ both — see the page's own edge-kind toggle to change this
100
+ live); --term <word> resolves to the Fact(s) whose subject/
101
+ object normalizes to that word and seeds from there.
85
102
  tmct serve [--repo <abs>] run the Anthropic Messages API-compatible endpoint
86
103
  [--host <h>] [--port <n>] (POST /v1/messages) over the graph — a deterministic,
87
104
  [--graph <path>] no-LLM "model" a tool-loop client can call; $0 usage.
@@ -96,6 +113,11 @@ In chat: /help lists slash-commands; /exit leaves. Session log → <repo>/.tmct/
96
113
  Shared graph-path precedence (chat/serve; see src/cli-args.mjs): --graph flag(s) >
97
114
  TMCT_GRAPH_FILE env > tmct.toml graph_file/graph_files > --repo-derived
98
115
  <repo>/.tmct/graph.json > git-root/cwd default.
116
+
117
+ Memory-backend precedence (chat; see src/chat.mjs createSession): --memory-backend
118
+ flag > TMCT_MEMORY_BACKEND env > tmct.toml [memory] backend > "default" (the flat
119
+ .tmct/ JSON file). Set it once with \`tmct init --memory-backend <...>\` and every
120
+ later \`tmct chat\` in that repo picks it up with no flag needed.
99
121
  `;
100
122
 
101
123
  const argv = process.argv.slice(2);
@@ -330,6 +352,7 @@ async function readConfigForRewrite(repoRoot) {
330
352
  }
331
353
  if (raw?.extensions !== undefined) cfg.extensions = raw.extensions;
332
354
  if (raw?.bias !== undefined) cfg.bias = raw.bias;
355
+ if (raw?.memory?.backend !== undefined) cfg.memory = { ...cfg.memory, backend: raw.memory.backend };
333
356
  return { raw, cfg };
334
357
  }
335
358
 
@@ -459,7 +482,27 @@ async function activatePluggableInput(repoRoot, resolved) {
459
482
  if (!seedable) {
460
483
  return `activated "${name}" (${entry.kind}) in tmct.toml — no corpus facts to seed for this kind.\n`;
461
484
  }
462
- const { perBundle } = await seedActiveCorpusEntries(repoRoot, new Map([[name, entry]]));
485
+ // Backend-aware seeding (same split-brain bug fix as src/init.mjs's own
486
+ // corpus seed, found in review): `cfg.memory.backend` already reflects
487
+ // tmct.toml as it stands RIGHT NOW (readConfigForRewrite, above) — which,
488
+ // for a `tmct init --corpus X --memory-backend sqlite` / `tmct import
489
+ // --corpus X --memory-backend sqlite` combined call, already carries the
490
+ // NEW backend (bin/tmct.mjs's init/import handlers write --memory-backend
491
+ // before running any activation). Resolved via the SAME openMemoryBackend
492
+ // createSession/initRepo use, so this bundle's facts land in the store a
493
+ // later `tmct chat` will actually read.
494
+ const { openMemoryBackend } = await import("../src/memory/core.mjs");
495
+ const backendChoice = String(cfg.memory?.backend || "").trim().toLowerCase();
496
+ if (backendChoice === "memory") {
497
+ return `activated "${name}" (${entry.kind}) in tmct.toml — seeding skipped (memory backend is in-process only, nothing would persist past this command).\n`;
498
+ }
499
+ const { dir: memoryDir, close: closeMemoryStore } = await openMemoryBackend(repoRoot, backendChoice);
500
+ let perBundle;
501
+ try {
502
+ ({ perBundle } = await seedActiveCorpusEntries(memoryDir, new Map([[name, entry]])));
503
+ } finally {
504
+ await closeMemoryStore();
505
+ }
463
506
  const seeded = perBundle[name];
464
507
  if (seeded.error) {
465
508
  throw new Error(`could not seed "${name}" — ${seeded.error}`);
@@ -526,12 +569,25 @@ async function main() {
526
569
  // TMCT_GRAPH_FILE — see chat.mjs's own docblock at createSession). Omitted from
527
570
  // the call entirely when absent, so a plain `tmct chat [--repo]` invocation is
528
571
  // byte-identical to before these flags existed.
529
- const { repeatedFlag, strFlag } = await import("../src/cli-args.mjs");
572
+ const { repeatedFlag, strFlag, enumFlag } = await import("../src/cli-args.mjs");
530
573
  const graphPaths = repeatedFlag(rest, ["--graph"]);
531
574
  const configPath = strFlag(rest, ["--config"]);
575
+ // `--memory-backend <default|memory|sqlite>`: the CLI's top tier of
576
+ // createSession's memoryBackend precedence (CLI flag > TMCT_MEMORY_BACKEND
577
+ // env > tmct.toml's [memory] backend > built-in default — src/cli-args.mjs's
578
+ // shared precedence order, resolved inside createSession itself). Omitted
579
+ // when absent so the lower tiers still apply unchanged.
580
+ let memoryBackend;
581
+ try {
582
+ memoryBackend = enumFlag(rest, ["--memory-backend"], ["default", "memory", "sqlite"]);
583
+ } catch (e) {
584
+ process.stderr.write(`tmct: ${e?.message || e}\n`);
585
+ process.exit(2);
586
+ }
532
587
  const extra = {};
533
588
  if (graphPaths.length) extra.graphPaths = graphPaths;
534
589
  if (configPath) extra.configPath = configPath;
590
+ if (memoryBackend) extra.memoryBackend = memoryBackend;
535
591
  // The shell gate: a real terminal gets the full-screen Ink TUI; `--plain` or a
536
592
  // non-TTY stream (pipes, scripts, the test suite) gets the readline shell. Both
537
593
  // drive the same createSession sink — only the drawing differs.
@@ -577,7 +633,7 @@ async function main() {
577
633
  // subcommand without a --repo flag. It now takes one like every other
578
634
  // subcommand, defaulting to cwd exactly as before when absent.
579
635
  const rest = process.argv.slice(3);
580
- const { strFlag, repeatedFlag } = await import("../src/cli-args.mjs");
636
+ const { strFlag, repeatedFlag, enumFlag } = await import("../src/cli-args.mjs");
581
637
  const { resolve: resolvePath } = await import("node:path");
582
638
  const { initRepo, PERSONA_PRESETS } = await import("../src/init.mjs");
583
639
 
@@ -589,6 +645,20 @@ async function main() {
589
645
  const lexiconVal = strFlag(rest, ["--lexicon"]);
590
646
  const graphFlags = repeatedFlag(rest, ["--graph"]);
591
647
 
648
+ // `--memory-backend <default|memory|sqlite>` (PLAN_SEED.md §6's storage-backend
649
+ // seam, now reachable from `tmct init`): validated BEFORE touching disk, same
650
+ // discipline as every other pluggable input below. Written into tmct.toml's
651
+ // `[memory] backend` (src/init.mjs's renderTomlConfig); chat.mjs's
652
+ // createSession reads it back at CLI-flag > TMCT_MEMORY_BACKEND env >
653
+ // tmct.toml > default precedence (src/cli-args.mjs's shared precedence order).
654
+ let memoryBackendVal;
655
+ try {
656
+ memoryBackendVal = enumFlag(rest, ["--memory-backend"], ["default", "memory", "sqlite"]);
657
+ } catch (e) {
658
+ process.stderr.write(`tmct init: ${e?.message || e}\n`);
659
+ process.exit(2);
660
+ }
661
+
592
662
  // Resolve + validate EVERY pluggable input BEFORE touching disk — mirrors
593
663
  // `--with-persona`'s own "validate before scaffolding" discipline, and the
594
664
  // original `--corpus`'s "unknown id touches nothing" contract.
@@ -650,7 +720,32 @@ async function main() {
650
720
  }
651
721
  }
652
722
 
653
- const res = await initRepo(repoRoot, { force: rest.includes("--force"), persona: personaPreset });
723
+ // `--memory-backend <default|memory|sqlite>`: written EARLY, before
724
+ // initRepo/any --corpus/--ontology/--lexicon activation below, so every
725
+ // seed step (initRepo's own corpus seed, AND activatePluggableInput's,
726
+ // below) sees the FINAL backend choice. A split-brain bug found in
727
+ // review: writing this AFTER seeding meant corpus facts always landed in
728
+ // the OLD backend, regardless of this flag. Two cases:
729
+ // - tmct.toml doesn't exist yet (the common case): threaded into
730
+ // initRepo() as `memoryBackend`, below — written atomically as part
731
+ // of the SAME fresh config write persona/corpus-limit already use.
732
+ // - tmct.toml already exists and `--force` isn't set (initRepo's own
733
+ // "preserve a user's tmct.toml" rule, same as persona): written HERE,
734
+ // before initRepo/activation run, so this repo's re-seed (if any) and
735
+ // any --corpus/--ontology/--lexicon activation below see it.
736
+ const forceFlag = rest.includes("--force");
737
+ if (memoryBackendVal) {
738
+ const { access } = await import("node:fs/promises");
739
+ const tomlPath = resolvePath(repoRoot, "tmct.toml");
740
+ const tomlAlreadyExists = await access(tomlPath).then(() => true, () => false);
741
+ if (tomlAlreadyExists && !forceFlag) {
742
+ const { cfg } = await readConfigForRewrite(repoRoot);
743
+ cfg.memory = { ...(cfg.memory || {}), backend: memoryBackendVal };
744
+ await writeConfig(repoRoot, cfg);
745
+ }
746
+ }
747
+
748
+ const res = await initRepo(repoRoot, { force: forceFlag, persona: personaPreset, memoryBackend: memoryBackendVal });
654
749
  process.stdout.write(res.message + "\n");
655
750
 
656
751
  // `--corpus`/`--ontology`/`--lexicon` now mean "activate this bundle and
@@ -689,6 +784,14 @@ async function main() {
689
784
  anyActivation = true;
690
785
  }
691
786
 
787
+ // `--memory-backend <default|memory|sqlite>`: already WRITTEN by now —
788
+ // either just above (pre-existing tmct.toml) or inside initRepo() itself
789
+ // (a fresh write, `memoryBackend` opt) — this just reports it.
790
+ if (memoryBackendVal) {
791
+ process.stdout.write(`memory backend set in tmct.toml: ${memoryBackendVal}\n`);
792
+ anyActivation = true;
793
+ }
794
+
692
795
  if (anyActivation) return;
693
796
 
694
797
  if (rest.includes("--detect")) {
@@ -719,7 +822,7 @@ async function main() {
719
822
  // additive operation (appendGraphFiles) — it grows tmct.toml's graph_files
720
823
  // array, never activates an extensions bundle.
721
824
  const rest = process.argv.slice(3);
722
- const { strFlag, repeatedFlag } = await import("../src/cli-args.mjs");
825
+ const { strFlag, repeatedFlag, enumFlag } = await import("../src/cli-args.mjs");
723
826
  const { resolve: resolvePath } = await import("node:path");
724
827
 
725
828
  const repoFlag = strFlag(rest, ["--repo"]);
@@ -729,9 +832,19 @@ async function main() {
729
832
  const ontologyVal = strFlag(rest, ["--ontology"]);
730
833
  const lexiconVal = strFlag(rest, ["--lexicon"]);
731
834
  const graphFlags = repeatedFlag(rest, ["--graph"]);
835
+ // `--memory-backend <default|memory|sqlite>`: same knob as `tmct init`'s
836
+ // (consistent name, same [memory] backend tmct.toml field) — lets an
837
+ // already-initialized repo change its storage backend without a re-init.
838
+ let memoryBackendVal;
839
+ try {
840
+ memoryBackendVal = enumFlag(rest, ["--memory-backend"], ["default", "memory", "sqlite"]);
841
+ } catch (e) {
842
+ process.stderr.write(`tmct import: ${e?.message || e}\n`);
843
+ process.exit(2);
844
+ }
732
845
 
733
- if (!corpusVal && !ontologyVal && !lexiconVal && !graphFlags.length) {
734
- process.stderr.write("tmct import: needs at least one of --corpus/--ontology/--lexicon/--graph\n");
846
+ if (!corpusVal && !ontologyVal && !lexiconVal && !graphFlags.length && !memoryBackendVal) {
847
+ process.stderr.write("tmct import: needs at least one of --corpus/--ontology/--lexicon/--graph/--memory-backend\n");
735
848
  process.exit(2);
736
849
  }
737
850
 
@@ -745,6 +858,19 @@ async function main() {
745
858
  process.exit(2);
746
859
  }
747
860
 
861
+ // `--memory-backend` is written FIRST, before any --corpus/--ontology/
862
+ // --lexicon activation below — the same split-brain bug fix as `tmct
863
+ // init`'s ordering: activatePluggableInput seeds into whichever backend
864
+ // tmct.toml names AT THE TIME it runs, so `tmct import --corpus aws
865
+ // --memory-backend sqlite` in one call must have the new backend on disk
866
+ // BEFORE the aws seed step, not after.
867
+ if (memoryBackendVal) {
868
+ const { cfg } = await readConfigForRewrite(repoRoot);
869
+ cfg.memory = { ...(cfg.memory || {}), backend: memoryBackendVal };
870
+ await writeConfig(repoRoot, cfg);
871
+ process.stdout.write(`memory backend set in tmct.toml: ${memoryBackendVal}\n`);
872
+ }
873
+
748
874
  for (const resolved of [corpusResolved, ontologyResolved, lexiconResolved]) {
749
875
  if (!resolved) continue;
750
876
  try {
@@ -863,24 +989,45 @@ async function main() {
863
989
  // resolveRuntimeConfig: --repo > git root > cwd.
864
990
  const rest = process.argv.slice(3);
865
991
  const { strFlag, resolveRuntimeConfig } = await import("../src/cli-args.mjs");
866
- const { computeVizGraph, renderVizHtml, readAskBundle } = await import("../src/viz.mjs");
992
+ const { computeVizGraph, renderVizHtml, readAskBundle, readMemoryAskBundle } = await import("../src/viz.mjs");
867
993
  const { writeFile } = await import("node:fs/promises");
868
994
  const { resolve } = await import("node:path");
995
+ const numFlag = (name) => {
996
+ const j = rest.indexOf(name);
997
+ const v = j !== -1 ? Number(rest[j + 1]) : NaN;
998
+ return Number.isFinite(v) ? v : undefined;
999
+ };
869
1000
  const focus = strFlag(rest, ["--focus"]);
1001
+ const term = strFlag(rest, ["--term"]); // PLAN_VIZ_MEMORY.md: seed via normFactTerm-matched Fact(s), alongside --focus
1002
+ const depth = numFlag("--depth"); // max arcs (hops) from the focus node
1003
+ const nodeLimit = numFlag("--limit"); // spiral length: total nodes walked
1004
+ const hubDegree = numFlag("--hub-degree"); // stop expanding THROUGH a node above N connections
1005
+ const edgeKindModeRaw = strFlag(rest, ["--edge-kind"]);
1006
+ const edgeKindMode = ["meta", "relation", "both"].includes(edgeKindModeRaw) ? edgeKindModeRaw : undefined;
870
1007
  const outPath = resolve(process.cwd(), strFlag(rest, ["--output", "--out"], "graph.html"));
871
1008
  const { repo } = await resolveRuntimeConfig({ argv: rest });
872
- const vizGraph = await computeVizGraph(repo, focus ? { focus } : {});
873
- // The embedded "Ask the graph" chat panel — the real ask.mjs engine,
874
- // bundled for the browser (scripts/build-ask-bundle.mjs's checked-in
875
- // output). readAskBundle() never throws; an empty string renders a
876
- // graph-only page with an honest "chat unavailable" note instead of a
877
- // broken one (e.g. a fresh checkout before the bundle's first build).
878
- const askBundle = await readAskBundle();
879
- const html = renderVizHtml({ ...vizGraph, askBundle });
1009
+ const vizGraph = await computeVizGraph(repo, {
1010
+ ...(focus ? { focus } : {}),
1011
+ ...(!focus && term ? { term } : {}), // --focus takes precedence when both are given
1012
+ ...(depth != null ? { depth } : {}),
1013
+ ...(nodeLimit != null ? { nodeLimit } : {}),
1014
+ ...(hubDegree != null ? { hubDegree } : {}),
1015
+ ...(edgeKindMode ? { edgeKindMode } : {}),
1016
+ });
1017
+ // The embedded "Ask the graph" chat panels — TWO real engines, bundled for
1018
+ // the browser (scripts/build-ask-bundle.mjs's checked-in output): the
1019
+ // code-graph ask.mjs engine, and (PLAN_VIZ_MEMORY.md Bug 1 fix) the
1020
+ // memory-graph factAnswer engine. Neither read*AskBundle() ever throws; an
1021
+ // empty string degrades that ONE engine gracefully rather than breaking
1022
+ // the page (e.g. a fresh checkout before the bundles' first build).
1023
+ const [askBundle, memoryAskBundle] = await Promise.all([readAskBundle(), readMemoryAskBundle()]);
1024
+ const html = renderVizHtml({ ...vizGraph, askBundle, memoryAskBundle });
880
1025
  await writeFile(outPath, html, "utf8");
1026
+ const chatNote = askBundle || memoryAskBundle
1027
+ ? ` (with the embedded ask-the-graph chat panel${askBundle && memoryAskBundle ? "s" : ""})`
1028
+ : " (no chat panel — run `npm run build:ask-bundle` first)";
881
1029
  process.stdout.write(
882
- `tmct viz — wrote ${vizGraph.nodes.length} node(s), ${vizGraph.edges.length} edge(s) to ${outPath}`
883
- + `${askBundle ? " (with the embedded ask-the-graph chat panel)" : " (no chat panel — run `npm run build:ask-bundle` first)"}\n`,
1030
+ `tmct viz — wrote ${vizGraph.nodes.length} node(s), ${vizGraph.edges.length} edge(s) to ${outPath}${chatNote}\n`,
884
1031
  );
885
1032
  return;
886
1033
  }
@@ -1,6 +1,6 @@
1
1
  # corpus/generated/ — mechanically-generated ACE surface variants
2
2
 
3
- `ace-surface-variants.jsonl` — PLAN_BREADTH_FIRST_NLU.md §6a's raw material for
3
+ `ace-surface-variants.jsonl` — archive/PLAN_BREADTH_FIRST_NLU.md §6a's raw material for
4
4
  tmct's "richer template/surface-realization variety" goal. Never an LLM, never
5
5
  invented text: every row is a real seed sentence with one word swapped for a
6
6
  real WordNet synset sibling, or a real sentence's alternate ACE-grammar-
@@ -29,7 +29,7 @@ Every row also carries `provenance` (`wordnet:<synsetId>` or
29
29
 
30
30
  This corpus is not loaded by `src/chat.mjs`/`src/ask.mjs` or any other
31
31
  product code — it is committed raw material, verified by
32
- `scripts/template-coverage.mjs`, per PLAN_BREADTH_FIRST_NLU.md §6's explicit
32
+ `scripts/template-coverage.mjs`, per archive/PLAN_BREADTH_FIRST_NLU.md §6's explicit
33
33
  non-goal. Wiring it into live answer rendering is a separate, future phase.
34
34
 
35
35
  ## Licence
@@ -0,0 +1,68 @@
1
+ LICENSE NOTICE — corpus/namenet/namenet.jsonl
2
+ ================================================================================
3
+
4
+ The file namenet.jsonl in this directory is a mechanical, ConceptNet-shape
5
+ conversion of three human-reviewed linking tables (species, taxon-to-common-
6
+ name, occupations) from the Open English Namenet project. It is licensed
7
+ under the Creative Commons Attribution 4.0 International License
8
+ (CC-BY-4.0), NOT under this repository's MPL-2.0.
9
+
10
+ https://creativecommons.org/licenses/by/4.0/
11
+
12
+ License status of the source repository — read this before redistributing
13
+ --------------------------------------------------------------------------
14
+
15
+ The source repository, globalwordnet/english-namenet, declares NO explicit
16
+ license of its own: no LICENSE file in the checkout, no license statement in
17
+ its README.md, and GitHub's own repository metadata reports `license: null`
18
+ (confirmed 2026-07-12). CC-BY-4.0 above is a CONSERVATIVE CHOICE by this
19
+ repository, matching the license of the Open English WordNet data the source
20
+ CSVs link against (see Attribution below) — it is not a license the
21
+ globalwordnet/english-namenet maintainers have themselves stated. Anyone
22
+ redistributing namenet.jsonl more widely than this repository should seek
23
+ explicit confirmation from the GlobalWordNet team before relying on that
24
+ label.
25
+
26
+ Attribution
27
+ -----------
28
+
29
+ This work includes data derived from the Open English Namenet project (an
30
+ extension of Open English WordNet with names drawn from Wikidata), and
31
+ transitively from Open English WordNet itself (CC-BY-4.0) and Wikidata
32
+ (structured data, CC0).
33
+
34
+ - Project: https://github.com/globalwordnet/english-namenet
35
+ - Open English WordNet: https://github.com/globalwordnet/english-wordnet
36
+ - Wikidata: https://www.wikidata.org/
37
+
38
+ Source
39
+ ------
40
+
41
+ - Dataset: three reviewed CSVs from a LOCAL, uncommitted checkout of
42
+ globalwordnet/english-namenet — never vendored into this repository:
43
+ - species_reviewed.csv (5,101 rows)
44
+ - taxon2common_reviewed.csv (2,368 rows)
45
+ - linked_occupations_reviewed.csv (2,193 rows)
46
+ Every accepted row in all three links a name/label to an Open English
47
+ WordNet synset or lemma set that a human reviewer confirmed denotes the
48
+ SAME real-world thing (a species, a folk-taxonomic category, an
49
+ occupation) — never a broader/narrower or capability relation. See
50
+ corpus/namenet/generate.mjs's own header comment for the full reasoning
51
+ behind mapping every row to /r/Synonym, and the exact accept/skip rules
52
+ per source file.
53
+ - species_reviewed.csv also required cross-referencing a SECOND local
54
+ checkout, globalwordnet/english-wordnet's `src/yaml/` synset source (the
55
+ same one corpus/wordnet/generate.mjs reads), to resolve each row's SSID
56
+ into a representative lemma — that CSV alone carries no lemma text.
57
+ - Conversion tool: corpus/namenet/generate.mjs (tmct code, MPL-2.0).
58
+
59
+ Share-alike
60
+ -----------
61
+
62
+ CC-BY-4.0 does NOT require share-alike (unlike ConceptNet's CC-BY-SA 4.0 in
63
+ corpus/conceptnet/) — a CC-BY-4.0 derivative may be relicensed, provided
64
+ attribution is preserved. This repository chooses to keep redistributing
65
+ namenet.jsonl itself under CC-BY-4.0 regardless, to stay unambiguous about
66
+ its origin, subject to the license-status caveat above. The code in this
67
+ directory (generate.mjs) is tmct code under the repository's MPL-2.0; only
68
+ the data file (namenet.jsonl) carries the CC-BY-4.0 label.