@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 +142 -8
- package/ROADMAP.md +35 -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**:
|
|
@@ -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 `
|
|
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
|
|
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
|
@@ -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
|
|
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.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.",
|