@polycode-projects/the-mechanical-code-talker 1.3.2 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -7
- package/ROADMAP.md +22 -0
- package/bin/tmct.mjs +128 -18
- package/package.json +1 -1
- package/src/chat.mjs +119 -72
- package/src/codegraph.mjs +73 -4
- package/src/config.mjs +7 -2
- package/src/conformance.mjs +59 -15
- package/src/corpus/templates.mjs +38 -0
- package/src/extensions.mjs +348 -0
- package/src/init.mjs +92 -7
- package/src/memory/bias.mjs +77 -0
- package/src/memory/blocks.mjs +57 -18
- package/src/memory/core.mjs +237 -20
- package/src/memory/fold.mjs +0 -0
- package/src/memory/trust.mjs +94 -6
- package/src/providers/bootstrap.mjs +5 -3
- package/src/providers/fixture.mjs +7 -3
- package/src/providers/graph-service.mjs +205 -28
- package/src/repository-interface.mjs +21 -7
- package/src/server.mjs +39 -29
- package/src/source-slice.mjs +68 -0
- package/src/telemetry.mjs +5 -2
- package/src/toml-config.mjs +17 -0
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.
|
|
8
|
-
|
|
9
|
-
|
|
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**:
|
|
@@ -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
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
@@ -407,6 +407,28 @@ consolidates three near-duplicate "research frontier" essays (the frame problem,
|
|
|
407
407
|
disambiguation/ontology scale, Winograd-hard coreference) that had independently grown across the
|
|
408
408
|
now-archived docs.
|
|
409
409
|
|
|
410
|
+
**v1.4.0 — the first PLAN_AGENTS.md uplift batch (2026-07-11)**: built as four parallel,
|
|
411
|
+
worktree-isolated background tracks, merged sequentially, watched throughout by a background
|
|
412
|
+
strategy-advisor agent (full tick-by-tick record in `STRATEGY_ADVISOR.log`). 1543/1543 tests green
|
|
413
|
+
at the final merge. Shipped: the Repository Interface wrapper fixes from the seonix audit (ranked
|
|
414
|
+
`search()`, a real graph-only `context()` — `INTERFACE_VERSION` 1.0.0→1.1.0 — depth-capped
|
|
415
|
+
`impact()`, source-backed `snippet()`, `edges()`/`search()` pagination, telemetry wiring), a
|
|
416
|
+
path-traversal security fix found and closed along the way (`src/source-slice.mjs`), hub-dampened
|
|
417
|
+
memory-fact ranking (on by default — the build found the original "modest degree, modest penalty"
|
|
418
|
+
assumption was mathematically wrong and proved the real bound instead), memory-tree versioning
|
|
419
|
+
(`snapshotMemory()`, manual trigger only), full session-scoped actor-level trust (shipped
|
|
420
|
+
unconditionally, no config flag — operator decision, single consumer), the extension-pack seam
|
|
421
|
+
(`src/extensions.mjs`, `[extensions]`/`[bias]` in `tmct.toml`, `tmct extend --validate`, and a
|
|
422
|
+
deliberate bug fix — `tmct init` now seeds SEON as well as ConceptNet), bias-weighted fact ranking
|
|
423
|
+
(`src/memory/bias.mjs`, verified by control-flow tracing to never drop a fact, only reorder it), and
|
|
424
|
+
`tmct init --with-persona <name>`. One real merge conflict (Track A's `tel` param and Track D's
|
|
425
|
+
`biasByBundle` param both threading through the same `chat.mjs` function signatures — resolved by
|
|
426
|
+
keeping both). One bug the strategy advisor caught that the original brief missed: the
|
|
427
|
+
path-traversal guard failed closed *incorrectly* under a relative `TMCT_GRAPH_FILE`, rejecting
|
|
428
|
+
legitimate reads, not just traversal attempts — fixed at the source and defensively in the guard.
|
|
429
|
+
One scope decision made mid-build: multi-language AST extraction stays in seonix permanently, not
|
|
430
|
+
tmct's job — full detail in `PLAN_AGENTS.md` §13.
|
|
431
|
+
|
|
410
432
|
## The umbrella product definition (item 1)
|
|
411
433
|
|
|
412
434
|
**A tolerant, ELIZA/PARRY-style chat, obsessed with software.** A best-efforts
|
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
|
|
320
|
-
//
|
|
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
|
|
323
|
-
//
|
|
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
|
|
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
|
-
//
|
|
336
|
-
//
|
|
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 {
|
|
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
|
-
|
|
344
|
-
if (!
|
|
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
|
-
|
|
350
|
-
|
|
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 "${
|
|
353
|
-
+ `${seeded.skipped ? `, ${seeded.skipped} already present` : ""}. Source: corpus/tier2/${
|
|
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
|
+
"version": "1.4.1",
|
|
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.",
|