@polycode-projects/the-mechanical-code-talker 0.5.0 → 0.7.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 +91 -27
- package/ROADMAP.md +45 -0
- package/bin/tmct.mjs +64 -2
- package/corpus/README.md +77 -5
- package/corpus/conceptnet/README.md +92 -33
- package/corpus/conceptnet/filter-dump.mjs +59 -2
- package/corpus/conceptnet/quality-filter.mjs +95 -0
- package/corpus/conceptnet/slice.jsonl +31067 -378
- package/corpus/seon/LICENSE-NOTICE +37 -0
- package/corpus/seon/README.md +121 -0
- package/corpus/seon/concepts.jsonl +238 -0
- package/corpus/seon/definitions.jsonl +288 -0
- package/corpus/seon/relations.jsonl +8 -0
- package/corpus/tier2/aws.jsonl +39 -0
- package/corpus/tier2/generate.mjs +253 -0
- package/corpus/tier2/java.jsonl +31 -0
- package/corpus/tier2/manifest.json +48 -0
- package/corpus/tier2/python.jsonl +30 -0
- package/data/templates/responses.jsonl +3 -0
- package/package.json +13 -2
- package/src/ask-vocab.mjs +19 -1
- package/src/ask.mjs +13 -13
- package/src/chat.mjs +759 -66
- package/src/codegraph.mjs +28 -5
- package/src/concept.mjs +393 -0
- package/src/conformance.mjs +166 -0
- package/src/corpus/conceptnet.mjs +42 -19
- package/src/init.mjs +12 -9
- package/src/interpret/normalize.mjs +42 -0
- package/src/interpret/pipeline.mjs +2 -2
- package/src/memory/core.mjs +84 -0
- package/src/memory/inspect.mjs +25 -0
- package/src/server.mjs +88 -7
- package/src/tui/app.mjs +26 -2
package/README.md
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
`@polycode-projects/the-mechanical-code-talker`
|
|
4
4
|
|
|
5
|
-
A pure-JS, **no-LLM**, offline, **$0** chatbot in the ELIZA/PARRY lineage
|
|
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
|
-
|
|
10
|
-
honest miss.
|
|
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.
|
|
11
10
|
|
|
12
11
|
```
|
|
13
12
|
$ tmct
|
|
@@ -50,6 +49,31 @@ under a guard that proves the protected spans came through byte-for-byte. Today
|
|
|
50
49
|
that pass fixes the a/an article defect; broader voice and agreement rules are
|
|
51
50
|
implemented but parked until they earn their place on the benchmark.
|
|
52
51
|
|
|
52
|
+
## How it guides you
|
|
53
|
+
|
|
54
|
+
When you touch a **concept** without asking a precise question — "what is a
|
|
55
|
+
class", "what about imports", "what calls are there" — tmct answers in three
|
|
56
|
+
bands instead of dead-ending:
|
|
57
|
+
|
|
58
|
+
1. the **definition** (a plain-English one-liner: *"A class is a template that
|
|
59
|
+
defines the structure and behaviour of objects."* / *"To import is to bring
|
|
60
|
+
another module's definitions into the current one."*);
|
|
61
|
+
2. **real instances from your graph** — *"In this codebase, for example: Record,
|
|
62
|
+
Task and User (10 classes)"*, or actual edges *"src/core/store.mjs imports
|
|
63
|
+
src/core/model.mjs (18 import edges)"*;
|
|
64
|
+
3. **guided follow-ups** — two or three concrete next questions, each one
|
|
65
|
+
*pre-checked against your graph* so every suggestion is guaranteed to resolve:
|
|
66
|
+
*"Want to go deeper? Try: which classes inherit from Record / what does Task
|
|
67
|
+
contain / where is User defined"*.
|
|
68
|
+
|
|
69
|
+
It fires for both **noun** concepts (class, module, function, method) and
|
|
70
|
+
**relation** concepts (imports, calls, contains, inherits, tests), and only when
|
|
71
|
+
tmct genuinely knows the concept *and* has instances of it — otherwise the honest
|
|
72
|
+
miss stands. The effect is a conversation that drills down from a vague opener to
|
|
73
|
+
a useful answer without ever hitting a wall. Natural phrasings are routed to the
|
|
74
|
+
capability you meant: *"what functions are in Task"* → its members, *"what
|
|
75
|
+
defined saveStore"* → where it's defined.
|
|
76
|
+
|
|
53
77
|
## How it remembers
|
|
54
78
|
|
|
55
79
|
tmct's memory has two layers, both fed by every parsed request and response and
|
|
@@ -61,9 +85,12 @@ by cleaned session logs:
|
|
|
61
85
|
relevance rather than loaded wholesale.
|
|
62
86
|
|
|
63
87
|
With no graph at all, tmct starts empty and remembers what you tell it — the
|
|
64
|
-
`.tmct/` graph is created from the conversation.
|
|
65
|
-
vocabulary
|
|
66
|
-
|
|
88
|
+
`.tmct/` graph is created from the conversation. On a first run it seeds the
|
|
89
|
+
committed vocabulary so it knows what it's talking about from turn one: a curated
|
|
90
|
+
**SEON** software ontology plus the whole filtered **ConceptNet slice**
|
|
91
|
+
(CC-BY-SA 4.0) — every term carries an English definition, so "what is a cache?"
|
|
92
|
+
answers offline, from disk, on turn one. `--ephemeral` (used by the shipped
|
|
93
|
+
`npm run example:*` demos) reads a graph but writes nothing back.
|
|
67
94
|
|
|
68
95
|
### Provenance and trust
|
|
69
96
|
|
|
@@ -125,6 +152,41 @@ or a bare user gets a working install in one command.
|
|
|
125
152
|
> Install-size note: tmct depends on wink-nlp's deterministic English language
|
|
126
153
|
> model (~3.8 MB installed). That model is a lookup table, not an LLM.
|
|
127
154
|
|
|
155
|
+
### Try it on an example graph
|
|
156
|
+
|
|
157
|
+
tmct *consumes* a code graph at `<repo>/.tmct/graph.json` — it does not build
|
|
158
|
+
one. Two ready-made example graphs ship in `examples/` so you can see it answer
|
|
159
|
+
real questions with no setup:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm run example:mini # "Questboard" — a small task-tracker web app (12 modules)
|
|
163
|
+
npm run example:polyglot # one shared OWL vocabulary across Java / Python / C#
|
|
164
|
+
npm run chat:repo -- ./any/path # chat over any repo that has a .tmct/graph.json
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Questions the **mini-webapp** graph answers:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
what classes are there
|
|
171
|
+
describe Task
|
|
172
|
+
how many modules
|
|
173
|
+
which modules import src/core/model.mjs
|
|
174
|
+
what tests cover src/handlers/tasks.mjs
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The **polyglot** graph shows the language-neutral idea — Java, Python and C#
|
|
178
|
+
entities all typed to the same `seon:Class` / `seon:Method` / `seon:Module`
|
|
179
|
+
concepts, so one query reasons across every language at once:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
how many classes # 9 — Java + Python + C# counted as one concept
|
|
183
|
+
what classes are there # Order (Java), Inventory (Python), PaymentService (C#), …
|
|
184
|
+
which modules define PaymentService
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
See `examples/mini-webapp/README.md` and `examples/polyglot/README.md` for the
|
|
188
|
+
full tours.
|
|
189
|
+
|
|
128
190
|
### As a library
|
|
129
191
|
|
|
130
192
|
```js
|
|
@@ -138,29 +200,31 @@ extension surface.
|
|
|
138
200
|
## The repository interface
|
|
139
201
|
|
|
140
202
|
tmct is not an indexer, so it consumes a graph through a typed contract any
|
|
141
|
-
producer can implement. That contract is
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
exactly as the no-LLM ethos requires.
|
|
203
|
+
producer can implement. That contract is first-class: a **versioned (1.0.0),
|
|
204
|
+
OWL-grounded, machine-readable service definition** (`docs/repository-interface.md`
|
|
205
|
+
plus a JSON schema) of every service, its arguments, result types, and error
|
|
206
|
+
contract. A **miss is a value, not a throw**: the interface models "no answer"
|
|
207
|
+
explicitly. tmct ships **reference providers** (a fixture graph and the
|
|
208
|
+
empty/bootstrap graph) that implement every service, and a **runnable conformance
|
|
209
|
+
suite**. tmct's own providers pass it in `npm test`. Any external graph producer
|
|
210
|
+
(seonix first) runs the same suite against its native implementation to claim
|
|
211
|
+
conformance. Conformance is the suite, not prose. This inverts the original
|
|
212
|
+
relationship: tmct was lifted out of seonix, and seonix now reorients as a *user*
|
|
213
|
+
that imports the tmct library and exposes its graph to tmct as a service. The LLM
|
|
214
|
+
agent stays outside tmct, as the no-LLM ethos requires.
|
|
154
215
|
|
|
155
216
|
## Security and supply chain
|
|
156
217
|
|
|
157
218
|
tmct is $0 to run and meant to be trusted offline, so the supply chain is
|
|
158
|
-
hardened
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
219
|
+
hardened:
|
|
220
|
+
|
|
221
|
+
- CI runs **SAST and secret detection**.
|
|
222
|
+
- A nightly **`npm audit` + OSV-Scanner** job watches dependencies.
|
|
223
|
+
- Releases are published with **npm provenance** (`--provenance`).
|
|
224
|
+
- A coordinated-disclosure `SECURITY.md` policy covers reports.
|
|
225
|
+
|
|
226
|
+
The content-address hash is single-sourced in `src/hash.mjs`, so the
|
|
227
|
+
cross-version-stable fact-id contract has exactly one definition.
|
|
164
228
|
|
|
165
229
|
## Provenance
|
|
166
230
|
|
package/ROADMAP.md
CHANGED
|
@@ -550,6 +550,51 @@ Repository Interface library-surface work (Phase 8) settling the extraction boun
|
|
|
550
550
|
`PLAN_OSS_ACE_PARSER.md`. Sibling publish-candidates (the bounded-Damerau fuzzy matcher, the
|
|
551
551
|
PageRank+IDF block ranker) may follow the same path if there is demand.
|
|
552
552
|
|
|
553
|
+
## Phase 10 — Conversational competence & onboarding (0.6.0 → 0.7.0)
|
|
554
|
+
|
|
555
|
+
*(Operator-directed, from live new-user testing.)* Once a graph is loaded the engine is strong;
|
|
556
|
+
the weak surfaces were the FIRST RUN and the VAGUE question. This phase makes the miss graceful,
|
|
557
|
+
the empty state honest, and the vague touch a guided answer — realising item 1's "tolerant, guides
|
|
558
|
+
you toward precision" promise on the conversational surface.
|
|
559
|
+
|
|
560
|
+
- **Onboarding UX (shipped 0.6.0):** the grammar wall moved behind `/help` (a short, tailored miss
|
|
561
|
+
instead); intent lanes for memory/teach ("remember that X"), meta/self ("what is this codebase",
|
|
562
|
+
"what do you know"), routed only when a graph query would miss; empty/degenerate-graph
|
|
563
|
+
orientation that distinguishes CODE STRUCTURE (needs a `.tmct/graph.json` via a producer or
|
|
564
|
+
`--repo`; tmct reads graphs, it does not index code) from VOCABULARY (`tmct init`/bootstrap seeds
|
|
565
|
+
concepts); `TMCT_GRAPH_FILE` honoured by chat; slash-optional commands (`stats`≡`/stats`);
|
|
566
|
+
`/memory` explore hooks; up/down-arrow prompt history in the TUI.
|
|
567
|
+
- **Knowledge (shipped 0.6.0 → 0.7.0):** the curated `corpus/seon` ontology — a software-sense
|
|
568
|
+
definition for EVERY lexicon term, language-neutral (Java/C#/Python `class` → one concept); the
|
|
569
|
+
ConceptNet slice quality-filtered (word-sense noise cut) and regrown to ~40k facts; tier-2
|
|
570
|
+
specialised corpuses (aws/python/java) with `tmct init --corpus`; batched `appendFacts` (one
|
|
571
|
+
write, 419s→2.5s) enabling **seed-all** so a fresh repo knows the whole curated vocabulary.
|
|
572
|
+
- **The concept force (shipped 0.7.0):** a vague touch on a concept X, where tmct knows X and has instances,
|
|
573
|
+
answers in three bands — **the definition** (from `corpus/seon`), **the examples** (real code-graph
|
|
574
|
+
+ memory instances of X), and **a soft guided follow-up** ("Want to go deeper?" + 2–3 questions
|
|
575
|
+
built from the real instances × the query shapes valid for that kind, each pre-validated to
|
|
576
|
+
resolve). Applies to NOUN concepts (`what is a class` → define + Base/Widget/Button + drill-ins)
|
|
577
|
+
AND **RELATION concepts** (`what about imports` / `what calls are there` → the verb definition +
|
|
578
|
+
example edges + guided queries), fixing the vague-query dead-ends. Fact rendering is
|
|
579
|
+
de-anthropomorphised (no first-person "i learned:" over-claim — corpus facts read as data +
|
|
580
|
+
provenance; `you told me` stays for operator-asserted facts); listings cap at 32 with a "say
|
|
581
|
+
'more'" pagination that holds the remainder in session state.
|
|
582
|
+
- **Dead-end routing + read-only demos (shipped 0.7.0):** natural drill-down phrasings are routed
|
|
583
|
+
onto the canonical shapes they mean — `what functions are in X` → members-of-class, `what defined
|
|
584
|
+
X` → where-is-X-defined, a no-context `what about X` → the concept/relation force (the discourse
|
|
585
|
+
continuation still wins when there IS a prior answer). `tmct chat --ephemeral` (and the
|
|
586
|
+
`npm run example:*` demos) reads a graph but writes nothing back, so a checked-in example is never
|
|
587
|
+
dirtied by a demo run.
|
|
588
|
+
- **The dialogue-flow loop (`SKILL_CHAT_PLAYTEST.md`, 0.7.0):** a fast, qualitative tuning loop that
|
|
589
|
+
complements the LLM-judge benchmark — Claude plays a curious user, hunts *dead-ends* (walls,
|
|
590
|
+
"unknown qualifier", phrasing-misses, invited follow-ups the engine can't take), fixes them by
|
|
591
|
+
ROUTING to existing capabilities, replays the same conversations until they flow, freezes them as
|
|
592
|
+
regression transcripts, then ratchets the complexity tier. The drill-down transcript above is its
|
|
593
|
+
first frozen fixture (`test/chatflow-drilldown.test.mjs`).
|
|
594
|
+
- **Measured** by the version-matched benchmark (`CHATBENCH_<version>` per `SKILL_TUNING_CYCLE.md`),
|
|
595
|
+
with new graded cells for the miss / empty-graph / concept-touch surfaces so these become
|
|
596
|
+
regression-protected levers, not one-off polish.
|
|
597
|
+
|
|
553
598
|
## Phase LATER — recognized, deferred, not now
|
|
554
599
|
|
|
555
600
|
Features we have deliberately shaped seams for but will not build until the phases above have
|
package/bin/tmct.mjs
CHANGED
|
@@ -34,12 +34,17 @@ software repository. No model calls; no codebase index of its own.
|
|
|
34
34
|
Usage:
|
|
35
35
|
tmct interactive chat (the headline surface)
|
|
36
36
|
tmct chat [--repo <abs>] chat over a specific repo's graph
|
|
37
|
+
[--ephemeral] read the graph but write nothing back (demo/read-only)
|
|
37
38
|
[--plain] force the plain readline shell (the default when
|
|
38
39
|
stdin/stdout is not a terminal)
|
|
39
40
|
tmct memory [--repo <abs>] what tmct remembers: facts, utterances, sessions,
|
|
40
41
|
[--verbose] folded blocks (the /memory chat command, from the shell)
|
|
41
42
|
tmct init [--force] initialize the current directory for tmct: .tmct/,
|
|
42
43
|
tmct.toml, tier-1 corpus seed, provenance record
|
|
44
|
+
[--corpus <id>] also seed a tier-2 corpus (aws|python|java) — opt-in,
|
|
45
|
+
offline, $0; init is tier-1-only unless asked
|
|
46
|
+
[--detect] suggest a tier-2 corpus from the repo's manifests
|
|
47
|
+
(pyproject.toml → python, pom.xml → java); never seeds unasked
|
|
43
48
|
tmct syllogise [--repo <abs>] speculative inference (offline maintenance job): forward-
|
|
44
49
|
[--depth <n>] [--budget <n>] chain the memory's rdfs:subClassOf closure, materialising
|
|
45
50
|
bounded, low-trust, retractable entailed facts (never on the chat path)
|
|
@@ -263,16 +268,21 @@ async function main() {
|
|
|
263
268
|
const rest = process.argv.slice(3);
|
|
264
269
|
const i = rest.indexOf("--repo");
|
|
265
270
|
const repoPath = i !== -1 ? rest[i + 1] : undefined;
|
|
271
|
+
// `--ephemeral` (used by the shipped `npm run example:*` scripts): read the
|
|
272
|
+
// target graph but write nothing back — no session folded into the committed
|
|
273
|
+
// code graph, no .tmct/memory dropped under it. A demo you can run repeatedly
|
|
274
|
+
// on a checked-in example without ever dirtying it.
|
|
275
|
+
const ephemeral = rest.includes("--ephemeral");
|
|
266
276
|
// The shell gate: a real terminal gets the full-screen Ink TUI; `--plain` or a
|
|
267
277
|
// non-TTY stream (pipes, scripts, the test suite) gets the readline shell. Both
|
|
268
278
|
// drive the same createSession sink — only the drawing differs.
|
|
269
279
|
const plain = rest.includes("--plain") || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
270
280
|
if (plain) {
|
|
271
281
|
const { runChat } = await import("../src/chat.mjs");
|
|
272
|
-
await runChat({ repoPath });
|
|
282
|
+
await runChat({ repoPath, ephemeral });
|
|
273
283
|
} else {
|
|
274
284
|
const { runTui } = await import("../src/tui/app.mjs");
|
|
275
|
-
await runTui({ repoPath });
|
|
285
|
+
await runTui({ repoPath, ephemeral });
|
|
276
286
|
}
|
|
277
287
|
return;
|
|
278
288
|
}
|
|
@@ -295,10 +305,62 @@ async function main() {
|
|
|
295
305
|
// `tmct init` — the Repository-Interface onboarding surface: scaffold .tmct/,
|
|
296
306
|
// write tmct.toml, seed the tier-1 corpus (offline, opt-out via TMCT_NO_SEED),
|
|
297
307
|
// and record provenance. Idempotent; --force rewrites config + re-records.
|
|
308
|
+
//
|
|
309
|
+
// TIERING POLICY: init is OFFLINE, $0 and TIER-1-ONLY by default. A tier-2
|
|
310
|
+
// domain/language corpus (corpus/tier2/: aws, python, java) is added ONLY when
|
|
311
|
+
// explicitly asked via `--corpus <id>`. The `--detect` auto-detect is a
|
|
312
|
+
// documented STUB: it inspects the repo's manifests (pyproject.toml → python,
|
|
313
|
+
// pom.xml → java) and SUGGESTS the matching corpus, but never seeds it unasked.
|
|
298
314
|
const rest = process.argv.slice(3);
|
|
299
315
|
const { initRepo } = await import("../src/init.mjs");
|
|
300
316
|
const res = await initRepo(process.cwd(), { force: rest.includes("--force") });
|
|
301
317
|
process.stdout.write(res.message + "\n");
|
|
318
|
+
|
|
319
|
+
const ci = rest.indexOf("--corpus");
|
|
320
|
+
const corpusId = ci !== -1 ? rest[ci + 1] : undefined;
|
|
321
|
+
if (corpusId) {
|
|
322
|
+
// Seed a tier-2 corpus by id from corpus/tier2/ (same slice shape as tier-1;
|
|
323
|
+
// provenance-tagged corpus:tier2:<id>). Idempotent (content-hashed fact ids).
|
|
324
|
+
const { readFile } = await import("node:fs/promises");
|
|
325
|
+
const { join, dirname } = await import("node:path");
|
|
326
|
+
const { seedMemory, TIER2_MANIFEST_FILE } = await import("../src/corpus/conceptnet.mjs");
|
|
327
|
+
let manifest;
|
|
328
|
+
try { manifest = JSON.parse(await readFile(TIER2_MANIFEST_FILE, "utf8")); }
|
|
329
|
+
catch (e) { process.stderr.write(`tmct init: cannot read the tier-2 manifest — ${e?.message || e}\n`); process.exit(1); }
|
|
330
|
+
const entry = (manifest.corpuses || []).find((c) => c.id === corpusId);
|
|
331
|
+
if (!entry) {
|
|
332
|
+
const ids = (manifest.corpuses || []).map((c) => c.id).join(", ");
|
|
333
|
+
process.stderr.write(`tmct init: unknown --corpus "${corpusId}". Available tier-2 corpuses: ${ids}.\n`);
|
|
334
|
+
process.exit(2);
|
|
335
|
+
}
|
|
336
|
+
const slicePath = join(dirname(TIER2_MANIFEST_FILE), entry.file);
|
|
337
|
+
const seeded = await seedMemory(process.cwd(), { slicePath, provenancePrefix: `corpus:tier2:${entry.id}` });
|
|
338
|
+
process.stdout.write(
|
|
339
|
+
`seeded tier-2 corpus "${entry.id}" (${entry.kind}) — ${seeded.appended} fact(s) added`
|
|
340
|
+
+ `${seeded.skipped ? `, ${seeded.skipped} already present` : ""}. Source: corpus/tier2/${entry.file} (${entry.license}).\n`,
|
|
341
|
+
);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (rest.includes("--detect")) {
|
|
346
|
+
// AUTO-DETECT STUB (documented, non-seeding): map a build manifest to the
|
|
347
|
+
// tier-2 corpus that fits, and tell the operator how to add it. Kept a stub on
|
|
348
|
+
// purpose — the $0/offline default never expands the corpus without an ask.
|
|
349
|
+
const { access } = await import("node:fs/promises");
|
|
350
|
+
const { join } = await import("node:path");
|
|
351
|
+
const has = (f) => access(join(process.cwd(), f)).then(() => true, () => false);
|
|
352
|
+
const DETECT = [["pyproject.toml", "python"], ["pom.xml", "java"]];
|
|
353
|
+
const found = [];
|
|
354
|
+
for (const [file, id] of DETECT) if (await has(file)) found.push([file, id]);
|
|
355
|
+
if (!found.length) {
|
|
356
|
+
process.stdout.write("no tier-2 corpus auto-detected (looked for pyproject.toml → python, pom.xml → java).\n");
|
|
357
|
+
} else {
|
|
358
|
+
for (const [file, id] of found) {
|
|
359
|
+
process.stdout.write(`detected ${file} — run \`tmct init --corpus ${id}\` to add the ${id} tier-2 corpus (offline, $0).\n`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
302
364
|
return;
|
|
303
365
|
}
|
|
304
366
|
|
package/corpus/README.md
CHANGED
|
@@ -5,14 +5,40 @@ The corpuses tmct ships so that an **empty** tmct still has a vocabulary
|
|
|
5
5
|
in `src/corpus/`. Related committed data lives in `data/` (response templates
|
|
6
6
|
+ the SE phrase book — items 4+7).
|
|
7
7
|
|
|
8
|
+
## The tiering policy (tier-1 / tier-2 / tier-3)
|
|
9
|
+
|
|
10
|
+
tmct's knowledge arrives in three tiers, distinguished by **when** it lands and
|
|
11
|
+
**whether it ships in the npm package**:
|
|
12
|
+
|
|
13
|
+
| Tier | What | Ships in the package? | Lands when | Provenance |
|
|
14
|
+
|---|---|---|---|---|
|
|
15
|
+
| **1 — base** | the general English/tech ConceptNet slice + the response templates + the SE phrasebook — the vocabulary every tmct has out of the box | **yes**, committed here | `tmct init` seeds `.tmct/` from committed data (offline, $0) | `corpus:conceptnet /r/…` |
|
|
16
|
+
| **2 — specialised** | LANGUAGE- or DOMAIN-specific fact sets (`aws`, `python`, `java`, …) so tmct can "expand into a concept for an applicable codebase" | **no** — selected per repo | `tmct init` fetches/generates the chosen tier-2 corpuses into `.tmct/` (Wave-2 wiring) | `corpus:tier2:<id> /r/…` |
|
|
17
|
+
| **3 — learned** | facts tmct writes from the actual conversation / the actual codebase it is pointed at | never committed | at runtime, into `.tmct/memory/` | `chat:…`, `codegraph:…` |
|
|
18
|
+
|
|
19
|
+
**Offline / $0 is the default at every tier.** Tier-1 is committed. Tier-2's
|
|
20
|
+
sample corpuses are *curated* (generated locally by `tier2/generate.mjs`, no
|
|
21
|
+
network); a tier-2 corpus too large to curate by hand may declare a `fetch`
|
|
22
|
+
source (a URL + a `sha256`), but the network is **opt-in only** — nothing
|
|
23
|
+
reaches out unless the operator asks for it. Tier-3 is whatever the user says.
|
|
24
|
+
|
|
25
|
+
**Checksums / integrity.** Every tier-2 corpus carries a `sha256` and a `bytes`
|
|
26
|
+
count in `tier2/manifest.json`; a `fetch`-sourced corpus is checksum-verified on
|
|
27
|
+
download (`generate.mjs` `fetchCorpus()`), so a corrupt or tampered fetch fails
|
|
28
|
+
loudly instead of seeding garbage.
|
|
29
|
+
|
|
8
30
|
## What's here
|
|
9
31
|
|
|
10
32
|
| Path | What | Size | Licence |
|
|
11
33
|
|---|---|---|---|
|
|
12
|
-
| `conceptnet/slice.jsonl` | filtered English/tech-domain ConceptNet 5.7 slice (one assertion per line) | ~1.
|
|
34
|
+
| `conceptnet/slice.jsonl` | tier-1: filtered English/tech-domain ConceptNet 5.7 slice, quality-filtered (one assertion per line) | ~1.35 MB | **CC-BY-SA 4.0** (see `conceptnet/LICENSE-NOTICE`) |
|
|
13
35
|
| `conceptnet/fetch-slice.mjs` | regeneration tool — the ConceptNet **API** route (polite, ~1 req/s) | — | MPL-2.0 |
|
|
14
36
|
| `conceptnet/filter-dump.mjs` | regeneration tool — the ConceptNet **dump** route (produced the committed slice; the API was down) | — | MPL-2.0 |
|
|
37
|
+
| `conceptnet/quality-filter.mjs` | second-pass noise filter (drops sentence-fragment/numeric/opinion rows); produced the committed clean slice | — | MPL-2.0 |
|
|
15
38
|
| `conceptnet/README.md` | provenance, retrieval date, seed terms, filter rules, row counts | — | — |
|
|
39
|
+
| `tier2/manifest.json` | tier-2: index of specialised corpuses (id, kind, description, source, sha256, size) | — | MPL-2.0 |
|
|
40
|
+
| `tier2/generate.mjs` | tier-2: the curated-corpus generator + manifest writer (+ opt-in network-fetch path) | — | MPL-2.0 |
|
|
41
|
+
| `tier2/{aws,python,java}.jsonl` | tier-2 SAMPLE corpuses — same fact shape as the tier-1 slice, loadable via the same path | ~4 KB each | MPL-2.0 |
|
|
16
42
|
|
|
17
43
|
And alongside (same phase, different directory because it is tmct-original
|
|
18
44
|
data, not a derived corpus):
|
|
@@ -44,9 +70,55 @@ await seedMemory(repoDir, { limit: 500 }); // capped (fast bootstrap)
|
|
|
44
70
|
- `ace = "none"` relations (RelatedTo, Synonym, FormOf, …) are deliberately
|
|
45
71
|
NOT seeded — they are kept in the slice for future lexicon/fuzzy-match use.
|
|
46
72
|
|
|
73
|
+
## Tier-2 specialised corpuses (`tier2/`)
|
|
74
|
+
|
|
75
|
+
A tier-2 corpus is a small, curated, LANGUAGE- or DOMAIN-specific fact set in
|
|
76
|
+
the **exact tier-1 fact shape** — one JSON object per line,
|
|
77
|
+
`{"start":"/c/en/…","rel":"/r/…","end":"/c/en/…","weight":N,"surfaceText":"…"}`,
|
|
78
|
+
with `rel` drawn only from the mapped relations in
|
|
79
|
+
`src/corpus/conceptnet-map.toml`. Because the shape is identical, a tier-2 file
|
|
80
|
+
loads and seeds through the very same `loadSlice()`/`toFacts()` path as the
|
|
81
|
+
tier-1 slice — `tier2/generate.mjs --verify` proves it (each sample loads and
|
|
82
|
+
all its facts seed cleanly, no `ace=none` dead rows).
|
|
83
|
+
|
|
84
|
+
The idea: a Python repo pulls the `python` corpus so tmct knows "a dict is a
|
|
85
|
+
kind of hash table"; an AWS project pulls `aws` so it knows "S3 is object
|
|
86
|
+
storage, a bucket is part of S3". Language terms unify onto the **shared CS
|
|
87
|
+
concept vocabulary** (`list → array`, `hashmap → hash table`) so specialised
|
|
88
|
+
knowledge connects to the tier-1 graph instead of floating apart.
|
|
89
|
+
|
|
90
|
+
**To add a corpus:** add an entry to `CORPUSES` in `tier2/generate.mjs` (a list
|
|
91
|
+
of `[subject, relation, concept]` triples) and run `node tier2/generate.mjs
|
|
92
|
+
--verify`. It rewrites `<id>.jsonl` and `manifest.json` (facts count, byte size,
|
|
93
|
+
sha256) in one deterministic pass. Curated data is authored in that file so it
|
|
94
|
+
stays reviewable; a corpus too big to hand-curate is a `fetch` manifest entry
|
|
95
|
+
(URL + sha256, opt-in network — `fetchCorpus()` is the reference downloader).
|
|
96
|
+
|
|
97
|
+
**How the coordinator should wire tier-2 into `tmct init` (Wave-2 — NOT done
|
|
98
|
+
here):**
|
|
99
|
+
|
|
100
|
+
1. Add a tier-2-aware seeder next to `seedMemory()` (e.g. `seedTier2(dir, id)`)
|
|
101
|
+
that reads `corpus/tier2/manifest.json`, resolves the requested corpus's
|
|
102
|
+
`file` (curated → already on disk; `fetch` → download + `sha256`-verify
|
|
103
|
+
first, only when network is explicitly enabled), then runs the SAME
|
|
104
|
+
`loadSlice → toFacts → appendFact` pipeline — **but stamps provenance
|
|
105
|
+
`corpus:tier2:<id> <rel>`** instead of the hard-coded `corpus:conceptnet …`
|
|
106
|
+
string in `toFacts()`. (Simplest: give `toFacts` an optional
|
|
107
|
+
`provenancePrefix` argument, default `"corpus:conceptnet"`.)
|
|
108
|
+
2. Give `tmct init` a `--corpus <id>[,<id>…]` flag (and/or codebase
|
|
109
|
+
auto-detection: a `requirements.txt`/`pyproject.toml` → `python`, a
|
|
110
|
+
`pom.xml`/`build.gradle` → `java`, an AWS SDK dep / `serverless.yml` →
|
|
111
|
+
`aws`). Default stays tier-1-only, offline, $0.
|
|
112
|
+
3. Idempotency is free — `seedMemory`'s content-hashed fact ids and pre-read
|
|
113
|
+
skip already handle re-seeds; tier-2 rides the same path.
|
|
114
|
+
|
|
115
|
+
None of that touches `package.json` or the tier-1 budget; tier-2 files are not
|
|
116
|
+
shipped, so they do not count against the ≤ 1.5 MB slice budget.
|
|
117
|
+
|
|
47
118
|
## How to regenerate / extend
|
|
48
119
|
|
|
49
|
-
See `conceptnet/README.md` — one command per route (API vs dump),
|
|
50
|
-
seed-term list to extend. The test suite
|
|
51
|
-
`test/corpus-templates.test.mjs`) guards the
|
|
52
|
-
en→en shape, the ≤ 1.5 MB budget, template
|
|
120
|
+
See `conceptnet/README.md` — one command per route (API vs dump), the
|
|
121
|
+
quality-filter pass, plus the seed-term list to extend. The test suite
|
|
122
|
+
(`test/corpus-conceptnet.test.mjs`, `test/corpus-templates.test.mjs`) guards the
|
|
123
|
+
contracts: slice/mapping drift, en→en shape, the ≤ 1.5 MB budget, template
|
|
124
|
+
ids/slots, and end-to-end seeding.
|
|
@@ -15,7 +15,9 @@ see `LICENSE-NOTICE` in this directory for the full attribution.
|
|
|
15
15
|
- **Source:** ConceptNet 5.7.0 assertions dump,
|
|
16
16
|
`https://s3.amazonaws.com/conceptnet/downloads/2019/edges/conceptnet-assertions-5.7.0.csv.gz`
|
|
17
17
|
(published 2019-07-03).
|
|
18
|
-
- **Retrieved + filtered:** 2026-07-04
|
|
18
|
+
- **Retrieved + filtered:** 2026-07-04; **regrown to the ~40k tier-1 target on
|
|
19
|
+
2026-07-05** (same dump, same licence) by widening the tech-seed domain and
|
|
20
|
+
raising the size budget — see "Growing the slice" below.
|
|
19
21
|
- **Why the dump, not the API:** the public API (`api.conceptnet.io`) was
|
|
20
22
|
hard-down (HTTP 502 from its nginx front-end on every request across ~15
|
|
21
23
|
attempts over 10+ minutes on 2026-07-04), so the slice was stream-filtered
|
|
@@ -33,12 +35,16 @@ see `LICENSE-NOTICE` in this directory for the full attribution.
|
|
|
33
35
|
policy: `/r/EtymologicallyRelatedTo`, `/r/EtymologicallyDerivedFrom`,
|
|
34
36
|
`/r/ExternalURL` (etymology noise and link-outs — no consumer in tmct).
|
|
35
37
|
3. **Tech-domain seed terms**: at least one endpoint's bare term is in the
|
|
36
|
-
|
|
38
|
+
tech seed list — the ~90-term base `SEED_TERMS` exported from
|
|
37
39
|
`fetch-slice.mjs` (software, computer, program, code, module, function,
|
|
38
40
|
database, server, network, bug, test, file, memory, algorithm, keyboard,
|
|
39
|
-
programmer, repository, commit, …)
|
|
41
|
+
programmer, repository, commit, …) **plus** the ~230-term `EXTRA_SEEDS`
|
|
42
|
+
growth list in `filter-dump.mjs` (programming languages, frameworks, data
|
|
43
|
+
structures, cloud/infra, protocols, tools, ML — python, java, docker,
|
|
44
|
+
kubernetes, git, neural_network, tcp, kernel, hashtable, …), added
|
|
45
|
+
2026-07-05 to reach the ~40k tier-1 target while staying in the tech domain.
|
|
40
46
|
4. **Dedupe** by `(start, rel, end)`, keeping the higher weight.
|
|
41
|
-
5. **Size budget** (committed slice ≤
|
|
47
|
+
5. **Size budget** (committed slice ≤ 5 MB; target ~4.3 MB), **two-tier**:
|
|
42
48
|
assertions whose relation maps to an ACE-OWL pattern (`ace != "none"` in
|
|
43
49
|
`conceptnet-map.toml`) are kept first, weight-descending; `ace = "none"`
|
|
44
50
|
relations (`RelatedTo`, `Synonym`, …) fill the remaining budget — they
|
|
@@ -46,34 +52,86 @@ see `LICENSE-NOTICE` in this directory for the full attribution.
|
|
|
46
52
|
facts.
|
|
47
53
|
6. Deterministic output order: `(rel, start, end)`.
|
|
48
54
|
|
|
49
|
-
##
|
|
55
|
+
## Quality-filter pass (regrown slice, 2026-07-05)
|
|
56
|
+
|
|
57
|
+
`filter-dump.mjs` keeps the DATA honest (tech-seed match, canonical relations,
|
|
58
|
+
budget) but not the SEMANTICS: ConceptNet's crowd-sourced "Verbosity"/Open-Mind
|
|
59
|
+
rows leave sentence-fragment "concepts" and opinion axioms that read as nonsense
|
|
60
|
+
once seeded ("a computer is a kind of dumb", "a class is a kind of elegance",
|
|
61
|
+
"mouse AtLocation taloned_grip_of_owl", "2 is a kind of software"). A second
|
|
62
|
+
pass, `quality-filter.mjs`, removes those by term/relation shape (never per
|
|
63
|
+
row):
|
|
64
|
+
|
|
65
|
+
- **numeric endpoint** — bare term all digits (`2`, `1000`, `80386`)
|
|
66
|
+
- **single-char endpoint** — bare term length ≤ 1 (`a`, `r`, `m`)
|
|
67
|
+
- **sentence fragment** — ≥ 4 underscore-words on either endpoint
|
|
68
|
+
(`taloned_grip_of_owl`, `worlds_largest_interconnected_network_of_networks`)
|
|
69
|
+
- **definitional phrase** — `/r/DefinedAs` with a ≥ 3-word object (real
|
|
70
|
+
`DefinedAs` is a synonym: `cpu → processor`)
|
|
71
|
+
- **opinion object** — `/r/IsA` / `/r/DefinedAs` whose object is in a small
|
|
72
|
+
evidence-based set (`elegance, evil, gloom, unreality, universalism, dumb,
|
|
73
|
+
free, junk`) — never a class
|
|
74
|
+
|
|
75
|
+
**Result (2026-07-05 regrow): 45,633 → 44,947 rows (686 cut), 4,308,850 →
|
|
76
|
+
4,220,629 bytes.** Cuts by reason: sentence-fragment 613, single-char 48,
|
|
77
|
+
numeric 11, opinion-object 8, definitional-phrase 6. Of the seedable (mapped,
|
|
78
|
+
`ace≠none`) facts, the clean slice carries **6,255** — up from 3,884 in the
|
|
79
|
+
1.35 MB slice. No relation disappeared entirely (the drift guard stays
|
|
80
|
+
satisfied). Re-run any time with:
|
|
50
81
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
82
|
+
```bash
|
|
83
|
+
node corpus/conceptnet/quality-filter.mjs --in-place corpus/conceptnet/slice.jsonl
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Growing the slice toward the ~40k tier-1 target (done 2026-07-05)
|
|
87
|
+
|
|
88
|
+
The operator's ~40k-fact tier-1 target is **shipped**: the slice was regrown
|
|
89
|
+
from the same ConceptNet dump by (a) widening the tech domain with the
|
|
90
|
+
`EXTRA_SEEDS` list in `filter-dump.mjs` (~230 tech terms — languages,
|
|
91
|
+
frameworks, data structures, cloud/infra, protocols, tools, ML) and (b) raising
|
|
92
|
+
`MAX_BYTES` in `filter-dump.mjs` from 1.4 MB to 4.5 MB with the matching test
|
|
93
|
+
budget assertion (`test/corpus-conceptnet.test.mjs`) raised from 1.5 MB to
|
|
94
|
+
5 MB. The widened seed set matched **45,633 unique en→en assertions** (all under
|
|
95
|
+
budget, so no tier-trimming was needed this pass), and the quality filter
|
|
96
|
+
trimmed them to the committed **44,947 clean facts**. Regenerate with:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# 1. stream-filter the dump (widened seeds + raised budget already live in filter-dump.mjs)
|
|
100
|
+
curl -s https://s3.amazonaws.com/conceptnet/downloads/2019/edges/conceptnet-assertions-5.7.0.csv.gz \
|
|
101
|
+
| gunzip -c | node corpus/conceptnet/filter-dump.mjs > corpus/conceptnet/slice.jsonl
|
|
102
|
+
# 2. re-apply the semantic quality filter
|
|
103
|
+
node corpus/conceptnet/quality-filter.mjs --in-place corpus/conceptnet/slice.jsonl
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Row counts (regrown slice, 2026-07-05)
|
|
107
|
+
|
|
108
|
+
**44,947 clean assertions, 4,220,629 bytes** — the quality-filtered committed
|
|
109
|
+
slice (raw filter-dump output was 45,633 rows / 4,308,850 bytes; 34,074,917 dump
|
|
110
|
+
lines scanned; 45,633 unique en→en seed assertions matched = 6,670 mappable +
|
|
111
|
+
38,963 `ace="none"`; ALL kept under the 4.5 MB budget, then 686 noise rows cut).
|
|
112
|
+
30 of the 31 non-filtered canonical relations are present; **6,255 seedable
|
|
113
|
+
(`ace≠none`) facts**:
|
|
55
114
|
|
|
56
115
|
| Relation | Rows | | Relation | Rows |
|
|
57
116
|
|---|---|---|---|---|
|
|
58
|
-
| `/r/RelatedTo` |
|
|
59
|
-
| `/r/HasContext` |
|
|
60
|
-
| `/r/IsA` |
|
|
61
|
-
| `/r/DerivedFrom` |
|
|
62
|
-
| `/r/
|
|
63
|
-
| `/r/
|
|
64
|
-
| `/r/
|
|
65
|
-
| `/r/
|
|
66
|
-
| `/r/CapableOf` |
|
|
67
|
-
| `/r/MannerOf` |
|
|
68
|
-
| `/r/PartOf` |
|
|
69
|
-
| `/r/
|
|
70
|
-
| `/r/
|
|
71
|
-
| `/r/
|
|
72
|
-
| | | | `/r/
|
|
73
|
-
|
|
74
|
-
Absent from the slice (nothing matched the seed terms): `/r/ObstructedBy
|
|
75
|
-
|
|
76
|
-
surfaces them stays covered.
|
|
117
|
+
| `/r/RelatedTo` | 29016 | | `/r/HasSubevent` | 42 |
|
|
118
|
+
| `/r/HasContext` | 4376 | | `/r/DistinctFrom` | 32 |
|
|
119
|
+
| `/r/IsA` | 4173 | | `/r/HasProperty` | 30 |
|
|
120
|
+
| `/r/DerivedFrom` | 3388 | | `/r/ReceivesAction` | 27 |
|
|
121
|
+
| `/r/Synonym` | 1126 | | `/r/MotivatedByGoal` | 26 |
|
|
122
|
+
| `/r/AtLocation` | 642 | | `/r/MadeOf` | 20 |
|
|
123
|
+
| `/r/FormOf` | 528 | | `/r/Causes` | 18 |
|
|
124
|
+
| `/r/UsedFor` | 378 | | `/r/CreatedBy` | 15 |
|
|
125
|
+
| `/r/CapableOf` | 231 | | `/r/CausesDesire` | 13 |
|
|
126
|
+
| `/r/MannerOf` | 230 | | `/r/Desires` | 9 |
|
|
127
|
+
| `/r/PartOf` | 196 | | `/r/HasLastSubevent` | 8 |
|
|
128
|
+
| `/r/Antonym` | 155 | | `/r/HasFirstSubevent` | 7 |
|
|
129
|
+
| `/r/HasPrerequisite` | 108 | | `/r/LocatedNear` | 3 |
|
|
130
|
+
| `/r/SimilarTo` | 102 | | `/r/DefinedAs` | 2 |
|
|
131
|
+
| `/r/HasA` | 45 | | `/r/SymbolOf` | 1 |
|
|
132
|
+
|
|
133
|
+
Absent from the slice (nothing matched the seed terms): `/r/ObstructedBy` — it
|
|
134
|
+
still has a mapping row, so a regenerated slice that surfaces it stays covered.
|
|
77
135
|
|
|
78
136
|
## How to regenerate / extend
|
|
79
137
|
|
|
@@ -87,11 +145,12 @@ curl -s https://s3.amazonaws.com/conceptnet/downloads/2019/edges/conceptnet-asse
|
|
|
87
145
|
| node corpus/conceptnet/filter-dump.mjs > corpus/conceptnet/slice.jsonl
|
|
88
146
|
```
|
|
89
147
|
|
|
90
|
-
To extend the domain, add seed terms to `
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
To extend the domain, add seed terms to `EXTRA_SEEDS` in `filter-dump.mjs` (the
|
|
149
|
+
dump route that produces the committed slice) or `SEED_TERMS` in
|
|
150
|
+
`fetch-slice.mjs` (the API route) and re-run. `npm test` guards the contract:
|
|
151
|
+
every relation present in the slice must have a row in
|
|
152
|
+
`src/corpus/conceptnet-map.toml` (drift guard), en→en shape and the ≤ 5 MB
|
|
153
|
+
budget are asserted, and the seeding path is exercised end-to-end.
|
|
95
154
|
|
|
96
155
|
## Consumers
|
|
97
156
|
|