@polycode-projects/the-mechanical-code-talker 1.10.13 → 1.11.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.
package/README.md CHANGED
@@ -114,6 +114,23 @@ tmct> /exit
114
114
  **[Try it live in your browser →](https://polycode-projects.gitlab.io/the-mechanical-code-talker/)**
115
115
  is a real, interactive chat demo running client-side. Your browser runs the
116
116
  actual query engine against a small example codebase, no server, no install.
117
+ The page leads with the **memory ledger**: every fact as a readable sentence,
118
+ drill by clicking the terms inside them, and an in-page chat whose answers
119
+ focus the ledger.
120
+
121
+ Two more surfaces, both generated by tmct itself:
122
+
123
+ ```bash
124
+ npx tmct viz --ledger # ledger.html — your own memory as the same
125
+ # readable, self-contained explorer
126
+ npx tmct init
127
+ npx tmct import --file .tmct/imports/games/hanoi-3.txt
128
+ npx tmct chat --prompt 'disk-1 rests on disk-2. disk-2 rests on disk-3.
129
+ disk-3 rests on peg-a. the goal is that every disk rests on peg-c. solve it.' \
130
+ --render blocks --output plan.html # an animated replay of the solved plan
131
+ ```
132
+
133
+ More on the game file and the planner under "Teach it a game" below.
117
134
 
118
135
  ## How it interprets you
119
136
 
@@ -127,65 +144,38 @@ Those triples are statements it can store, retrieve, and answer from later.
127
144
  Text that doesn't fit the grammar still gets the tolerant strategies. Nothing
128
145
  is rejected for being loose, fuzzy, or misspelled.
129
146
 
130
- **Everyday question shapes.** Bare "what is Commit" (no article) now resolves
131
- like "what is a commit" for tmct's own vocabulary. "What's model.mjs for" and
132
- "what's model.mjs about" answer like "what does model.mjs do". "Is Base a
133
- superclass of Widget" is read as the reverse of "is Widget a subclass of
134
- Base", the same relationship either way round. "Recent commits" and "the last
135
- commit" resolve to real dated history, not a literal string miss. Polite or
136
- indirect framing reaches the same capability as a direct request: "I'd like
137
- you to remember X" teaches like bare "remember X"; "please tell me about X"
138
- and "search for X" describe and find exactly as their direct forms do.
139
-
140
- **Negation and passive.** "Which modules do *not* import X?" computes a bounded
141
- **set complement** over the graph. An empty result stays a miss rather than a
142
- fabricated list (a non-enumerable type, like arbitrary *changes*, is refused
143
- outright). Reversible-passive questions traverse the right direction:
144
- "what is imported by Y" and "what does Y import" are understood as opposite
145
- edges, not the same one.
146
-
147
- **Finding by description.** "find me the payment class" searches by type and a
148
- fuzzy match against the entity's own properties, instead of making you name it
149
- exactly. It checks the type itself and its subclasses first. Only if nothing
150
- matches there does it widen to a related type, and when it does, it says so
151
- plainly rather than presenting the looser match as exact.
152
-
153
- **Comparing two things.** "compare TaskController and UserController" or "how
154
- is TaskController different from UserController" lines up both entities'
155
- shared and differing edges side by side: *"Comparing TaskController and
156
- UserController (both Class): inherits [seon:hasSuperType]: TaskController (1)
157
- -> Controller; UserController (1) -> Controller"*. Every row is a real edge
158
- or attribute from the graph, never a hand-written diff.
159
-
160
- **Up-refining to a containing module.** A class rarely has its own
161
- symbol-precise commit or import record. "who touched TaskController" answers
162
- from its containing module's real history instead of a confident-looking but
163
- wrong "nothing touched it".
164
-
165
- **Following a list.** tmct remembers the last list it gave you. After
166
- "which modules import src/core/model.mjs", "which of those are tested" or
167
- "how many of those" resolves "those"/"them" against that list, not a fresh,
168
- unresolved pronoun.
169
-
170
- **Synonyms and everyday phrasing.** tmct matches many of the words people
171
- actually use for the same idea, from a curated synonym list plus a filtered
172
- ConceptNet slice. A slightly different word for the same concept still
173
- resolves. It also follows a few common sentence shapes: clauses starting with
174
- *because/although/while*, and conditionals ("if X were removed, what
175
- breaks"). It flags a question whose premise doesn't hold, too: "why does X
176
- still import Y" when it no longer does.
177
-
178
- **Response finishing.** Before an answer is printed it is segmented into typed
179
- spans: prose versus *protected* entities, paths, numbers, code, provenance, and
180
- receipts. A small data-driven grammar pass then runs on the prose spans only,
181
- under a guard that proves the protected spans came through byte-for-byte. Today
182
- that pass fixes the a/an article defect. Broader voice and agreement rules are
183
- implemented but parked until they earn their place on the benchmark.
147
+ On top of that base, tmct reads the shapes people actually use, and each one
148
+ resolves to a real graph traversal or declines honestly:
149
+
150
+ - everyday question forms: "what is Commit", "what's model.mjs for",
151
+ "recent commits" as real dated history;
152
+ - polite or indirect framing: "I'd like you to remember X" teaches like bare
153
+ "remember X";
154
+ - negation as a bounded **set complement**: "which modules do *not* import
155
+ X?", with an empty result staying a miss, never a fabricated list;
156
+ - reversible passives: "what is imported by Y" and "what does Y import" are
157
+ opposite edges, not the same one;
158
+ - finding by description: "find me the payment class" checks the type and
159
+ its subclasses first, and says so plainly when it widens;
160
+ - comparison: "compare TaskController and UserController" lines up both
161
+ entities' real edges side by side, never a hand-written diff;
162
+ - list follow-ups: after "which modules import src/core/model.mjs",
163
+ "which of those are tested" resolves against that list;
164
+ - curated synonyms plus a filtered ConceptNet slice, clause openers
165
+ (*because/although/while*), conditionals, and false-premise flags ("why
166
+ does X still import Y" when it no longer does).
167
+
168
+ The full catalog with measured coverage lives in `CAPABILITIES_1.7.3.md` and
169
+ the `BENCHMARK_*.md` reports.
170
+
171
+ **Response finishing.** Before an answer prints, it is segmented into typed
172
+ spans: prose versus *protected* entities, paths, numbers, code, provenance,
173
+ and receipts. A small data-driven grammar pass runs on the prose spans only,
174
+ under a guard that proves the protected spans came through byte-for-byte.
184
175
 
185
176
  A frozen regression suite plays out full multi-turn dialogues built from these
186
- phrasings, at every complexity level this project defines. That ranges from a
187
- single question up to a messy, typo-ridden real user. Tier-by-tier detail is
188
- in `HANDOVER.md` and `ROADMAP.md`.
177
+ phrasings, from a single question up to a messy, typo-ridden real user.
178
+ Tier-by-tier detail is in `HANDOVER.md` and `ROADMAP.md`.
189
179
 
190
180
  ## How it guides you
191
181
 
@@ -250,13 +240,7 @@ const memory = await loadMemory(dir);
250
240
  const graphService = createCompletionsGraphAdapter(graph, memory);
251
241
 
252
242
  const { text } = await generateCompletion(dir, "Store", { query: "Store", graph, memory, graphService });
253
- console.log(text);
254
- ```
255
-
256
- ```
257
- Attribute: prose_tokens = memory record store [mgx:hasProseTokens]. Attribute:
258
- doc = In-memory record store. [seon:hasDoc]. Other matches: src/core/store.mjs
259
- (Module), loadStore (Function), saveStore (Function), testLoadStore (Function).
243
+ console.log(text); // prints the same text as the chat answer above
260
244
  ```
261
245
 
262
246
  Full pipeline design in `archive/PLAN_COMPLETIONS.md`.
@@ -328,6 +312,45 @@ proof chain, composed answer) for a caller that wants to consume this
328
312
  programmatically rather than read the report. `tmct plan --help` has the full
329
313
  flag reference.
330
314
 
315
+ ## Teach it a game, then ask it to plan
316
+
317
+ The planner above works over a fixed toolset. This one works over rules you
318
+ teach. A game definition is a plain-text file of controlled English — the
319
+ classes, the pieces, the ordering, and the legal moves as taught action
320
+ rules — with `#` comment lines carrying example prompts. `tmct init`
321
+ scaffolds one at `.tmct/imports/games/hanoi-3.txt`, and
322
+ `tmct import --file` teaches it sentence by sentence, reporting every line
323
+ and refusing (exit 1) if any sentence declines.
324
+
325
+ Then one message states the board and the goal, and "solve it" searches the
326
+ taught rules for the shortest move sequence:
327
+
328
+ ```
329
+ tmct> disk-1 rests on disk-2. disk-2 rests on disk-3. disk-3 rests on peg-a.
330
+ the goal is that every disk rests on peg-c. solve it.
331
+ plan found — 7 moves (shortest):
332
+ 1. move disk-1 onto peg-c
333
+ 2. move disk-2 onto peg-b
334
+ 3. move disk-1 onto disk-2
335
+ 4. move disk-3 onto peg-c
336
+ 5. move disk-1 onto peg-a
337
+ 6. move disk-2 onto disk-3
338
+ 7. move disk-1 onto disk-2
339
+
340
+ because — you taught me the "move onto" rule and 3 ordering facts. Say "next" to make move 1, or ask "what moves are legal now".
341
+
342
+ Goal (inferred): Plan a move sequence from the current state to the goal (7 moves).
343
+ ```
344
+
345
+ "next" executes one move at a time, writing each board state into memory as
346
+ facts; the final step re-reads those facts and confirms the goal from them,
347
+ never assuming success. The search is genuine and domain-general: the test
348
+ suite teaches Towers of Hanoi purely as sentences for 1 to 8 disks and
349
+ asserts the plan is exactly 2^n − 1 moves every time, and a second game
350
+ (`crates.txt`, stacking crates with different rules and a two-goal
351
+ conjunction) solves with zero interpreter changes. `--render blocks` writes
352
+ the plan as a self-contained animated page (see "Two more surfaces" above).
353
+
331
354
  ## How it remembers
332
355
 
333
356
  tmct's memory has two layers, both fed by every parsed request and response and
@@ -359,38 +382,21 @@ reasoning). Design detail and the full fact-count tables are in `archive/PLAN_SE
359
382
  The default memory backend writes an OWL-labelled JSON file under `.tmct/`.
360
383
  Two more exist: `memory` keeps taught facts in the process only, nothing
361
384
  written to disk; `sqlite` persists them to a local SQLite file instead
362
- (`.tmct/memory/graph.sqlite`).
363
-
364
- Pick one at init time and it sticks. `tmct init --memory-backend sqlite`
365
- writes the choice into `tmct.toml`, and every later `tmct chat` in that repo
366
- uses it with no flag needed:
367
-
368
- ```bash
369
- tmct init --memory-backend sqlite # writes [memory] backend = "sqlite" to tmct.toml
370
- tmct chat # picks it up automatically
371
- tmct chat --memory-backend memory # override for just this session
372
- ```
373
-
374
- Precedence is `--memory-backend` flag > `TMCT_MEMORY_BACKEND` env > tmct.toml's
375
- `[memory] backend` > the default. A library caller sets the same thing
376
- directly: `runChat({ memoryBackend: "sqlite" })`.
385
+ (`.tmct/memory/graph.sqlite`). `tmct init --memory-backend sqlite` writes the
386
+ choice into `tmct.toml` and every later `tmct chat` in that repo picks it up.
387
+ Precedence is `--memory-backend` flag > `TMCT_MEMORY_BACKEND` env >
388
+ tmct.toml's `[memory] backend` > the default. A library caller sets the same
389
+ thing directly: `runChat({ memoryBackend: "sqlite" })`.
377
390
 
378
391
  Teaching isn't limited to the ACE grammar's fixed shapes. Tell tmct an
379
392
  arbitrary fact, like "margo really eats ribs", and it mints a fact you can
380
- later ask about directly: "what does margo eat", or "does margo eat ribs".
381
-
382
- New vocabulary compounds as you teach it. "redis is a cache" mints "redis" as
383
- a class-level concept even though it was never in the built-in lexicon, and a
384
- later "every cache is a store" does the same for "store," the other way
385
- round, as long as one side of the sentence is already grounded. tmct never
386
- mints a fact between two totally ungrounded terms. It declines instead, and
387
- nudges you to ground one side first. Quantified teaching works too: "some
388
- functions are risky" stores the quantifier, and a later "how many functions
389
- are risky" answers "A few."
390
-
391
- Once you've taught a few facts, "how many facts are there" counts them back.
392
- That's the same count phrasing a code graph answers "how many classes are
393
- there" with, just now reading tmct's own memory.
393
+ ask about directly: "what does margo eat". New vocabulary compounds as you
394
+ teach: "redis is a cache" mints "redis" even though it was never in the
395
+ built-in lexicon, as long as one side of the sentence is already grounded
396
+ tmct never mints a fact between two totally ungrounded terms; it declines and
397
+ nudges you to ground one side first. Quantified teaching stores the
398
+ quantifier ("some functions are risky" "how many functions are risky"
399
+ "A few."), and "how many facts are there" counts the store back.
394
400
 
395
401
  ### Provenance and trust
396
402
 
@@ -410,10 +416,8 @@ provenance** rather than silently picking a winner.
410
416
 
411
417
  `tmct syllogise [--depth n] [--budget n]` is an offline, bounded, deterministic
412
418
  batch that forward-chains the memory's `rdfs:subClassOf` closure into new
413
- **entailed** facts, pre-deriving what the trusted sources already imply. It runs
414
- once automatically after seeding and on demand; the entailed facts are
415
- **low-trust and retractable** (never outranking a stated fact) and this never runs
416
- on the chat's hot path.
419
+ **entailed** facts. They are low-trust and retractable, never outranking a
420
+ stated fact, and this never runs on the chat's hot path.
417
421
 
418
422
  ## Install & use
419
423
 
@@ -461,6 +465,11 @@ Usage:
461
465
  see src/graph-merge.mjs); wins over --repo/TMCT_GRAPH_FILE/tmct.toml
462
466
  [--config <path>] an alternate tmct.toml location (a file or a directory)
463
467
  [--ephemeral] read the graph but write nothing back (demo/read-only)
468
+ [--prompt "<text>"] one-shot: run the prompt's sentences as turns and print
469
+ the final answer (teach state first, trigger last)
470
+ [--render blocks] with --prompt: when the final turn produced a plan,
471
+ write it as a self-contained animated page
472
+ [--output <path>] the rendered page's path (default plan.html)
464
473
  [--narrate] start with narrate mode on — a verbose, developer-facing
465
474
  trace of decision points/matched pattern/results/goal per
466
475
  turn, appended under a "--- narrate ---" marker (also
@@ -518,6 +527,9 @@ already set up. Its `--graph` flag works differently from the others: it appends
518
527
  [--ontology <name|path>] DIFFERENT operation from the others: it APPENDS to
519
528
  [--lexicon <name|path>] tmct.toml's graph_files array (multi-graph growth),
520
529
  [--graph <path>] never an extensions-bundle activation.
530
+ [--file <definition.txt>] teach a plain-text definition file sentence by
531
+ sentence (# lines are comments); any declined
532
+ sentence exits non-zero with the sentence named
521
533
  [--memory-backend <default|memory|sqlite>] same knob as `tmct init`
522
534
  [--config <path>]
523
535
  ```
@@ -544,10 +556,12 @@ inference" above:
544
556
 
545
557
  ```
546
558
  tmct viz [--repo <abs>] write one self-contained, navigable HTML file rendering the
547
- [--focus <id>] memory graph: pan/zoom, click a node for its label/class/
548
- [--term <word>] timestamps. Seeds from the most recently created individual
549
- [--depth <n>] by default (--focus <id> or --term <word> override it);
550
- [--limit <n>] --output defaults to graph.html in the cwd.
559
+ [--ledger] memory graph: pan/zoom, click a node for its label/class/
560
+ [--focus <id>] timestamps. Seeds from the most recently created individual
561
+ [--term <word>] by default (--focus <id> or --term <word> override it);
562
+ [--depth <n>] --output defaults to graph.html in the cwd. With --ledger,
563
+ [--limit <n>] a readable fact ledger around one focus term instead
564
+ (writes ledger.html; --limit caps the embedded fact rows).
551
565
  [--hub-degree <n>] --depth = max arcs (hops) from the focus node (default 3);
552
566
  [--edge-kind <mode>] --limit = spiral length, total nodes walked (default 300);
553
567
  [--output <path>] --hub-degree = stop expanding THROUGH a node above N
package/ROADMAP.md CHANGED
@@ -10,7 +10,10 @@ open items, session-scoped), see `HANDOVER.md` instead — this file doesn't dup
10
10
  A tolerant, ELIZA/PARRY-style chat surface over a codebase, obsessed with software the way PARRY was
11
11
  obsessed with the mafia — deterministic, zero-cost, **no LLM anywhere in the product path**. Guides a
12
12
  user toward precision queries rather than guessing; every answer is grounded, restates every genuine
13
- reading it finds in full, or is an honest miss when nothing grounds it at all.
13
+ reading it finds in full, or is an honest miss when nothing grounds it at all. Its visual surfaces —
14
+ the ledger explorer with its in-browser chat (`tmct viz --ledger`), the animated plan page
15
+ (`chat --prompt … --render blocks`), and the Pages homepage hero — are the same graph read out loud:
16
+ same engine, same provenance, no LLM.
14
17
 
15
18
  ## Ambition
16
19
 
@@ -48,9 +51,19 @@ getting silently traded away by inherited caution:
48
51
  and an NPC turn scheduler. Design-only.
49
52
  - **`PLAN_SYLLOGIST.md`** — retraction-aware consistency checking under a hard budget and trust
50
53
  tiers, the one open piece of the reasoning engine's research horizon. Design-only.
51
- - **`PLAN_GUESS_NUMBER.md` / `PLAN_HANOI.md`** closed-loop and open-loop planning domains for the
52
- `findActionPath`/`findReachableSet` kernels, both already built and proven but not yet wired to
53
- either domain. Design-only.
54
+ - **`PLAN_HANOI.md` follow-ups**the plan itself shipped (taught game domains, the chat plan
55
+ lane, `tmct import --file` + the `.tmct/imports/` scaffold, the animated plan page; see its
56
+ implementation addendum). Remaining: river-crossing's two missing frames plus the multi-effect
57
+ interpreter extension, and planner-side consumption of the `taught:` capability records the
58
+ registry bridge now registers.
59
+ - **`PLAN_GUESS_NUMBER.md`** — the closed-loop (observe-and-replan) planning domain for the same
60
+ kernels. Design-only.
61
+ - **`PLAN_VIZ_LEDGER.md` follow-ups** — phases 1-4 shipped (the ledger explorer, its chat dock,
62
+ the Pages hero, this README pass). Remaining: whether the ledger becomes the default viz
63
+ surface; a `goal` field on `factAnswer`'s returns so the dock can carry the chat's goal line
64
+ (needs operator sign-off — it touches the ask engine); a predicate-cardinality question for
65
+ `findContradictions` (multi-valued `has`/`can` facts group under the same contract as genuine
66
+ disagreements); bundle weight if the ledger page outgrows its measured ~533 KB.
54
67
  - **`PLAN_CODE.md`** — small JS-function and HTML/CSS-fragment synthesis via a sandboxed headless
55
68
  browser (Track 1, program synthesis, already shipped). Blocked on a sandbox dependency decision.
56
69
  - **`PLAN_AGENTS.md`** — the governing plan for tmct's broader multi-repo arc (marginalia, seonix,
package/bin/tmct.mjs CHANGED
@@ -38,6 +38,11 @@ Usage:
38
38
  see src/graph-merge.mjs); wins over --repo/TMCT_GRAPH_FILE/tmct.toml
39
39
  [--config <path>] an alternate tmct.toml location (a file or a directory)
40
40
  [--ephemeral] read the graph but write nothing back (demo/read-only)
41
+ [--prompt "<text>"] one-shot: run the prompt's sentences as turns and print
42
+ the final answer (teach state first, trigger last)
43
+ [--render blocks] with --prompt: when the final turn produced a plan,
44
+ write it as a self-contained animated page
45
+ [--output <path>] the rendered page's path (default plan.html)
41
46
  [--narrate] start with narrate mode on — a verbose, developer-facing
42
47
  trace of decision points/matched pattern/results/goal per
43
48
  turn, appended under a "--- narrate ---" marker (also
@@ -77,6 +82,9 @@ Usage:
77
82
  [--ontology <name|path>] DIFFERENT operation from the others: it APPENDS to
78
83
  [--lexicon <name|path>] tmct.toml's graph_files array (multi-graph growth),
79
84
  [--graph <path>] never an extensions-bundle activation.
85
+ [--file <definition.txt>] teach a plain-text definition file sentence by
86
+ sentence (# lines are comments); any declined
87
+ sentence exits non-zero with the sentence named
80
88
  [--memory-backend <default|memory|sqlite>] same knob as \`tmct init\`
81
89
  [--config <path>]
82
90
  tmct extend --validate <dir> validate a third-party extension pack's declared
@@ -86,10 +94,12 @@ Usage:
86
94
  [--depth <n>] [--budget <n>] chain the memory's rdfs:subClassOf closure, materialising
87
95
  [--config <path>] bounded, low-trust, retractable entailed facts (never on the chat path)
88
96
  tmct viz [--repo <abs>] write one self-contained, navigable HTML file rendering the
89
- [--focus <id>] memory graph: pan/zoom, click a node for its label/class/
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.
97
+ [--ledger] memory graph: pan/zoom, click a node for its label/class/
98
+ [--focus <id>] timestamps. Seeds from the most recently created individual
99
+ [--term <word>] by default (--focus <id> or --term <word> override it);
100
+ [--depth <n>] --output defaults to graph.html in the cwd. With --ledger,
101
+ [--limit <n>] a readable fact ledger around one focus term instead
102
+ (writes ledger.html; --limit caps the embedded fact rows).
93
103
  [--hub-degree <n>] --depth = max arcs (hops) from the focus node (default 3);
94
104
  [--edge-kind <mode>] --limit = spiral length, total nodes walked (default 300);
95
105
  [--output <path>] --hub-degree = stop expanding THROUGH a node above N
@@ -596,6 +606,65 @@ async function main() {
596
606
  if (graphPaths.length) extra.graphPaths = graphPaths;
597
607
  if (configPath) extra.configPath = configPath;
598
608
  if (memoryBackend) extra.memoryBackend = memoryBackend;
609
+ // `--prompt "<text>"` — one-shot mode: each sentence of the prompt runs as
610
+ // its own turn (state teaches first, the goal/solve trigger last), the
611
+ // FINAL turn's answer prints to stdout, and the process exits. Piped stdin
612
+ // stays the interactive fallback.
613
+ const prompt = strFlag(rest, ["--prompt"]);
614
+ // `--render <archetype> [--output <path>]` — after a one-shot --prompt whose
615
+ // final turn produced a plan, write the self-contained animated plan page
616
+ // (src/plan-viz.mjs). Requires --prompt: interactive chat has no single
617
+ // final turn to render.
618
+ let renderArchetype;
619
+ try {
620
+ renderArchetype = enumFlag(rest, ["--render"], ["blocks"]);
621
+ } catch (e) {
622
+ process.stderr.write(`tmct: ${e?.message || e}\n`);
623
+ process.exit(2);
624
+ }
625
+ if (renderArchetype && !prompt) {
626
+ process.stderr.write("tmct: --render needs --prompt (a one-shot turn whose plan it renders)\n");
627
+ process.exit(1);
628
+ }
629
+ if (prompt) {
630
+ const { createSession } = await import("../src/chat.mjs");
631
+ const { splitSentences } = await import("../src/sentences.mjs");
632
+ const session = await createSession({ repoPath, ephemeral, narrate, ...extra });
633
+ let finalAnswer = "";
634
+ let finalPlan = null;
635
+ let parts;
636
+ try { parts = splitSentences(prompt); } catch { parts = null; }
637
+ const sentences = parts && parts.length ? parts : [prompt];
638
+ for (const s of sentences) {
639
+ const t = await session.turn(s);
640
+ finalAnswer = t?.answer ?? "";
641
+ finalPlan = t?.plan ?? null;
642
+ }
643
+ await session.close();
644
+ process.stdout.write(finalAnswer + "\n");
645
+ if (renderArchetype) {
646
+ if (!finalPlan) {
647
+ process.stderr.write("the final turn produced no plan — nothing to render\n");
648
+ process.exitCode = 1;
649
+ return;
650
+ }
651
+ const { renderPlanHtml } = await import("../src/plan-viz.mjs");
652
+ const { writeFile } = await import("node:fs/promises");
653
+ const { resolve: resolvePath } = await import("node:path");
654
+ const outPath = resolvePath(process.cwd(), strFlag(rest, ["--output", "--out"], "plan.html"));
655
+ const rendersAs = finalPlan.domain?.renderHints ?? {};
656
+ const sizeOrder = (finalPlan.domain?.ordering ?? [])
657
+ .filter((row) => /-than$/.test(String(row.predicate || "")))
658
+ .map((row) => [row.subject, row.object]);
659
+ const html = renderPlanHtml({
660
+ plan: finalPlan, rendersAs, sizeOrder,
661
+ title: finalPlan.goal?.text || "tmct plan",
662
+ });
663
+ await writeFile(outPath, html, "utf8");
664
+ process.stdout.write(`wrote ${outPath} (${finalPlan.actions.length} moves, ${finalPlan.states.length} snapshots)\n`);
665
+ }
666
+ return;
667
+ }
599
668
  // The shell gate: a real terminal gets the full-screen Ink TUI; `--plain` or a
600
669
  // non-TTY stream (pipes, scripts, the test suite) gets the readline shell. Both
601
670
  // drive the same createSession sink — only the drawing differs.
@@ -867,8 +936,9 @@ async function main() {
867
936
  process.exit(2);
868
937
  }
869
938
 
870
- if (!corpusVal && !ontologyVal && !lexiconVal && !graphFlags.length && !memoryBackendVal) {
871
- process.stderr.write("tmct import: needs at least one of --corpus/--ontology/--lexicon/--graph/--memory-backend\n");
939
+ const fileVal = strFlag(rest, ["--file"]);
940
+ if (!corpusVal && !ontologyVal && !lexiconVal && !graphFlags.length && !memoryBackendVal && !fileVal) {
941
+ process.stderr.write("tmct import: needs at least one of --corpus/--ontology/--lexicon/--graph/--memory-backend/--file\n");
872
942
  process.exit(2);
873
943
  }
874
944
 
@@ -914,6 +984,22 @@ async function main() {
914
984
  process.exit(1);
915
985
  }
916
986
  }
987
+
988
+ // `--file <definition.txt>` — teach a plain-text definition file, one
989
+ // sentence per turn, through the same recognizers the live chat uses.
990
+ // Any declined sentence exits non-zero: a half-taught game plans wrongly
991
+ // or not at all with no visible cause otherwise.
992
+ if (fileVal) {
993
+ const { importDefinitionFile } = await import("../src/import-file.mjs");
994
+ try {
995
+ const result = await importDefinitionFile(repoRoot, fileVal);
996
+ process.stdout.write(result.report + "\n");
997
+ if (result.declined.length > 0) process.exit(1);
998
+ } catch (e) {
999
+ process.stderr.write(`tmct import: ${e?.message || e}\n`);
1000
+ process.exit(1);
1001
+ }
1002
+ }
917
1003
  return;
918
1004
  }
919
1005
 
@@ -1012,7 +1098,7 @@ async function main() {
1012
1098
  // depth/age falloff. Same repo resolution as `memory`/`syllogise` —
1013
1099
  // resolveRuntimeConfig: --repo > git root > cwd.
1014
1100
  const rest = process.argv.slice(3);
1015
- const { strFlag, resolveRuntimeConfig } = await import("../src/cli-args.mjs");
1101
+ const { strFlag, boolFlag, resolveRuntimeConfig } = await import("../src/cli-args.mjs");
1016
1102
  const { computeVizGraph, renderVizHtml, readAskBundle, readMemoryAskBundle } = await import("../src/viz.mjs");
1017
1103
  const { writeFile } = await import("node:fs/promises");
1018
1104
  const { resolve } = await import("node:path");
@@ -1021,15 +1107,35 @@ async function main() {
1021
1107
  const v = j !== -1 ? Number(rest[j + 1]) : NaN;
1022
1108
  return Number.isFinite(v) ? v : undefined;
1023
1109
  };
1110
+ const ledger = boolFlag(rest, ["--ledger"]);
1024
1111
  const focus = strFlag(rest, ["--focus"]);
1025
1112
  const term = strFlag(rest, ["--term"]); // PLAN_VIZ_MEMORY.md: seed via normFactTerm-matched Fact(s), alongside --focus
1026
1113
  const depth = numFlag("--depth"); // max arcs (hops) from the focus node
1027
- const nodeLimit = numFlag("--limit"); // spiral length: total nodes walked
1114
+ const nodeLimit = numFlag("--limit"); // spiral length: total nodes walked (ledger mode: fact-row cap)
1028
1115
  const hubDegree = numFlag("--hub-degree"); // stop expanding THROUGH a node above N connections
1029
1116
  const edgeKindModeRaw = strFlag(rest, ["--edge-kind"]);
1030
1117
  const edgeKindMode = ["meta", "relation", "both"].includes(edgeKindModeRaw) ? edgeKindModeRaw : undefined;
1031
- const outPath = resolve(process.cwd(), strFlag(rest, ["--output", "--out"], "graph.html"));
1118
+ const outPath = resolve(process.cwd(), strFlag(rest, ["--output", "--out"], ledger ? "ledger.html" : "graph.html"));
1032
1119
  const { repo } = await resolveRuntimeConfig({ argv: rest });
1120
+ if (ledger) {
1121
+ const { computeLedgerData, renderLedgerHtml } = await import("../src/ledger-viz.mjs");
1122
+ const data = await computeLedgerData(repo, {
1123
+ ...(focus ? { focus } : {}),
1124
+ ...(!focus && term ? { term } : {}),
1125
+ ...(nodeLimit != null ? { rowLimit: nodeLimit } : {}),
1126
+ });
1127
+ const memoryAskBundle = await readMemoryAskBundle();
1128
+ await writeFile(outPath, renderLedgerHtml({ ...data, memoryAskBundle }), "utf8");
1129
+ process.stdout.write(
1130
+ `tmct viz --ledger — wrote ${data.meta.shown} fact row(s) around ${data.focus ? `'${data.focus}'` : "no focus"} to ${outPath}\n`,
1131
+ );
1132
+ if (data.meta.truncated) {
1133
+ process.stdout.write(
1134
+ `showing ${data.meta.shown} of ${data.meta.total} rows — narrow with --focus <term> or raise --limit\n`,
1135
+ );
1136
+ }
1137
+ return;
1138
+ }
1033
1139
  const vizGraph = await computeVizGraph(repo, {
1034
1140
  ...(focus ? { focus } : {}),
1035
1141
  ...(!focus && term ? { term } : {}), // --focus takes precedence when both are given
@@ -0,0 +1,24 @@
1
+ # crates — a second taught game, same closed frames as hanoi-3.
2
+ # Lines starting with # are skipped by import.
3
+ # No size order here: stacking is legal onto any clear crate or pallet, so this
4
+ # domain exercises the comparator precondition being optional. Goals can be a
5
+ # conjunction — teach several goal sentences before "solve it".
6
+ #
7
+ # After `tmct import --file crates.txt`, try this in `tmct chat` (or via --prompt):
8
+ #
9
+ # crate-c rests on crate-a. crate-a rests on pallet-1. crate-b rests on pallet-2.
10
+ # the goal is that crate-a rests on crate-b. the goal is that crate-b rests on pallet-2.
11
+ # solve it.
12
+ #
13
+ # (2 moves: clear crate-a, then stack it — the solver may route crate-c to
14
+ # pallet-3 or onto crate-b; both are optimal.)
15
+ a crate is a kind of container.
16
+ a pallet is a kind of place.
17
+ crate-a is a crate. crate-b is a crate. crate-c is a crate.
18
+ pallet-1 is a pallet. pallet-2 is a pallet. pallet-3 is a pallet.
19
+ you can stack a crate onto a pallet.
20
+ you can stack a crate onto a crate.
21
+ to stack a crate onto a target, nothing may rest on the crate.
22
+ to stack a crate onto a target, nothing may rest on the target.
23
+ stacking a crate onto a target makes the crate rest on the target.
24
+ a crate renders as a block. a pallet renders as a slot.
@@ -0,0 +1,30 @@
1
+ # hanoi-3 — a taught game definition. Lines starting with # are skipped by import.
2
+ # After `tmct import --file hanoi-3.txt`, try this in `tmct chat` (or via --prompt):
3
+ #
4
+ # disk-1 rests on disk-2. disk-2 rests on disk-3. disk-3 rests on peg-a.
5
+ # the goal is that every disk rests on peg-c. solve it.
6
+ #
7
+ # Variations, each stretching a different direction:
8
+ # scale — first teach: "disk-4 is a disk. disk-3 is smaller than disk-4."
9
+ # start all four on peg-a, same goal. 15 moves (2^4 - 1).
10
+ # any start — disk-1 rests on peg-b. disk-2 rests on peg-c. disk-3 rests on peg-a.
11
+ # the goal is that every disk rests on peg-c. solve it.
12
+ # other goal — same start, "the goal is that every disk rests on peg-b. solve it."
13
+ # partial — "the goal is that disk-3 rests on peg-c. solve it."
14
+ # (a one-fact goal; exercises the non-universal goal frame)
15
+ # legality — "what moves are legal now?" (findReachableSet, one ply, no plan)
16
+ # read-back — "what rests on disk-2?" / "is disk-1 clear?" (plain fact questions,
17
+ # no planning involved)
18
+ a disk is a kind of game piece.
19
+ a peg is a kind of place.
20
+ disk-1 is a disk. disk-2 is a disk. disk-3 is a disk.
21
+ peg-a is a peg. peg-b is a peg. peg-c is a peg.
22
+ disk-1 is smaller than disk-2. disk-1 is smaller than disk-3.
23
+ disk-2 is smaller than disk-3.
24
+ you can move a disk onto a peg.
25
+ you can move a disk onto a disk.
26
+ to move a disk onto a target, nothing may rest on the disk.
27
+ to move a disk onto a target, nothing may rest on the target.
28
+ to move a disk onto a disk, the disk must be smaller than the target.
29
+ moving a disk onto a target makes the disk rest on the target.
30
+ a disk renders as a block. a peg renders as a slot.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "1.10.13",
3
+ "version": "1.11.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; no codebase index of its own.",