npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@polycode-projects/the-mechanical-code-talker

v7.0.0

Published

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; indexes a repo on request (tmct index) or reads

Readme

tmct — The Mechanical Code Talker

npm version npm downloads licence live demos

Canonical home: GitLab. Installs come from npm. The GitHub repo is a read-only mirror, synced hourly. Issues and merge requests go to GitLab.

30-second quickstart

Install it:

npm install @polycode-projects/the-mechanical-code-talker

Teach it a fact, then ask about it. This is the whole public API for a one-off library call:

import { runTurn } from "@polycode-projects/the-mechanical-code-talker";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";

const memoryDir = await mkdtemp(join(tmpdir(), "tmct-quickstart-"));
await runTurn("a dog is an animal", { memoryDir });
const { answer } = await runTurn("what is a dog", { memoryDir });
console.log(answer);
await rm(memoryDir, { recursive: true, force: true });

Cloned the repo instead? The same engine runs as a chat over stdin:

printf 'hi\n/exit\n' | node bin/tmct.mjs

That greets you and exits 0. It's the project's own smoke test. (This one is marked skip= so the README suite doesn't run it. It would seed .tmct/ in whatever directory it runs from, and every other example here runs from a disposable temp dir instead of your clone.)

Architecture

Four layers, top to bottom. A surface (CLI, HTTP, TUI, or the browser) takes input. A service (chat, plan, research, ledger, adventure) runs the use case. Domain logic decides what's true and does no I/O of its own. An adapter sits at the seam. It is the only layer that touches disk, reading and writing the graph store, the OWL-labelled .tmct/ graph.

tmct architecture: surfaces call services, services call domain, adapters sit at the seam between domain and the graph store

flowchart TB
  subgraph Surfaces
    CLI
    HTTP
    TUI
    Web[Web browser]
  end
  subgraph Services
    chat
    plan
    research
    ledger
    adventure
  end
  Domain["domain — pure logic<br/>router · planner · codegraph · completions"]
  Adapters["adapters — the seam<br/>I/O · storage · providers"]
  Store[(".tmct/ graph store<br/>SQLite or in-memory")]

  Surfaces --> Services
  Services --> Domain
  Domain -.->|interface| Adapters
  Adapters --> Store

@polycode-projects/the-mechanical-code-talker

A pure-JS, no-LLM, offline, $0 chatbot in the ELIZA/PARRY lineage. It is pattern-driven and focused on software, and it makes no model calls.

tmct turns natural language directly into a graph database. On first run it seeds an everyday human-world persona: people, places, objects, nature, time. It already has a vocabulary before you teach it anything. A code-focused persona is available as an opt-in alternative: a software ontology (real definitions), a lexicon (everyday words mapped onto it), and a wider ConceptNet corpus. Point tmct at a real codebase's graph and it reasons over that too, whichever persona is active.

Teach it a fact in plain English and it mints a node. Ask it a question and it answers from what it was seeded with, what you taught it, and what it can derive by rule from both. Every answer is either grounded or an honest miss.

Host it yourself

tmct is a library first. You can embed it as a backend, running the engine server-side over your own fact store (DynamoDB, SQLite, in-memory, or a custom backend that passes the published conformance kit). Load corpus data from your own sources using the shipped tmct corpus CLI verb; the reference bands (WordNet, ConceptNet, Wikidata) are built in-tree. The deployed demos at tmct.polycode.co.uk show this architecture end to end: the chat and ledger pages run the engine in-page locally, while the news page is a thin client that calls server-side turn and feed endpoints — both run on the same tmct library backend and session table. Choose the architecture that fits your use case.

Repository layout

| directory | contains | |---|---| | bin/ | the CLI entrypoint (tmct.mjs) | | src/ | the shipped product: domain/ (pure logic), adapters/ (I/O, storage, providers), services/ (chat, adventure, research, ledger, plan), surfaces/ (CLI, HTTP, TUI, web), index/ (repo indexing) | | corpus/ | committed corpus and template data (ConceptNet, WordNet, NameNet, the generated persona vocab) | | data/ | seed data assets: sprites, phrasebook, games, response templates | | ontology/ | the software ontology (tmct-core.ttl) and memory shapes, in Turtle | | scripts/ | build, check, and maintenance scripts (npm run targets live here) | | infra/ | the AWS CDK app that provisions the deployed site and its OIDC/deploy roles | | examples/ | runnable example scripts and fixture repos used by the README's own examples and the test suite | | demo/ | standalone demo scripts (e.g. the agentic-loop demo) | | docs/ | reference docs: the adapter/repository-interface contracts, bibliography | | test/ | the unit, corpus, and estate-guard test suite (npm test) | | test-e2e/ | the end-to-end suite: real CLI/TUI spawns and Playwright browser journeys (npm run test:e2e) | | test-benchmarks/ | the benchmark harnesses (agentbench, idxbench, infbench, ingestbench, researchbench, synthbench) and their shared benchlib/ | | reports/ | benchmark write-ups (BENCHMARK_*.md) — see the root STATUS.md for the one-page summary these feed | | playtests/ | numbered playtest session logs, one edge found and fixed per entry | | archive/ | delivered PLAN_*.md/BENCHMARK_*.md docs, kept for history | | public/ | the demo site: the hand-written home page, help.html, the six about pages, receipts.html, claims.html, the shared stylesheet and the model/screenshot assets. The demo pages and browser bundles beside them are gitignored build outputs of npm run demo:build |

node_modules/ (dependencies) and dotfiles/hidden tooling directories are omitted above.

Teach it, then ask it to reason

This is real, runnable output. No cherry-picking, no model anywhere in the loop. The script lives at examples/teach-and-infer.mjs in this repo; run it yourself with node examples/teach-and-infer.mjs, or copy the source below:

import { runChat } from "@polycode-projects/the-mechanical-code-talker";
import { Readable, PassThrough } from "node:stream";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";

const TEACH = [
  "ahab is the father of john",
  "john is the father of ishmael",
  "a father is a kind of parent",
  "remember that ahab is male",
  "a grandparent is a parent of a parent",
  "a grandfather is a grandparent who is male",
];
const ASK = "is ahab the grandfather of ishmael";

// memoryBackend: "memory" keeps this whole session in the live handle only —
// no repo, no graph file, no disk write.
const repoPath = await mkdtemp(join(tmpdir(), "tmct-example-"));
const output = new PassThrough();
let transcript = "";
output.on("data", (chunk) => { transcript += chunk; });
const lines = [...TEACH, ASK].map((line) => line + "\n");
await runChat({ repoPath, memoryBackend: "memory", input: Readable.from([...lines, "/exit\n"]), output });

(examples/teach-and-infer.mjs adds the parsing that turns transcript into the answers below, plus cleanup — see the file for the full script.)

Output, captured from an actual run:

tmct> ahab is the father of john
noted — remembered: ahab fathers john

Goal (inferred): Teach/remember a new fact.
(this session keeps nothing — the fact is gone when it ends. Run without --ephemeral, or on a stored backend, to keep it.)

tmct> john is the father of ishmael
noted — remembered: john fathers ishmael

Goal (inferred): Teach/remember a new fact.
(this session keeps nothing — the fact is gone when it ends. Run without --ephemeral, or on a stored backend, to keep it.)

tmct> a father is a kind of parent
noted — remembered 1 fact: father rdfs:subClassOf parent (father is a type of parent)

Goal (inferred): Teach/remember a new fact.

Canonical: does "father" inherits "parent"? — ask(inherits, subject="father", "parent")
(this session keeps nothing — the fact is gone when it ends. Run without --ephemeral, or on a stored backend, to keep it.)

tmct> remember that ahab is male
noted — remembered: ahab is male

Goal (inferred): Teach/remember a new fact.
(this session keeps nothing — the fact is gone when it ends. Run without --ephemeral, or on a stored backend, to keep it.)

tmct> a grandparent is a parent of a parent
noted — remembered: a grandparent is a parent of a parent

Goal (inferred): Teach/remember a new fact.
(this session keeps nothing — the fact is gone when it ends. Run without --ephemeral, or on a stored backend, to keep it.)

tmct> a grandfather is a grandparent who is male
noted — remembered: a grandfather is a grandparent who is male

Goal (inferred): Teach/remember a new fact.
(this session keeps nothing — the fact is gone when it ends. Run without --ephemeral, or on a stored backend, to keep it.)

tmct> is ahab the grandfather of ishmael
yes — you told me: ahab fathers john (source: teach:chat:<session-id>@<timestamp>); father is a kind of parent (source: ace:chat:<session-id>@<timestamp>); you told me: john fathers ishmael (source: teach:chat:<session-id>@<timestamp>); you told me: ahab is male (source: teach:chat:<session-id>@<timestamp>)

Nobody told tmct that ahab is ishmael's grandfather. It combined four facts taught across six turns: the two father facts, the father-is-a-kind-of-parent alias, and the ahab-is-male property, then cited all four. The source: … parts are real provenance receipts. Every fact tmct stores records where it came from and when (more on that below).

The test suite replays every runnable example in this README against the live product, this transcript included. Every line shown must be a line the product prints, in the order shown, so if the chat behavior drifts from the output above, the suite fails and says so. Two blocks below are marked skip= and never run: one would touch the network, the other needs an LLM judge. docs/public-examples.md maps every example on every public surface to the test that holds it.

Facts don't have to come entirely from teaching. tmct's corpus already knows a dog can bark; teach it that Rover is a dog and it reasons the rest, citing both the taught fact and the corpus fact the answer chained through:

tmct> Rover is a dog.
noted — remembered 1 fact: rover rdf:type dog

Goal (inferred): Teach/remember a new fact.

Canonical: rover rdf:type dog — fact("rover", "rdf:type", "dog")

tmct> Does Rover bark?
yes — dog can bark (source: corpus:human /r/CapableOf) — via: rover is a dog (source: ace:chat:<session-id>@<timestamp>)

Every fact above is a small stored record, not just words in an answer. Here are two of them exactly as they sit in the graph — dog capableOf bark and dog subClassOf animal — timestamps normalized so this stays reproducible:

{
  "id": "fact:d5327019d311a956@src:corpus:human",
  "label": "dog mgx:capableOf bark",
  "class": "Fact",
  "derived_from": [],
  "mentions": [],
  "attributes": [
    {
      "prop": "rdf:type",
      "key": "type",
      "value": "rdf:Statement"
    },
    {
      "prop": "rdf:subject",
      "key": "subject",
      "value": "dog"
    },
    {
      "prop": "rdf:predicate",
      "key": "predicate",
      "value": "mgx:capableOf"
    },
    {
      "prop": "rdf:object",
      "key": "object",
      "value": "bark"
    },
    {
      "prop": "mgx:createdAt",
      "key": "createdAt",
      "value": "<timestamp>"
    },
    {
      "prop": "mgx:sourceId",
      "key": "sourceId",
      "value": "src:corpus:human"
    },
    {
      "prop": "mgx:factProvenance",
      "key": "provenance",
      "value": "corpus:human /r/CapableOf"
    },
    {
      "prop": "mgx:hasProseTokens",
      "key": "prose_tokens",
      "value": "bark capableof dog mgx"
    },
    {
      "prop": "mgx:trustScore",
      "key": "trustScore",
      "value": "0.7"
    },
    {
      "prop": "mgx:trustInputs",
      "key": "trustInputs",
      "value": {
        "sourceType": "corpus",
        "sourceId": "src:corpus:human",
        "createdAt": "<timestamp>"
      }
    },
    {
      "prop": "mgx:updatedAt",
      "key": "updatedAt",
      "value": "<timestamp>"
    }
  ]
}

{
  "id": "fact:08d02295fc0fed1c@src:corpus:human",
  "label": "dog rdfs:subClassOf animal",
  "class": "Fact",
  "derived_from": [],
  "mentions": [],
  "attributes": [
    {
      "prop": "rdf:type",
      "key": "type",
      "value": "rdf:Statement"
    },
    {
      "prop": "rdf:subject",
      "key": "subject",
      "value": "dog"
    },
    {
      "prop": "rdf:predicate",
      "key": "predicate",
      "value": "rdfs:subClassOf"
    },
    {
      "prop": "rdf:object",
      "key": "object",
      "value": "animal"
    },
    {
      "prop": "mgx:createdAt",
      "key": "createdAt",
      "value": "<timestamp>"
    },
    {
      "prop": "mgx:sourceId",
      "key": "sourceId",
      "value": "src:corpus:human"
    },
    {
      "prop": "mgx:factProvenance",
      "key": "provenance",
      "value": "corpus:human /r/IsA"
    },
    {
      "prop": "mgx:hasProseTokens",
      "key": "prose_tokens",
      "value": "animal dog rdfs subclassof"
    },
    {
      "prop": "mgx:trustScore",
      "key": "trustScore",
      "value": "0.7"
    },
    {
      "prop": "mgx:trustInputs",
      "key": "trustInputs",
      "value": {
        "sourceType": "corpus",
        "sourceId": "src:corpus:human",
        "createdAt": "<timestamp>"
      }
    },
    {
      "prop": "mgx:updatedAt",
      "key": "updatedAt",
      "value": "<timestamp>"
    }
  ]
}

Every attribute value here is a plain string except mgx:trustInputs, which is JSON.stringify'd into its value field in the actual store (parsed back out above for readability) since the flat attribute list has nowhere else to put a nested object.

The id says who asserted it: fact:<triple-hash>@<source-id>. One record is one source asserting one triple, so a fact two sources agree on is two records sharing the hash. The part before the @ is the fact id you see in citations and premise lists, and it never changes. Each record's own mgx:trustScore is just that one source's prior, from its type and its own track record — never guessed. What the whole fact is worth, corroboration across sources and how recently each of them said it, is folded over those records when you read them, so it answers for today rather than for the day it was written.

Point it at a codebase's graph and the same engine answers structural questions. examples/mini-webapp ships in this repo, so this runs as written:

$ node bin/tmct.mjs chat --repo examples/mini-webapp --ephemeral
tmct> what does app.mjs talk to?
src/server/router.mjs and src/handlers/tasks.mjs and src/handlers/users.mjs and src/lib/logger.mjs.
tmct> what talks to store.mjs?
src/handlers/tasks.mjs and src/handlers/users.mjs.
tmct> /exit

Try it live in your browser → runs the actual query engine client-side. No server, no install. The landing page answers codebase questions live, and six more pages each ground their own domain: a full chat seeded with 70,453 facts (the same eight bands as npm run init:xl), the memory ledger (every fact as a readable sentence; drill by clicking the terms inside), and a Towers-of-Hanoi plan replayed move by move. A 3D town square has a fox and goblins each plan their own move from what they've personally seen; the same game also opens onto a market day in the square and a chapel corner where two foxes hunt, each its own closed-opener scene. The text-adventure page renders its household staff (butler, cook, gardener, housekeeper) as turning, walking sprites on their own schedule, skipped entirely if you have reduced motion set. The rest are the text-adventure game and a sprite gallery whose chat dock answers from 1,480 generated sprite facts: hover a tile to flip it between its static and moving frame, click one to start it turning and click again to cycle its emotions, hit "random scene" to compose a sentence from the graph's own vocabulary (colours, emotions, roles, rooms), or open an ontology tree to see every class's rdfs:subClassOf parent drawn as a connector line rather than listed. The chat page and the ledger take the same paste-or-drop text in place; every page that holds a fact store exports it as JSONL.

help.html walks asking, teaching, and what to do when something goes wrong. receipts.html and claims.html carry the site's own numbers and claims, each figure naming the committed file it was measured or rendered from, including the ones that don't flatter tmct.

The site hosts its own copy of wink-nlp, ships its assets precompressed, and a service worker precaches the big ones, so a second visit works offline. tmct chat --render spider-fly|adventure|sprites [--output <path>] writes any of the three view pages as one self-contained file.

From a clone, two build scripts regenerate that demo so you can check it offline before it deploys. The example graph, the chat seed, the pages, and the engine copy land in public/. The in-page chat's query bundle is rebuilt from the same src/ the CLI runs:

npm run demo:build        # public/: demo graph, memory, ledger page, engine copy
npm run build:ask-bundle  # the browser query bundle the in-page chat runs

Two more surfaces, both generated by tmct itself:

npx tmct viz                          # ledger.html — your own memory as the same
                                      # readable, self-contained explorer
npx tmct init
npx tmct import --file .tmct/imports/games/hanoi-3.txt
npx tmct viz --focus disk-1 --output disks.html   # the explorer again, focused on one term
npx tmct chat --prompt 'disk-1 rests on disk-2. disk-2 rests on disk-3.
  disk-3 rests on peg-a. the goal is that every disk rests on peg-c. solve it.' \
  --render blocks --output plan.html  # an animated replay of the solved plan

More on the game file and the planner under "Teach it a game" below.

How it interprets you

Every message runs through multiple concurrent interpretation strategies: a grammar parse, keyword picking, noise-word removal, fuzzy matching. Their results are grouped by class.

One of the strategies is an ACE-inspired controlled grammar: when your text fits the controlled fragment, tmct emits OWL-labelled triples from it. Those triples are statements it can store, retrieve, and answer from later. Text that doesn't fit the grammar still gets the tolerant strategies. Nothing is rejected for being loose, fuzzy, or misspelled.

On top of that base, tmct reads the shapes people actually use, and each one resolves to a real graph traversal or declines cleanly:

  • everyday question forms: "what is Commit", "what's model.mjs for", "recent commits" as real dated history;
  • polite or indirect framing: "I'd like you to remember X" teaches like bare "remember X";
  • negation as a bounded set complement: "which modules do not import X?", with an empty result staying a miss, never a fabricated list;
  • reversible passives: "what is imported by Y" and "what does Y import" are opposite edges, not the same one;
  • finding by description: "find me the payment class" checks the type and its subclasses first, and says so plainly when it widens;
  • comparison: "compare TaskController and UserController" lines up both entities' real edges side by side, never a hand-written diff;
  • list follow-ups: after "which modules import src/core/model.mjs", "which of those are tested" resolves against that list;
  • follow-up context more broadly: "it", "that", and "those" bind to whatever you just asked about or were told, so a listed set survives a count ("which modules import http.mjs", then "how many of those are tested" — and "those" still means that same list on the turn after the count), and a genuine tie — "that" fitting two things said in the same turn — declines and lists both candidates rather than guessing which one you meant;
  • filler-clause openers: "oh nice. um what about cats" answers like its clean form underneath, but only when the opener matches a closed inventory of fillers and what's left still grounds; a stripped remainder can never reach the teach lane, so a filler-topped teach attempt still declines;
  • curated synonyms plus a filtered ConceptNet slice, clause openers (because/although/while), conditionals, and false-premise flags ("why does X still import Y" when it no longer does).

The full catalog with measured coverage lives in the reports/BENCHMARK_*.md reports.

Response finishing. Before an answer prints, it is segmented into typed spans: prose versus protected entities, paths, numbers, code, provenance, and receipts. A small data-driven grammar pass runs on the prose spans only, under a guard that proves the protected spans came through byte-for-byte.

A frozen regression suite plays out full multi-turn dialogues built from these phrasings, from a single question up to a messy, typo-ridden real user. Tier-by-tier detail is in NEXT.md.

How it guides you

When you touch a concept without asking a precise question, like "what is a class", "what about imports", or "what calls are there", tmct answers in three bands instead of dead-ending:

  1. the definition (a plain-English one-liner: "A class is a template that defines the structure and behaviour of objects." / "To import is to bring another module's definitions into the current one.");
  2. real instances from your graph: "In this codebase, for example: Record, Task and User (10 classes)", or actual edges "src/core/store.mjs imports src/core/model.mjs (18 import edges)";
  3. guided follow-ups: two or three concrete next questions, each one pre-checked against your graph so every suggestion is guaranteed to resolve: "Want to go deeper? Try: which classes inherit from Record / what does Task contain / where is User defined".

It fires for both noun concepts (class, module, function, method) and relation concepts (imports, calls, contains, inherits, tests), and only when tmct knows the concept and has instances of it. Otherwise the honest miss stands. This lets you start from a vague opener and still reach a useful answer. Natural phrasings are routed to the capability you meant: "what functions are in Task" → its members, "what defined saveStore" → where it's defined.

Detailed, grounded answers

Ask a precise question and tmct gives you a precise answer. Ask for more and it gives you more: "give me a detailed summary of how X works" (or "explain in detail how X works", or "...detailed overview/explanation of X") gets a longer, multi-sentence account instead of one line. Every sentence in it is lifted from a real graph edge, attribute, or taught fact. tmct never generates free text.

The wording varies a little too. A small, curated, deterministic pool swaps a handful of connector words, like "defined in", "located in", or "found in". The same fact doesn't read identically for every entity, but the same question against the same entity always renders the same way.

$ node bin/tmct.mjs chat --repo examples/mini-webapp --ephemeral
tmct> give me a detailed overview of how the Store works
Attribute: prose_tokens = memory record store [mgx:hasProseTokens]. Attribute: doc = In-memory record store. [seon:hasDoc]. Store — Class (id: fn:src/core/store.mjs#Store).

Programmatically, the same pipeline is generateCompletion() (src/domain/completions/complete.mjs):

import { fetchEntities } from "./src/adapters/source.mjs";
import { parseEntities } from "./src/domain/codegraph.mjs";
import { loadMemory } from "./src/adapters/memory/core.mjs";
import { createCompletionsGraphAdapter } from "@polycode-projects/the-mechanical-code-talker/createCompletionsGraphAdapter";
import { generateCompletion } from "@polycode-projects/the-mechanical-code-talker/generateCompletion";

const dir = "examples/mini-webapp";
const graph = parseEntities(await fetchEntities({ graphFile: `${dir}/.tmct/graph.json` }));
const memory = await loadMemory(dir);
const graphService = createCompletionsGraphAdapter(graph, memory);

const { text } = await generateCompletion(dir, "Store", { query: "Store", graph, memory, graphService });
console.log(text);   // prints the same text as the chat answer above

Planning across the graph

Some questions need more than one lookup. tmct plan is a small STRIPS/PDDL-style planner over the same read-only graph-query tools chat/serve use (src/domain/router/*): it decomposes a compound request, resolves and executes each step in order with a provable causal-link proof chain, and folds the results into one answer. A request neither the planner nor a single lookup can ground escalates to a closed-world goal-reasoner, which deduces maintenance goals (coverage gaps, change-coupling risk) straight from the graph, never from keywords in your question. Anything none of that grounds is an honest "no plan found", the same "grounded or an honest miss" rule as everywhere else in tmct.

$ node bin/tmct.mjs plan "of the modules impacted by src/lib/http.mjs, which are untested" --repo examples/mini-webapp
tmct plan: "of the modules impacted by src/lib/http.mjs, which are untested"
driver: resolver-0.8.0

steps:
  1. tmct_impact {"module":"src/lib/http.mjs"}
     Impact of changing src/lib/http.mjs (reverse closure over imports/calls edges, module- and function-level):
     total: 5 dependent(s) across 2 depth level(s) (lists capped for brevity).
     depth 1 (3 direct dependents):
       - src/handlers/base.mjs (imports it) — tests: none recorded
       - src/handlers/tasks.mjs (imports it) — tests: test/tasks.test.mjs
       - src/server/router.mjs (imports it) — tests: none recorded
     depth 2 (2):
       - src/handlers/users.mjs (reaches it through an intermediary) — tests: none recorded
       - src/server/app.mjs (reaches it through an intermediary) — tests: none recorded
  2. tmct_untested {}
     7 source module(s) with no covering test module:
       src/core/validate.mjs
       src/handlers/base.mjs
       src/handlers/users.mjs
       src/lib/http.mjs
       src/lib/logger.mjs
       src/server/app.mjs
       src/server/router.mjs

composed answer (4): src/handlers/base.mjs, src/handlers/users.mjs, src/server/app.mjs, src/server/router.mjs

tmct planned two calls (tmct_impact then tmct_untested), ran both against the real graph, and intersected the results itself. You get the four modules that are both downstream of the change AND missing coverage, not two separate lists you'd have to cross-reference by hand.

Leave the entity out and ask a maintenance question instead, and the goal-reasoner picks up where the planner refuses:

$ node bin/tmct.mjs plan "what most needs a test in this codebase" --repo examples/mini-webapp
tmct plan: "what most needs a test in this codebase"
driver: goal-0.8.1
...
composed answer (1): src/lib/http.mjs

It deduced the goal ("an impactful module must be tested"), gathered every untested module, ranked each by blast radius, and named the one worth testing first: src/lib/http.mjs, the module with the widest reach.

--tools tmct_impact,tmct_untested restricts which capabilities the planner is allowed to use; --json prints the full machine-readable loop result (calls, proof chain, composed answer) for a caller that wants to consume this programmatically rather than read the report. tmct plan --help has the full flag reference.

/goals (or tmct plan --goals on the command line) lists what a trace can be recognized against: the maintenance invariants above, one goal per declared tool, this world's own objective, and whatever you've taught as an action rule. Inside the text adventure, ask "what am I doing" or "what is the butler doing" and tmct reads the moves actually taken and names the declared goal they fit, by containment. A trace that fits two goals equally well declines and lists both rather than picking one, and a trace that fits none is told so plainly, pointed at the goal set it didn't match — the same honest-miss rule the rest of tmct runs on.

Teach it a game, then ask it to plan

The planner above works over a fixed toolset. This one works over rules you teach. A game definition is a plain-text file of controlled English: the classes, the pieces, the ordering, and the legal moves as taught action rules, with # comment lines carrying example prompts. tmct init scaffolds one at .tmct/imports/games/hanoi-3.txt, and tmct import --file teaches it sentence by sentence, reporting every line and refusing (exit 1) if any sentence declines.

Then one message states the board and the goal, and "solve it" searches the taught rules for the shortest move sequence:

tmct> disk-1 rests on disk-2. disk-2 rests on disk-3. disk-3 rests on peg-a. the goal is that every disk rests on peg-c. solve it.
plan found — 7 moves (shortest):
  1. move disk-1 onto peg-c
  2. move disk-2 onto peg-b
  3. move disk-1 onto disk-2
  4. move disk-3 onto peg-c
  5. move disk-1 onto peg-a
  6. move disk-2 onto disk-3
  7. move disk-1 onto disk-2

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".

Goal (inferred): Plan a move sequence from the current state to the goal (7 moves).

"next" executes one move at a time, writing each board state into memory as facts stamped with the step that produced them ("disk-1@step1 rests on peg-c", sourced to the plan). The final step re-reads the store and confirms the goal from those written facts, never assuming success. The stamp is what makes each step a separate record. A question about the piece itself ("where does disk-1 rest?", "is disk-1 clear?") reads the current board: the latest step's facts, not every step at once. The search is domain-general: the test suite teaches Towers of Hanoi purely as sentences for 1 to 8 disks and asserts the plan is exactly 2^n − 1 moves every time, and a second game (crates.txt, stacking crates with different rules and a two-goal conjunction) solves with zero interpreter changes. --render blocks writes the plan as a self-contained animated page (see "Two more surfaces" above).

Once a plan is found, you can question it without re-solving for real. "what if disk-1 started on peg-c instead?" re-solves the hypothetical board and reports its own move count, leaving the held plan, its cursor, and its move count untouched. "why did you move disk-1 first instead of disk-2?" (or "why not move disk-2 first?", the same answer) forces the alternative first move and names either the extra cost against the plan found or the taught precondition that blocks it outright.

Play a game with it

Three games run inside an ordinary chat session, no setup.

Guess the number. Say I'm thinking of a number between 1 and 100 and tmct guesses by narrowing an interval: answer higher, lower, or correct. It finds any number in at most 7 guesses, and if your answers contradict each other it names the contradicting pair and stops rather than guessing on. Say think of a number to swap seats: tmct commits to a secret and sticks to it. It answers your guesses, reveals the number on request, and corrects you from its own record if you claim it already said correct. The behaviour is pinned by test/corpus/games/guess-number.jsonl.

A text adventure. Say start the adventure (or play ashcombe hall) and tmct loads a small country-house mystery from a lazily-fetched worlds pack (corpus/worlds/) into the session's ordinary memory graph: rooms, objects and people become graph facts, and the verbs (go, take, open, unlock, look…) are taught action rules, not hard-wired code. Every move writes per-turn snapshot facts, look is an extractive digest of the graph, a blocked action declines by name, and one of the household moves on its own schedule whether you are there to see it or not. The full worked mystery is pinned step by step in test/corpus/games/adventure.jsonl.

Two agents, planning against each other. Say play spider and fly (or watch the spider and the fly) and tmct runs both sides itself. Neither is player-controlled. A spider hunts a fly across a 10×10 web; each side only believes what it can currently see (vision_radius, tunable), a fly wanders when nothing threatens it and evades when something does, a spider avoids other spiders, chases what it believes it sees, and builds a web when it holds position. Mass is real: both sides waste away each turn they don't eat, and a spider gains exactly the mass of what it catches. You can address either side directly (@spider the fly is east) to feed it a belief, true or false, and watch a wrong assertion mislead it for as long as the real target stays out of sight. tmct.toml's [games.spider-fly] table tunes every rate; the full mechanic is pinned in test/corpus/games/spider-fly.jsonl.

Learning on a miss

A question tmct cannot ground is still an honest miss. But on the cleanest kind of miss (a recognised word, a clean parse, simply no facts anywhere), it now consults two shipped, lazily-loaded packs before giving up:

  • corpus/child/: 93k everyday-world triples filtered from ConceptNet by a child-concept seed. Asked what is a kettle cold, tmct loads the term's triples into memory (provenance child:conceptnet:kettle, ranked below anything you teach) and answers from them; the next ask answers from memory directly.
  • corpus/reference/: 3,888 Simple English Wikipedia summaries. When the triples cannot answer, a matching article answers as a cited read-out (source: reference article "Otter"…, CC BY-SA 4.0).

Facts first, prose second; if neither pack carries the term, the turn is the same honest miss it always was, byte for byte. An unknown word, a parse failure, or an ambiguous reading never consults a pack at all. The gate and both fallbacks are pinned by test/corpus/reference.jsonl and the chat-lane tests beside it.

Live Wikipedia is the opt-in third tier, off by default. Turn it on with /wiki on in a session, --live-wikipedia on the command line, TMCT_LIVE_WIKIPEDIA=1, or tier = "tier3" under [corpus] in tmct.toml. The browser chat page has the same switch ("ask Wikipedia when I don't know"). When it is on and both shipped packs miss, tmct makes two requests to en.wikipedia.org (a title search, then the page summary) and answers as a cited read-out, CC BY-SA, pinned to the revision it read (provenance reference:wikipedia-live:<Title>@<revid>). A failed lookup (no matching title, a timeout, a rate limit) leaves the honest miss byte-identical.

A live article carries its own source-type prior (referenceLive, 0.5) below the curated revision-pinned pack (reference, 0.6). A fact fetched live never outranks the shipped article on the same term, and a fact you teach outranks both.

That miss-time rescue is passive. research <topic> is the deliberate version: say research owls and tmct fetches the article, ingests it as graph facts, and queues the topics its lead section links to, one per research next (the chat page's own play button submits that for you). Asking by name is its own consent for the fetch: unlike the miss-time rescue, it needs no /wiki on. A topic that fails to fetch or ground reports the miss plainly and moves the queue on rather than faking a result. research stop clears the queue.

How it remembers

tmct's memory has two layers, both fed by every parsed request and response and by cleaned session logs:

  • an always-loaded OWL-labelled graph on disk under .tmct/, a SQLite file by default (local artifact, never committed);
  • text blocks under a PageRank-style index, pulled into context on relevance rather than loaded wholesale.

Every session also writes its own human-readable transcript, .tmct/session-<id>.md, a glow-friendly Markdown file with one heading per turn, the question as a blockquote, the reply in a fenced block. The browser chat page's "export .md" button writes the same shape.

With no graph at all, tmct starts empty and remembers what you tell it. The .tmct/ graph is created from the conversation. On a first run it seeds the committed vocabulary so it knows what it's talking about from turn one: an everyday human-world persona covering people, places, objects, nature, time and events, body and food, and mind vocabulary. It's hand-curated from Open English WordNet and bridged to Schema.org's top-level classes, so "what is a dog?" answers offline, from disk, on turn one. --ephemeral (used by the shipped npm run example:* demos) reads a graph but writes nothing back.

A bare install stays domain-neutral. No code vocabulary reaches any banner, greeting, count, miss, help, or orientation text.

A domain pack bundles a corpus of facts, a lane vocabulary for that domain, optional templates, and a declared grounding channel. The code pack is the first shipped pack: a curated software ontology (SEON), the full filtered ConceptNet slice (CC-BY-SA 4.0), and code-domain lane vocabulary. Activate it with tmct init --with-persona code or automatically by running tmct index inside a real codebase.

The default persona also comes in three sizes: Small (~664 facts, the default), Medium (~1,608, tmct init --persona-size medium) and Large (~13,600, --persona-size large, deep enough to chain real multi-hop reasoning).

Memory backends

The default backend persists taught facts to a local SQLite file (.tmct/memory/graph.sqlite). A second backend, memory, keeps them in the process only and writes nothing to disk. tmct init --memory-backend <name> writes the choice into tmct.toml and every later tmct chat in that repo picks it up. Precedence is --memory-backend flag > TMCT_MEMORY_BACKEND env > tmct.toml's [memory] backend > the sqlite default. A library caller sets the same thing directly: runChat({ memoryBackend: "memory" }).

Three init presets in package.json wrap the common setups. What each one runs (--force re-initializes the same directory):

npx tmct init                                # npm run init:sqlite — sqlite is already the default backend
npx tmct init --force --with-persona human   # npm run init:persona:human — the default persona, made explicit
npx tmct init --force --with-persona empty   # npm run init:persona:empty — no seeded vocabulary at all

Teaching isn't limited to the ACE grammar's fixed shapes. Tell tmct an arbitrary fact, like "margo really eats ribs", and it mints a fact you can ask about directly: "what does margo eat". New vocabulary compounds as you teach: "redis is a cache" mints "redis" even though it was never in the built-in lexicon, as long as one side of the sentence is already grounded. tmct never mints a fact between two totally ungrounded terms; it declines and nudges you to ground one side first. Quantified teaching stores the quantifier ("some functions are risky" … "how many functions are risky" → "A few."), and "how many facts are there" counts the store back. A quantified subject over a locative works the same way: "every disk rests on peg-a" teaches, so does "one more disk rests on peg-a", and the read-back answers either shape ("does every disk rest on peg-a" → "yes — …").

The store answers about its own contents directly. "list facts" and "list utterances" enumerate what it holds; "how many sessions are there", "how many sources", and "how many rules" count the store's own book-keeping classes rather than the code graph. A taught class answers both shapes too: after "dog is a kind of animal", "how many animals are there" counts its members and "list all animals" reads them back, each cited to where it came from.

When you ask about a term, the read-back shows each "is a kind of" object with its own superclass chain: "what is rover" answers "rover is a dog → canine → mammal → animal" (a bare, undeclared name like "rover" reads as one named individual, so the first hop drops "kind of" — every class hop after it keeps the phrase). If one label carries two unrelated senses (you taught "rover is a dog" and a corpus row says "rover is a scout"), the answer groups by concept ("rover, the dog:" / "rover, the scout:") instead of listing two unrelated lines as if they were one thing. The split is deterministic over the stored hierarchy: two senses part when a stored disjointness separates their ancestors, when their chains never meet, or when they meet only at the very top. When the evidence is thin the answer stays a flat list. Grouping is presentation, and it never retracts or reranks a fact.

Teaching doesn't have to be typed, either. tmct extract runs a plain text file through the same recognizer the chat's teach lane uses. Sentences the recognizer grounds become fact rows; everything else is skipped and counted, never paraphrased. Add --repo <abs> to write them into that repo's own memory; without it nothing on disk is mutated and the facts print as JSONL:

printf 'We deployed redis last week. a cache is a kind of store. Why was it slow?\n' > /tmp/notes.txt
node bin/tmct.mjs extract /tmp/notes.txt
{"subject":"cache","predicate":"rdfs:subClassOf","object":"store","provenance":"extracted:notes.txt","quantifier":"","sentence":"a cache is a kind of store."}
3 sentences found, 1 recognized as fact (1 fact row), 2 skipped — not a recognized declarative shape (an honest, expected gap; this is an attempt, not full NLU).

Pass --repo <path> instead to write the recognized facts straight into that repo's memory, or --out <file.jsonl> to save the rows. Each one carries an extracted:<file> provenance tag at its own trust tier.

--optimistic adds a second, lower-trust tier over the sentences the strict recognizer skips: a copula or a known relation verb flanked by two nouns becomes a candidate triple, stored under its own optimistic-extract:<file> provenance (prior 0.35, below every curated pack) with no operator tag riding alongside, so a fuzzy guess can never corroborate a curated fact. It is an attempt, not full NLU: a sentence with no clean pair yields nothing. --canonical prints each grounded fact as a triple, noting how each endpoint already links into the store.

The same pipeline is one library seam, ingestText(text, options) (exported as @polycode-projects/the-mechanical-code-talker/ingest), and one cold tool, tmct_ingest. A browser page, a script, or a tool-calling agent can ground text without the CLI.

Provenance and trust

Every fact and text block records where it came from and when. Sources are first-class individuals: operator chat, a curated corpus, a provider graph, a web scrape, a rule-derived entailment. A fact links back to all of them (mgx:derivedFrom / mgx:statedBy / mgx:canonicalisedFrom), timestamped with mgx:createdAt. From those links tmct computes a deterministic, explainable trust score, combining a source-type prior with corroboration (how many independent sources agree) and recency. It is never hand-set. Every value traces back to its inputs. Retrieval then ranks by relevance × trust, so a corroborated, operator-stated fact outranks a lone web scrape on the same question. When two trusted sources disagree, the /memory inspector shows both sides with their provenance rather than silently picking a winner.

Speculative inference (a maintenance job, not a chat cost)

tmct syllogise [--depth n] [--budget n] is an offline, bounded, deterministic batch: a forward-chaining materialisation over the memory's OWL 2 RL rule kernels (the classical syllogism is one of them, and the verb keeps Aristotle's broader sense; see the bibliography) that writes new entailed facts. They are low-trust and retractable, never outranking a stated fact.

The full-store batch is a maintenance job, off the chat's hot path. One bounded sibling does run inline: when a learn-on-miss load pulls new facts in (a child pack, a reference or live-Wikipedia article), a small focus-scoped pass around the loaded term connects those new facts to what's already remembered, so a load becomes durable knowledge rather than an island. It shares the same kernels, the same entailed:* provenance and the same low, retractable trust; only its scope and budget shrink.

Install & use

npm install -g @polycode-projects/the-mechanical-code-talker
tmct                                  # bare = chat (the headline)
tmct chat --repo /abs/path/to/repo    # chat over a specific repo's graph
tmct init                             # scaffold .tmct/, tmct.toml, seed + provenance
tmct syllogise                        # offline: pre-derive entailed facts (maintenance)
npm run viz && open ledger.html       # self-contained HTML memory-ledger explorer

Inside the chat: /help lists commands, /memory inspects what tmct remembers (grouped by OWL class, with provenance and any contradictions), /exit leaves. TMCT_GRAPH_FILE overrides the graph location.

tmct --help (or npm run help from a clone of this repo) is the full, up-to-date flag reference for every subcommand. A bare npm run only lists script names, so npm run help is the documented way in from there.

tmct init is the onboarding surface for the repository interface below: it creates the .tmct/ directory, writes the externalized tmct.toml configuration, seeds the default persona, and records provenance. A host package or a bare user gets a working install in one command.

Install-size note: tmct depends on wink-nlp's deterministic English language model (~3.8 MB installed). That model is a lookup table, not an LLM.

Custom memory backends

Memory — the facts you teach tmct — lives behind a pluggable adapter seam. The CLI and library come with three backends: in-memory (ephemeral), SQLite (the CLI's default), and DynamoDB (for hosted deployments). You can inject a custom backend and tmct will read and write through it instead. The backend is a small async row store scoped to one session key — it needs eight methods (read, write, delete, and a couple of scalar sidecars). The published conformance suite validates that a custom backend meets the contract. See docs/adapter-contract.md for the full interface and src/adapters/memory/row-backend-memory.mjs for the reference implementation. Wire it into a session:

import { createSession } from "@polycode-projects/the-mechanical-code-talker";
import { createMyBackend } from "./my-backend.mjs";

const backend = createMyBackend(sessionKey, config);
const session = await createSession({ memoryBackend: backend });

Full command reference (tmct --help)

tmct --help always prints the real, current flags. What follows is that same output, split into one block per command with a short note on what each one is for, so it is easier to scan than the raw dump.

Every subcommand shares one flag/config resolver (src/services/cli-args.mjs), which is why --repo, --graph, and --config behave the same way everywhere.

The bare command and tmct chat open the interactive session:

Usage:
  tmct                         interactive chat (the headline surface)
  tmct chat [--repo <abs>]     chat over a specific repo's graph
       [--graph <path>]        explicit graph file (repeatable — multiple graphs merge;
                               see src/adapters/graph-merge.mjs); wins over --repo/TMCT_GRAPH_FILE/tmct.toml
       [--config <path>]       an alternate tmct.toml location (a file or a directory)
       [--ephemeral]           read the graph but write nothing back (demo/read-only)
       [--prompt "<text>"]     one-shot: run the prompt's sentences as turns and print
                               the final answer (teach state first, trigger last)
       [--render blocks]       with --prompt: when the final turn produced a plan,
                               write it as a self-contained animated page
       [--render spider-fly|adventure|sprites]  write that demo view as one self-contained
                               page (no --prompt; the game pages inline their engine)
       [--output <path>]       the rendered page's path (default plan.html for
                               blocks, <archetype>.html for the views)
       [--narrate]             start with narrate mode on — a verbose, developer-facing
                               trace of decision points/matched pattern/results/goal per
                               turn, appended under a "--- narrate ---" marker (also
                               TMCT_NARRATE=1; toggle mid-session with /narrate on|off)
       [--live-wikipedia]      start with the live Wikipedia supplement on — a question
                               nothing local can answer also tries en.wikipedia.org,
                               cited (network; also TMCT_LIVE_WIKIPEDIA=1 or tmct.toml
                               corpus tier3; toggle mid-session with /wiki on|off)
       [--plain]               force the plain readline shell (the default when
                               stdin/stdout is not a terminal)
       [--memory-backend <default|memory|sqlite>]  storage backend for taught facts this
                               session (CLI flag > TMCT_MEMORY_BACKEND env > tmct.toml's
                               [memory] backend > sqlite, .tmct/memory/graph.sqlite)

tmct memory is the CLI-side view of the same data the /memory chat command shows:

  tmct memory [--repo <abs>]   what tmct remembers: facts, utterances, sessions,
       [--config <path>]       folded blocks (the /memory chat command, from the shell)
       [--verbose]
       [--export <file.jsonl>]  write every stored fact as JSONL (subject/predicate/object/
                               provenance) to a file and exit — the shape `tmct extract`
                               emits, for audit or backup

tmct init sets up a repo for the first time: .tmct/, tmct.toml, a seed, and a provenance record. Most of its flags choose what gets seeded and where config is written:

  tmct init [--repo <abs>]     initialize a repo for tmct (default: cwd): .tmct/,
       [--force]               tmct.toml, .tmct/TOOLS.md (the cold-tool catalog),
                               tier-1 corpus seed, provenance record
       [--corpus <id|path>]    also seed a corpus — a bundle name (code|conceptnet|child|
                               namenet|general) or a jsonl file path — opt-in, offline, $0
       [--ontology <name|path>]  activate+seed an ontology bundle (a recognized name or a path)
       [--lexicon <name|path>]  activate a lexicon bundle (recognized name or a path;
                               merged read-time, never seeded — see mergedLexiconExtra)
       [--graph <path>]        set graph_file/graph_files in tmct.toml (repeatable)
       [--config <path>]       write to an alternate tmct.toml location
       [--with-persona <name>]  write an explicit [extensions]/[bias] preset into tmct.toml
                               ("code" — today's implicit default, made explicit)
       [--persona-size <medium|large>]  grow the default "human" persona's fact count
                               beyond Small (the default): "medium" activates
                               human-medium.jsonl (~1,608 facts total), "large" also
                               activates human-large.jsonl (~13,600 facts total,
                               with genuine multi-hop hypernym chains) — additive
                               size tiers of the SAME bundle, not separate personas
       [--memory-backend <default|memory|sqlite>]  write tmct.toml's [memory] backend
                               (same flag name as `tmct chat`) — a later `tmct chat`
                               in this repo picks it up with no flag needed

tmct index is the producer side of the graph seam. It walks a repo's own source and writes the .tmct/graph.json that chat, serve and the CLI then read.

  tmct index [--repo <abs>]    produce a code graph from a repo's OWN source (default: cwd):
       [--no-history]          walk the tree, parse JS/TS with the TypeScript compiler
                               API, read git history, and write <repo>/.tmct/graph.json —
                               the artifact chat/serve/cli then read. --no-history skips
                               the git passes (no commit/touches/cochange edges)

tmct import does the same activation as tmct init, but against a repo that is already set up. Its --graph flag works differently from the others: it appends to tmct.toml's graph_files array instead of activating a bundle.

  tmct import [--repo <abs>]   activate+seed into an ALREADY-initialized repo (any
       [--corpus <id|path>]    combination of these flags in one call). --graph is a
       [--ontology <name|path>]  DIFFERENT operation from the others: it APPENDS to
       [--lexicon <name|path>]  tmct.toml's graph_files array (multi-graph growth),
       [--graph <path>]        never an extensions-bundle activation.
       [--file <defs.txt|facts.jsonl>]  teach a definition file: a .txt taught sentence by
                               sentence (# lines are comments; a declined sentence exits
                               non-zero, named), or a .jsonl triple dump loaded fact by
                               fact, keeping each line's own provenance
       [--memory-backend <default|memory|sqlite>]  same knob as `tmct init`
       [--config <path>]

tmct extract is the document route into memory described under "Teach it" above: the same teach recognizer, reading a file instead of your typing:

  tmct extract <text-file>     read a plain text file's sentences through the chat's own
       [--file <text-file>]    teach recognizer and keep the facts it grounds; every
                               other sentence is skipped and counted, never paraphrased
       [--repo <abs>]          write the facts into that repo's own tmct memory; without
                               it nothing on disk is mutated and the facts print as JSONL
       [--out <file.jsonl>]    write that JSONL to a file instead of stdout
       [--optimistic]          also run a lower-trust fuzzy tier over the sentences the
                               strict recognizer skips; candidates rank below every curated pack
       [--canonical]           print each grounded fact as a triple linked into the store

tmct extend --validate checks a third-party extension pack's declared resources before you switch any repo's tmct.toml over to it:

  tmct extend --validate <dir>  validate a third-party extension pack's declared
       [--config <path>]       resources (corpus/lexicon/templates) before activating
                               it in any repo's tmct.toml; exits non-zero on failure

tmct syllogise is the offline maintenance job described under "Speculative inference" above:

  tmct syllogise [--repo <abs>]  speculative inference (offline maintenance job): a deterministic
       [--depth <n>] [--budget <n>]  forward-chaining materialisation over OWL 2 RL rule kernels
       [--config <path>]       (the classical syllogism among them), writing bounded, low-trust,
                               retractable entailed facts (never on the chat path)

tmct viz renders the memory graph as the ledger explorer, a single, self-contained HTML file you can open in a browser:

  tmct viz [--repo <abs>]      write one self-contained HTML page: the memory graph as a
       [--focus <term>]        readable ledger of fact-sentences around one focus term,
       [--term <word>]         with segments, a two-hop minimap, and an in-page chat dock
       [--limit <n>]           that answers from the embedded graph. Focuses on the newest
       [--output <path>]       taught fact's subject by default (--focus <term> or
       [--config <path>]       --term <word> override it); --output defaults to
                               ledger.html in the cwd; --limit caps the embedded fact
                               rows; --term resolves via the same normalization chat uses.

tmct digest <term> turns what the graph knows about one term into a short, readable paragraph — the vocabulary-side sibling of tmct cli digest's code map. The narrative leads, its sources follow, and the full fact count points at the ledger for the rest:

  tmct digest <term>           a readable digest of what the graph knows about one term:
       [--repo <abs>]          a bounded narrative first (selected, sense-filtered,
       [--graph <path>]        deduped), then its sources and the stored-fact count.
       [--config <path>]       The vocabulary-side sibling of `cli digest`'s code map.

tmct serve runs an Anthropic Messages API-compatible HTTP endpoint over the graph, so a tool-loop client can call tmct like a model, at $0:

  tmct serve [--repo <abs>]    run the Anthropic Messages API-compatible endpoint
       [--host <h>] [--port <n>]  (POST /v1/messages) over the graph — a deterministic,
       [--graph <path>]        no-LLM "model" a tool-loop client can call; $0 usage.
       [--config <path>]       Defaults: host 127.0.0.1, port 8787. Ctrl+C to stop.

A tool-loop client talks to it like any Messages endpoint. One round trip against the example graph, end to end:

node bin/tmct.mjs serve --repo examples/mini-webapp --port 8791 &
SERVE_PID=$!
until curl -s -o /dev/null http://127.0.0.1:8791/v1/messages; do sleep 0.2; done
curl -s http://127.0.0.1:8791/v1/messages -H 'content-type: application/json' \
  -d '{"model":"tmct","max_tokens":256,"messages":[{"role":"user","content":"which modules import src/core/model.mjs?"}]}'
kill $SERVE_PID

tmct plan is the capability router described under "Planning across the graph" above:

  tmct plan "<request>"        the capability router: compose/execute read-only graph-
       [--repo <abs>]          query tool calls for a compound or maintenance-goal
       [--graph <path>]        request ("of the modules impacted by X, which are
       [--config <path>]       untested", "what most needs a test") — a real STRIPS/
       [--tools <a,b,...>]     PDDL planner (src/domain/router/*), never a guessed call.
       [--json]                Prints the grounded step sequence + composed answer,
                               or an honest "no plan found". --tools restricts the
                               declared toolset; --json prints the full loop result.

tmct cli is a lower-level, carry-over surface for invoking a graph tool directly:

  tmct cli <tool> '{…}'        invoke a graph tool directly (carry-over, de-emphasized)
       [--repo <abs>]          the repo to answer from; the payload's "repo_path" says
       [--graph <path>]        the same thing. --graph names the graph file outright
       [--config <path>]       (repeatable), --config an alternate tmct.toml
  tmct cli digest '{…}'        architecture map + per-module context bundles

tmct corpus loads a shared, read-only corpus band into a DynamoDB row-backend table, or clears one; the deployed turn service reads the loaded bands per query:

  tmct corpus load <band> [--table <name>] [--source <path>] [--dry-run]  load a shared, read-only corpus band (wordnet-complete, or a
       [--table <name>]        consumer's own) into a DynamoDB row-backend table from a jsonl of
                               wire-row-shaped facts (default table from TMCT_DYNAMO_TABLE); a
                               source whose digest already matches the band's manifest is a no-op
       [--source <path>]       the band's jsonl (a scripts/corpus-bands/ build output, or any jsonl
                               in the same wire-row shape)
       [--dry-run]             report the row count and source digest without writing anything
  tmct corpus clear <band> [--table <name>]  physically delete every row and the manifest for one band
  tmct --help                  show this help

The help closes with two notes on where a chat session runs and what it leaves behind:

On a terminal, chat opens the full-screen TUI; piped input gets the plain shell.
In chat: /help lists slash-commands; /exit leaves. Session log → <repo>/.tmct/session-<id>.md.

Two precedence chains apply across every command above, in this order:

Shared graph-path precedence (chat/serve/cli; see src/services/cli-args.mjs): --graph flag(s) >
TMCT_GRAPH_FILE env > tmct.toml graph_file/graph_files > --repo-derived
<repo>/.tmct/graph.json > git-root/cwd default. On the `cli` route, a payload's
"repo_path" fills the --repo tier when the flag is absent.

Memory-backend precedence (chat; see src/services/chat.mjs createSession): --memory-backend
flag > TMCT_MEMORY_BACKEND env > tmct.toml [memory] backend > sqlite (the built-in
default, .tmct/memory/graph.sqlite); "memory" keeps the store in-process only. Set it
once with `tmct init --memory-backend <...>` and every later `tmct chat` in that
repo picks it up with no flag needed.

npm run init in package.json chains one init and two import --corpus calls to combine the human persona, the code domain pack and conceptnet into ~37,700 facts on the default sqlite backend, a working example to copy from (init:large is now just an alias for it). npm run init:small is the lighter variant: a bare tmct init, default persona only, no big corpora, 688 facts. It's what a first npm run chat in an uninitialized repo bootstraps automatically, so no init command is required just to start talking; running init:small explicitly (after rm -rf tmct.toml .tmct) gets you back to that same minimal state on purpose. init:xl starts from the large persona tier and adds wordnet-xl, namenet and the child vocabulary pack (~127,000 facts); init:xxl swaps wordnet-xl for the full WordNet slice (~296,000 facts, the biggest committed vocabulary, so expect its imports to take a few minutes). The xl chain, spelled out:

npx tmct init --persona-size large   # npm run init:xl runs this whole chain from a clone
npx tmct import --corpus code
npx tmct import --corpus conceptnet
npx tmct import --corpus wordnet-xl  # init:xxl uses wordnet-full here instead
npx tmct import --corpus namenet
npx tmct import --corpus child

tmct.toml reference

tmct init writes a sparse tmct.toml with just the keys it needs. The file recognizes more keys than that default covers. Below is one config with every recognized key set, so you can see the full surface in one place (src/adapters/toml-config.mjs is the source of truth; src/services/extensions.mjs defines the [extensions.*]/[bias] shape).

# Newline-delimited-file form is also accepted: repositories = "repos.txt"
repositories = ["../other-service", "../another-service"]

# Where generated output (e.g. tmct viz's default ledger.html) resolves to.
out_root = "./out"

# The code-graph JSON artifact. TMCT_GRAPH_FILE overrides this at runtime.
graph_file = ".tmct/graph.json"
# Extra graphs, merged alongside graph_file (ids that collide are auto-prefixed).
graph_files = [".tmct/graph.json", ".tmct/legacy-graph.json"]

[graph]
# A chat session reads this repo's graph and writes