@polycode-projects/the-mechanical-code-talker 1.3.1 → 1.4.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
@@ -4,9 +4,77 @@
4
4
 
5
5
  A pure-JS, **no-LLM**, offline, **$0** chatbot in the ELIZA/PARRY lineage:
6
6
  pattern-driven, best-efforts, and obsessed with software the way PARRY was
7
- obsessed with the mafia. No model calls anywhere. Interpretation is mechanical
8
- (deterministic language libraries, template sets, committed corpuses). Memory is
9
- a graph on disk. Every answer is either grounded or an honest miss.
7
+ obsessed with the mafia. No model calls anywhere.
8
+
9
+ tmct turns natural language directly into a graph database. The graph starts
10
+ seeded with an **ontology** (a curated software vocabulary with real
11
+ definitions), a **lexicon** (everyday words mapped onto that vocabulary), and
12
+ a **corpus** (a filtered ConceptNet slice of general-world terms). Teach it a
13
+ fact in plain English and it mints a node. Ask it a question and it answers
14
+ from what it was seeded with, what you taught it, and what it can derive by
15
+ rule from both. Every answer is either grounded or an honest miss.
16
+
17
+ ## Teach it, then ask it to reason
18
+
19
+ This is real, runnable output. No cherry-picking, no model anywhere in the
20
+ loop. Copy it into a file and run it:
21
+
22
+ ```js
23
+ import { runChat } from "@polycode-projects/the-mechanical-code-talker";
24
+ import { Readable, PassThrough } from "node:stream";
25
+ import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
26
+ import { tmpdir } from "node:os";
27
+ import { join } from "node:path";
28
+
29
+ // A graph producer feeds tmct a code graph like this one (see "The repository
30
+ // interface" below). This is a 4-node slice: two modules, a base class, and a
31
+ // class that inherits it.
32
+ const graph = {
33
+ individuals: [
34
+ { id: "mod:src/handlers/base.mjs", label: "src/handlers/base.mjs", class: "Module" },
35
+ { id: "mod:src/handlers/tasks.mjs", label: "src/handlers/tasks.mjs", class: "Module" },
36
+ { id: "fn:src/handlers/base.mjs#Controller", label: "Controller", class: "Class" },
37
+ { id: "fn:src/handlers/tasks.mjs#TaskController", label: "TaskController", class: "Class" },
38
+ ],
39
+ objectProperties: [{
40
+ predicate: "inherits", prop: "seon:hasSuperType", count: 1,
41
+ examples: [{ subject: "fn:src/handlers/tasks.mjs#TaskController", object: "fn:src/handlers/base.mjs#Controller",
42
+ subjectLabel: "TaskController", objectLabel: "Controller" }],
43
+ }],
44
+ };
45
+
46
+ const repoPath = await mkdtemp(join(tmpdir(), "tmct-demo-"));
47
+ await mkdir(join(repoPath, ".tmct"), { recursive: true });
48
+ await writeFile(join(repoPath, ".tmct", "graph.json"), JSON.stringify(graph));
49
+
50
+ // tmct has one API surface for both teaching and asking: a chat turn, in
51
+ // English. Each call below is a short session over the same repo, so what
52
+ // gets taught in the first call is still remembered in the second.
53
+ async function tell(line) {
54
+ const out = new PassThrough();
55
+ let transcript = "";
56
+ out.on("data", (chunk) => { transcript += chunk; });
57
+ await runChat({ repoPath, input: Readable.from([line + "\n", "/exit\n"]), output: out });
58
+ return transcript.split("\n").find((l) => l.startsWith("tmct> "));
59
+ }
60
+
61
+ await tell("a controller is a kind of handler"); // Learn
62
+ console.log(await tell("is TaskController a handler")); // Infer from learnings
63
+ ```
64
+
65
+ Output, captured from an actual run:
66
+
67
+ ```
68
+ tmct> yes — the code graph says TaskController inherits Controller, and you
69
+ told me: controller is a kind of handler (source: ace:chat:<session-id>@<timestamp>)
70
+ ```
71
+
72
+ Nothing here was told that "handler" and "Controller" relate. tmct combined a
73
+ fact already in the graph (`TaskController inherits Controller`) with a fact
74
+ you just taught it in English (`controller is a kind of handler`) and wrote
75
+ the connecting sentence itself, citing both sources. The `source: ace:chat:…`
76
+ part is a real provenance receipt. Every fact tmct stores records where it
77
+ came from and when (more on that below).
10
78
 
11
79
  ```
12
80
  $ tmct
@@ -21,6 +89,67 @@ tmct> /exit
21
89
  is a real, interactive chat demo running client-side. Your browser runs the
22
90
  actual query engine against a small example codebase, no server, no install.
23
91
 
92
+ ## Compared to other JS libraries
93
+
94
+ Nothing found in the JS ecosystem does this exact combination: teach in
95
+ English, reason over a seeded ontology, answer in English, with no model call
96
+ anywhere. Three kinds of library each cover one piece of it well.
97
+
98
+ **[compromise](https://github.com/spencermountain/compromise)** (`npm i
99
+ compromise`, 12k+ GitHub stars, describes itself as "modest natural language
100
+ processing") is the general-purpose NLP toolkit of the three. Its own README
101
+ example:
102
+
103
+ ```js
104
+ import nlp from 'compromise'
105
+ let doc = nlp('she sells seashells by the seashore.')
106
+ doc.verbs().toPastTense()
107
+ doc.text()
108
+ // 'she sold seashells by the seashore.'
109
+ ```
110
+
111
+ It tags part-of-speech, conjugates verbs, and parses fractions and money far
112
+ more broadly than tmct attempts to. It has no graph, no ontology, and no
113
+ memory between calls. Each call is stateless text in, text out.
114
+
115
+ **[N3.js](https://github.com/rdfjs/N3.js)** (`npm i n3`) is a mature,
116
+ spec-compliant RDF/OWL toolkit: parsing, writing, and in-memory storage of
117
+ triples. Its own README example:
118
+
119
+ ```js
120
+ const parser = new N3.Parser();
121
+ parser.parse(tomAndJerry, (error, quad, prefixes) => {
122
+ if (quad) console.log(quad);
123
+ else console.log("That's all, folks!", prefixes);
124
+ });
125
+ ```
126
+
127
+ (`tomAndJerry` is Turtle text the caller writes by hand: `c:Tom a c:Cat.
128
+ c:Jerry a c:Mouse; c:smarterThan c:Tom.`) N3.js is the right choice if you
129
+ already have RDF and need to parse or serialize it fast. It has no
130
+ natural-language front end (you write the triples yourself) and no built-in
131
+ reasoning beyond an optional, limited basic-graph-pattern reasoner.
132
+
133
+ **[elizabot](https://github.com/tkafka/node-elizabot)** (`npm i elizabot`) is
134
+ the direct ELIZA lineage in JS, the same territory tmct's chat surface sits
135
+ in. Its own README example:
136
+
137
+ ```js
138
+ var eliza = new ElizaBot();
139
+ var initial = eliza.getInitial();
140
+ var reply = eliza.transform(inputstring);
141
+ ```
142
+
143
+ It is deterministic and needs no model, same as tmct. But it has no graph and
144
+ no persistent memory: a fact from one line never carries into the next, and
145
+ its "reasoning" is pattern substitution, not a stored, queryable fact.
146
+
147
+ Put together: broad NLP without a graph (compromise), a graph without a
148
+ natural-language front end (N3.js), or a conversational front end without a
149
+ graph (elizabot). Combining ontology-seeded graph memory, English teaching,
150
+ and rule-based inference into one no-model pipeline is what looks distinctive
151
+ about tmct as of this writing.
152
+
24
153
  ## How it interprets you
25
154
 
26
155
  Every message runs through **multiple concurrent interpretation strategies**:
@@ -170,7 +299,7 @@ on the chat's hot path.
170
299
  entailment (`tmct syllogise`) is mechanical OWL rule materialization applied
171
300
  offline, rule-by-rule and retractable, not an LLM. There is **no LLM anywhere
172
301
  in the product**. (An LLM-as-judge exists only in the offline eval harness
173
- that tunes tmct, see `SKILL_TUNING_CYCLE.md`, never in the product path.)
302
+ that tunes tmct, see `SKILL_BENCHMARK_CHAT.md`, never in the product path.)
174
303
  - **It never guesses silently.** When it cannot resolve your question it says
175
304
  so and nudges you toward a query it *can* answer.
176
305
 
@@ -199,7 +328,8 @@ or a bare user gets a working install in one command.
199
328
  ### Try it on an example graph
200
329
 
201
330
  tmct *consumes* a code graph at `<repo>/.tmct/graph.json`; it does not build
202
- one. Two ready-made example graphs ship in `examples/` so you can see it answer
331
+ one. Two ready-made example graphs live in `examples/` in this repo (not in the
332
+ published npm package — clone the repo to use them) so you can see it answer
203
333
  real questions with no setup:
204
334
 
205
335
  ```bash
@@ -237,9 +367,13 @@ full tours.
237
367
  import { runChat, ask, resolveObject, fetchEntities } from "@polycode-projects/the-mechanical-code-talker";
238
368
  ```
239
369
 
240
- The `exports` map and the chat primitives (`ask`, `resolveObject`,
241
- `relationKind`, `impactClosure`, `dispatchTool`, `fetchEntities`) are the
242
- extension surface.
370
+ `runChat` is the full teach-and-ask surface (see "Teach it, then ask it to
371
+ reason" above it works over injectable streams, so a script can drive a
372
+ session the same way the tests do). `ask`/`resolveObject` are the lower-level,
373
+ read-only query primitives over an already-loaded graph, for a caller that
374
+ wants to query without a chat session. The `exports` map and the chat
375
+ primitives (`ask`, `resolveObject`, `relationKind`, `impactClosure`,
376
+ `dispatchTool`, `fetchEntities`) are the extension surface.
243
377
 
244
378
  ## The repository interface
245
379
 
package/ROADMAP.md CHANGED
@@ -382,6 +382,31 @@ resolver/guardrail/planner (96% plan completion, 0% hallucination, closed-world
382
382
  levers; the `../bedrock-meter` $0 rung; the playtest; Stage-2/Stage-5 research notes. Full detail:
383
383
  `CHATBENCH_0.8.0.md`, `AGENTBENCH_0.8.0.md`.
384
384
 
385
+ **Doc restructuring — `PLAN_AGENTS.md` (2026-07-10)**: `PLAN_TMCT_ECOSYSTEM_INTEGRATION.md` was
386
+ rewritten and renamed to `PLAN_AGENTS.md`, absorbing six sibling docs (`PLAN_AGI_ARCHITECTURE.md`,
387
+ `PLAN_CAPABILITY_ROUTER.md`, `PLAN_TAUGHT_RELATIONS.md`, `PLAN_OSS_ACE_PARSER.md`,
388
+ `PLAN_ontology-hierarchies.md`, `PLAN_ADVANCED_GRAMMAR.md` — all now in `archive/`) and sequencing
389
+ their durable content into Phase 0 (foundations) through Phase 4 (tmct as a pluggable LLM rung for
390
+ Claude Code/Bedrock/Copilot), plus a tiered research horizon (R1–R3). Two fresh comparative audits
391
+ of `../marginalia` and `../seonix` fed a new §2, "tmct uplift" — mechanisms those sibling repos
392
+ already have that tmct lacks or does more crudely (memory-tree versioning, actor-level trust, a
393
+ declarative SHACL-style ingest gate, real multi-language AST extraction, Chronograph-style temporal
394
+ diffing, and several Repository-Interface wrapper gaps that just need to be pointed at logic already
395
+ sitting in tmct's own `codegraph.mjs`). A new sibling doc, `PLAN_COMPLETIONS.md`, specs a second,
396
+ competing "tmct produces an artifact" capability alongside `PLAN_CODE.md`'s program synthesis:
397
+ mechanical, extractive text generation (broad search → group → infer between groups → summarize →
398
+ prune → grammar/voice pass), never LLM-style free generation. Separately, the benchmark/skill doc
399
+ landscape was unified: CHATBENCH stops splitting report+transcripts into two files going forward;
400
+ `SKILL_CHAT_PLAYTEST.md` and `SKILL_PLAYTEST_SPRINT.md` merged into `SKILL_BENCHMARK_PLAYTEST.md`
401
+ (with a new `PLAYTESTBENCH_<version>.md` report convention); `SKILL_TUNING_CYCLE.md` and
402
+ `SKILL_INFERENCE_TESTING.md` renamed to `SKILL_BENCHMARK_CHAT.md`/`SKILL_BENCHMARK_INFERENCE.md`;
403
+ a new `SKILL_BENCHMARK_AGENT.md` formalizes the previously-ad-hoc AGENTBENCH cycle;
404
+ `SKILL_STRATEGY_ADVISOR.md`/`SKILL_PLAIN_PROSE.md` renamed to `SKILL_AGENT_STRATEGY_ADVISOR.md`/
405
+ `SKILL_AGENT_PLAIN_PROSE.md`. A new shared reference doc, `docs/references/research-horizon.md`,
406
+ consolidates three near-duplicate "research frontier" essays (the frame problem, word-sense
407
+ disambiguation/ontology scale, Winograd-hard coreference) that had independently grown across the
408
+ now-archived docs.
409
+
385
410
  ## The umbrella product definition (item 1)
386
411
 
387
412
  **A tolerant, ELIZA/PARRY-style chat, obsessed with software.** A best-efforts
@@ -1094,6 +1119,16 @@ Features we have deliberately shaped seams for but will not build until the phas
1094
1119
  earned them. **Not everything below is deferred for the same reason** — the design horizon,
1095
1120
  stated explicitly (2026-07-08 research pass):
1096
1121
 
1122
+ **`PLAN_AGENTS.md` (2026-07-10) is the governing plan for tmct's next major arc** — mounting tmct
1123
+ hard into `../marginalia` and `../seonix` as their shared NL↔graph engine and tool-loop/completions
1124
+ API, plus a pluggable LLM rung for Claude Code, Amazon Bedrock, and GitHub Copilot. It sequences
1125
+ Phase 0 (foundations — an extension-pack/corpus-lexicon seam, RI wrapper fixes, several other
1126
+ small known-how items) through Phase 4 (the LLM-rung protocol shims), a "tmct uplift" section
1127
+ grounded in fresh comparative audits of both sibling repos, and a tiered research horizon (R1–R3).
1128
+ It supersedes the six phase/track pointers below that reference now-archived docs — treat this
1129
+ paragraph as the up-to-date entry point, and the items below as historical record of how those six
1130
+ docs' scope was reached before consolidation.
1131
+
1097
1132
  ### Future direction: a genuine planning/agentic loop (flagged 2026-07-09, research pass done, not implemented)
1098
1133
 
1099
1134
  The operator's own framing, explicitly out of scope for the routing-level `GOAL_BY_COMMAND`/
package/bin/tmct.mjs CHANGED
@@ -49,6 +49,11 @@ Usage:
49
49
  offline, $0; init is tier-1-only unless asked
50
50
  [--detect] suggest a tier-2 corpus from the repo's manifests
51
51
  (pyproject.toml → python, pom.xml → java); never seeds unasked
52
+ [--with-persona <name>] write an explicit [extensions]/[bias] preset into tmct.toml
53
+ ("code" — today's implicit default, made explicit)
54
+ tmct extend --validate <dir> validate a third-party extension pack's declared
55
+ resources (corpus/lexicon/templates) before activating
56
+ it in any repo's tmct.toml; exits non-zero on failure
52
57
  tmct syllogise [--repo <abs>] speculative inference (offline maintenance job): forward-
53
58
  [--depth <n>] [--budget <n>] chain the memory's rdfs:subClassOf closure, materialising
54
59
  bounded, low-trust, retractable entailed facts (never on the chat path)
@@ -316,41 +321,96 @@ async function main() {
316
321
 
317
322
  if (mode === "init") {
318
323
  // `tmct init` — the Repository-Interface onboarding surface: scaffold .tmct/,
319
- // write tmct.toml, seed the tier-1 corpus (offline, opt-out via TMCT_NO_SEED),
320
- // and record provenance. Idempotent; --force rewrites config + re-records.
324
+ // write tmct.toml, seed the corpus (offline, opt-out via TMCT_NO_SEED), and
325
+ // record provenance. Idempotent; --force rewrites config + re-records.
321
326
  //
322
- // TIERING POLICY: init is OFFLINE, $0 and TIER-1-ONLY by default. A tier-2
323
- // domain/language corpus (corpus/tier2/: aws, python, java) is added ONLY when
327
+ // TIERING POLICY: init is OFFLINE, $0 and TIER-1-ONLY by default (seon +
328
+ // conceptnet src/extensions.mjs's BUILTIN_EXTENSIONS). A tier-2 domain/
329
+ // language corpus (corpus/tier2/: aws, python, java) is added ONLY when
324
330
  // explicitly asked via `--corpus <id>`. The `--detect` auto-detect is a
325
331
  // documented STUB: it inspects the repo's manifests (pyproject.toml → python,
326
332
  // pom.xml → java) and SUGGESTS the matching corpus, but never seeds it unasked.
327
333
  const rest = process.argv.slice(3);
328
- const { initRepo } = await import("../src/init.mjs");
329
- const res = await initRepo(process.cwd(), { force: rest.includes("--force") });
330
- process.stdout.write(res.message + "\n");
334
+ const { initRepo, defaultConfig, renderTomlConfig, CONFIG_FILE, PERSONA_PRESETS } = await import("../src/init.mjs");
335
+ const { loadTomlConfig } = await import("../src/toml-config.mjs");
331
336
 
332
337
  const ci = rest.indexOf("--corpus");
333
338
  const corpusId = ci !== -1 ? rest[ci + 1] : undefined;
339
+ let manifest = null;
340
+ let manifestEntry = null;
334
341
  if (corpusId) {
335
- // Seed a tier-2 corpus by id from corpus/tier2/ (same slice shape as tier-1;
336
- // provenance-tagged corpus:tier2:<id>). Idempotent (content-hashed fact ids).
342
+ // Validate the id against the tier-2 manifest (same source of truth the
343
+ // old ad hoc path used) BEFORE touching anything on disk.
337
344
  const { readFile } = await import("node:fs/promises");
338
- const { join, dirname } = await import("node:path");
339
- const { seedMemory, TIER2_MANIFEST_FILE } = await import("../src/corpus/conceptnet.mjs");
340
- let manifest;
345
+ const { TIER2_MANIFEST_FILE } = await import("../src/corpus/conceptnet.mjs");
341
346
  try { manifest = JSON.parse(await readFile(TIER2_MANIFEST_FILE, "utf8")); }
342
347
  catch (e) { process.stderr.write(`tmct init: cannot read the tier-2 manifest — ${e?.message || e}\n`); process.exit(1); }
343
- const entry = (manifest.corpuses || []).find((c) => c.id === corpusId);
344
- if (!entry) {
348
+ manifestEntry = (manifest.corpuses || []).find((c) => c.id === corpusId);
349
+ if (!manifestEntry) {
345
350
  const ids = (manifest.corpuses || []).map((c) => c.id).join(", ");
346
351
  process.stderr.write(`tmct init: unknown --corpus "${corpusId}". Available tier-2 corpuses: ${ids}.\n`);
347
352
  process.exit(2);
348
353
  }
349
- const slicePath = join(dirname(TIER2_MANIFEST_FILE), entry.file);
350
- const seeded = await seedMemory(process.cwd(), { slicePath, provenancePrefix: `corpus:tier2:${entry.id}` });
354
+ }
355
+
356
+ // `--with-persona <name>` (Part 7): resolve + validate BEFORE touching
357
+ // disk, mirroring `--corpus`'s own unknown-id error handling — a bad
358
+ // persona name never scaffolds anything.
359
+ const pi = rest.indexOf("--with-persona");
360
+ const personaName = pi !== -1 ? rest[pi + 1] : undefined;
361
+ let personaPreset = null;
362
+ if (personaName) {
363
+ if (!Object.prototype.hasOwnProperty.call(PERSONA_PRESETS, personaName)) {
364
+ const names = Object.keys(PERSONA_PRESETS).join(", ");
365
+ process.stderr.write(`tmct init: unknown --with-persona "${personaName}". Available personas: ${names}.\n`);
366
+ process.exit(2);
367
+ }
368
+ personaPreset = PERSONA_PRESETS[personaName];
369
+ }
370
+
371
+ const res = await initRepo(process.cwd(), { force: rest.includes("--force"), persona: personaPreset });
372
+ process.stdout.write(res.message + "\n");
373
+
374
+ if (manifestEntry) {
375
+ // `--corpus <id>` now means "activate extensions.tier2-<id> and PERSIST
376
+ // that into tmct.toml" — so a second `tmct init` (or the next chat
377
+ // bootstrap) remembers the choice, unlike the old ad hoc path, which had
378
+ // to be repeated every time. This changes the tier-2 provenance tag from
379
+ // the old colon-separated "corpus:tier2:<id>" to the hyphenated
380
+ // "corpus:tier2-<id>" (matching the TOML-legal extension name) — a
381
+ // deliberate, low-risk rename; nothing in chat.mjs's runtime logic keys
382
+ // on the old colon-separated string (verified via grep).
383
+ const extName = `tier2-${manifestEntry.id}`;
384
+ const raw = await loadTomlConfig(process.cwd()); // just-written by initRepo above
385
+ const cfg = { ...defaultConfig() };
386
+ if (raw?.graph_file !== undefined) cfg.graphFile = String(raw.graph_file);
387
+ if (raw?.corpus?.tier !== undefined) cfg.corpus = { tier: raw.corpus.tier };
388
+ if (raw?.seed) {
389
+ cfg.seed = { ...cfg.seed };
390
+ if (raw.seed.enabled !== undefined) cfg.seed.enabled = Boolean(raw.seed.enabled);
391
+ if (raw.seed.limit !== undefined) cfg.seed.limit = Number(raw.seed.limit);
392
+ }
393
+ cfg.extensions = { ...(raw?.extensions || {}), [extName]: { ...(raw?.extensions?.[extName] || {}), active: true } };
394
+ if (raw?.bias !== undefined) cfg.bias = raw.bias;
395
+ const { writeFile } = await import("node:fs/promises");
396
+ const { join } = await import("node:path");
397
+ await writeFile(join(process.cwd(), CONFIG_FILE), renderTomlConfig(cfg));
398
+
399
+ // Seed it now too — through the SAME unified corpus loader every other
400
+ // bundle goes through (src/extensions.mjs), not a bespoke seedMemory call.
401
+ const { resolveExtensions, seedActiveCorpusEntries } = await import("../src/extensions.mjs");
402
+ const { entries } = await resolveExtensions(process.cwd());
403
+ const entry = entries.get(extName);
404
+ const { perBundle } = await seedActiveCorpusEntries(process.cwd(), new Map([[extName, entry]]));
405
+ const seeded = perBundle[extName];
406
+ if (seeded.error) {
407
+ process.stderr.write(`tmct init: could not seed tier-2 corpus "${manifestEntry.id}" — ${seeded.error}\n`);
408
+ process.exit(1);
409
+ }
351
410
  process.stdout.write(
352
- `seeded tier-2 corpus "${entry.id}" (${entry.kind}) — ${seeded.appended} fact(s) added`
353
- + `${seeded.skipped ? `, ${seeded.skipped} already present` : ""}. Source: corpus/tier2/${entry.file} (${entry.license}).\n`,
411
+ `seeded tier-2 corpus "${manifestEntry.id}" (${manifestEntry.kind}) — ${seeded.appended} fact(s) added`
412
+ + `${seeded.skipped ? `, ${seeded.skipped} already present` : ""}. Source: corpus/tier2/${manifestEntry.file} (${manifestEntry.license}). `
413
+ + `Activated in tmct.toml — future \`tmct init\`/chat sessions seed it automatically.\n`,
354
414
  );
355
415
  return;
356
416
  }
@@ -377,6 +437,56 @@ async function main() {
377
437
  return;
378
438
  }
379
439
 
440
+ if (mode === "extend") {
441
+ // `tmct extend --validate <dir>` — validate a THIRD-PARTY extension pack
442
+ // (the shape a package like seonix/marginalia ships) BEFORE it's activated
443
+ // in any repo's tmct.toml. Reuses existing throw-loudly primitives
444
+ // (loadSlice/loadMap/toFacts, loadLexicon, loadTemplates) via
445
+ // src/extensions.mjs's validateExtensionPack — never invents new
446
+ // shape-checking logic. `<dir>` must carry its own tmct.toml declaring one
447
+ // or more `[extensions.<name>]` host entries (the SAME [extensions] table
448
+ // shape a repo's own tmct.toml uses) naming the resource(s) to validate;
449
+ // the shipped builtins (seon/conceptnet/tier2-*) are never re-validated
450
+ // here — this command is about a PACK's OWN declared resources.
451
+ const rest = process.argv.slice(3);
452
+ const vi = rest.indexOf("--validate");
453
+ const dirArg = vi !== -1 ? rest[vi + 1] : undefined;
454
+ if (!dirArg) {
455
+ process.stderr.write("tmct extend: --validate <dir> requires a directory\n");
456
+ process.exit(2);
457
+ }
458
+ const { resolve: resolvePath } = await import("node:path");
459
+ const target = resolvePath(process.cwd(), dirArg);
460
+ const { resolveExtensions, BUILTIN_EXTENSIONS, validateExtensionPack } = await import("../src/extensions.mjs");
461
+ let entries;
462
+ try {
463
+ ({ entries } = await resolveExtensions(target));
464
+ } catch (e) {
465
+ process.stderr.write(`tmct extend --validate: ${e?.message || e}\n`);
466
+ process.exit(1);
467
+ }
468
+ const hostEntries = [...entries].filter(([name]) => !(name in BUILTIN_EXTENSIONS));
469
+ if (!hostEntries.length) {
470
+ process.stderr.write(`tmct extend --validate: no host-declared [extensions.*] entries found in ${target}/tmct.toml\n`);
471
+ process.exit(1);
472
+ }
473
+ let allOk = true;
474
+ for (const [name, entry] of hostEntries) {
475
+ process.stdout.write(`${name} (${entry.kind}):\n`);
476
+ const { ok, results } = await validateExtensionPack(target, entry);
477
+ if (!ok) allOk = false;
478
+ for (const r of results) {
479
+ const status = r.ok ? "PASS" : "FAIL";
480
+ const detail = r.ok
481
+ ? (r.counts ? ` (${Object.entries(r.counts).map(([k, v]) => `${k}=${v}`).join(", ")})` : "")
482
+ : ` — ${r.error}`;
483
+ process.stdout.write(` [${status}] ${r.kind}: ${r.path}${detail}\n`);
484
+ }
485
+ }
486
+ process.stdout.write(allOk ? "tmct extend --validate: all resources passed.\n" : "tmct extend --validate: one or more resources FAILED.\n");
487
+ process.exit(allOk ? 0 : 1);
488
+ }
489
+
380
490
  if (mode === "syllogise") {
381
491
  // `tmct syllogise` — the explicit speculative-inference batch (never on the chat
382
492
  // hot path): forward-chain the memory's rdfs:subClassOf closure into bounded,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "1.3.1",
3
+ "version": "1.4.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.",