@polycode-projects/the-mechanical-code-talker 6.0.19 → 6.0.20

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.
Files changed (72) hide show
  1. package/README.md +20 -23
  2. package/bin/tmct.mjs +16 -33
  3. package/corpus/LICENSES.json +0 -21
  4. package/corpus/README.md +10 -13
  5. package/corpus/reference/manifest.json +19 -19
  6. package/corpus/reference/shards/ref-01.jsonl.gz +0 -0
  7. package/corpus/reference/shards/ref-04.jsonl.gz +0 -0
  8. package/corpus/reference/shards/ref-08.jsonl.gz +0 -0
  9. package/corpus/reference/shards/ref-10.jsonl.gz +0 -0
  10. package/corpus/reference/shards/ref-11.jsonl.gz +0 -0
  11. package/corpus/reference/shards/ref-17.jsonl.gz +0 -0
  12. package/corpus/reference/shards/ref-20.jsonl.gz +0 -0
  13. package/corpus/reference/shards/ref-25.jsonl.gz +0 -0
  14. package/corpus/reference/shards/ref-2c.jsonl.gz +0 -0
  15. package/corpus/tier2/generate.mjs +6 -142
  16. package/corpus/tier2/manifest.json +0 -42
  17. package/package.json +4 -4
  18. package/src/adapters/corpus/child-seed.mjs +74 -0
  19. package/src/adapters/corpus/conceptnet.mjs +45 -26
  20. package/src/adapters/memory/blocks.mjs +7 -1
  21. package/src/adapters/memory/core.mjs +453 -103
  22. package/src/adapters/memory/corpus-bands.mjs +27 -10
  23. package/src/adapters/memory/inspect.mjs +24 -5
  24. package/src/adapters/memory/rows.mjs +106 -9
  25. package/src/adapters/memory/shacl.mjs +10 -3
  26. package/src/domain/ask.mjs +27 -10
  27. package/src/domain/cli-verbs.mjs +3 -4
  28. package/src/domain/completions/group.mjs +8 -3
  29. package/src/domain/completions/infer.mjs +7 -2
  30. package/src/domain/completions/prune.mjs +5 -1
  31. package/src/domain/completions/rank.mjs +7 -2
  32. package/src/domain/digest/compose.mjs +5 -1
  33. package/src/domain/digest/select.mjs +12 -6
  34. package/src/domain/domain.mjs +15 -8
  35. package/src/domain/el-classify.mjs +11 -2
  36. package/src/domain/fact-phrase.mjs +86 -4
  37. package/src/domain/hash.mjs +9 -0
  38. package/src/domain/memory/bias.mjs +8 -4
  39. package/src/domain/memory/capability.mjs +12 -6
  40. package/src/domain/memory/fact-order.mjs +29 -0
  41. package/src/domain/memory/resolution.mjs +3 -0
  42. package/src/domain/news-feed.mjs +422 -56
  43. package/src/domain/reference-pack.mjs +5 -0
  44. package/src/domain/sense-scope.mjs +116 -0
  45. package/src/domain/sense-split.mjs +1 -1
  46. package/src/domain/syllogise.mjs +21 -13
  47. package/src/domain/tableau.mjs +23 -14
  48. package/src/domain/worlds-pack.mjs +5 -1
  49. package/src/services/adventure-autoplay.mjs +6 -1
  50. package/src/services/adventure-editor.mjs +43 -21
  51. package/src/services/adventure-viz.mjs +26 -9
  52. package/src/services/adventure.mjs +40 -10
  53. package/src/services/chat.mjs +253 -113
  54. package/src/services/extensions.mjs +51 -58
  55. package/src/services/extract-facts.mjs +670 -95
  56. package/src/services/init.mjs +4 -4
  57. package/src/services/ledger-viz.mjs +9 -4
  58. package/src/services/memory-panel-viz.mjs +4 -5
  59. package/src/services/mud-editor.mjs +40 -16
  60. package/src/services/mud-viz.mjs +8 -2
  61. package/src/services/mudiii-turn.mjs +5 -3
  62. package/src/services/mudiii-viz.mjs +8 -2
  63. package/src/services/news.mjs +257 -11
  64. package/src/services/research-viz.mjs +1 -1
  65. package/src/services/sprite-catalog-viz.mjs +10 -5
  66. package/src/surfaces/web/adventure-browser-entry.mjs +6 -12
  67. package/src/surfaces/web/memory-ask-browser.bundle.js +152 -151
  68. package/src/surfaces/web/mud-browser-entry.mjs +7 -11
  69. package/src/surfaces/web/research-browser-entry.mjs +5 -2
  70. package/corpus/tier2/aws.jsonl +0 -39
  71. package/corpus/tier2/java.jsonl +0 -31
  72. package/corpus/tier2/python.jsonl +0 -30
@@ -2,48 +2,6 @@
2
2
  "version": 1,
3
3
  "generated": "by corpus/tier2/generate.mjs",
4
4
  "corpuses": [
5
- {
6
- "id": "aws",
7
- "kind": "domain",
8
- "description": "Amazon Web Services core services and primitives (S3, Lambda, DynamoDB, EC2, IAM, SQS) mapped to general cloud/CS concepts.",
9
- "source": {
10
- "kind": "curated",
11
- "tool": "corpus/tier2/generate.mjs"
12
- },
13
- "file": "aws.jsonl",
14
- "facts": 39,
15
- "bytes": 4802,
16
- "sha256": "7ab3e656bc4bc717be3c50d97af030e8592d6597cde182b15390a1dfaca9755a",
17
- "license": "MPL-2.0"
18
- },
19
- {
20
- "id": "python",
21
- "kind": "language",
22
- "description": "Python language constructs and stdlib types mapped to the shared CS concept vocabulary (list->array, dict->hash table, …).",
23
- "source": {
24
- "kind": "curated",
25
- "tool": "corpus/tier2/generate.mjs"
26
- },
27
- "file": "python.jsonl",
28
- "facts": 30,
29
- "bytes": 3794,
30
- "sha256": "009245d024357bc4285e967fba486e32349ccec8fbe2796ba13011cb2be4c19c",
31
- "license": "MPL-2.0"
32
- },
33
- {
34
- "id": "java",
35
- "kind": "language",
36
- "description": "Java language and JVM constructs mapped to the shared CS concept vocabulary (ArrayList->list, HashMap->hash table, …).",
37
- "source": {
38
- "kind": "curated",
39
- "tool": "corpus/tier2/generate.mjs"
40
- },
41
- "file": "java.jsonl",
42
- "facts": 31,
43
- "bytes": 3920,
44
- "sha256": "d089426833c393f6f1574fe75c4ac94a1483ee80f72f79bd7fd32aaaa24a6e44",
45
- "license": "MPL-2.0"
46
- },
47
5
  {
48
6
  "id": "general",
49
7
  "kind": "domain",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "6.0.19",
3
+ "version": "6.0.20",
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; indexes a repo on request (tmct index) or reads any producer's graph.",
@@ -140,14 +140,14 @@
140
140
  "chat:repo": "node --disable-warning=ExperimentalWarning bin/tmct.mjs chat --repo",
141
141
  "chat:plain": "node --disable-warning=ExperimentalWarning bin/tmct.mjs chat --plain",
142
142
  "chat:narrate": "node --disable-warning=ExperimentalWarning bin/tmct.mjs chat --narrate",
143
- "init": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus seon && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus conceptnet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus aws && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus python && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus java",
143
+ "init": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus code && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus conceptnet",
144
144
  "init:small": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init",
145
145
  "init:sqlite": "npm run init:small",
146
146
  "init:persona:human": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --with-persona human",
147
147
  "init:persona:empty": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --with-persona empty",
148
148
  "init:large": "npm run init",
149
- "init:xl": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --persona-size large && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus seon && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus conceptnet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus aws && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus python && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus java && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus wordnet-xl",
150
- "init:xxl": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --persona-size large && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus seon && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus conceptnet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus aws && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus python && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus java && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus wordnet-full && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus namenet",
149
+ "init:xl": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --persona-size large && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus code && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus conceptnet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus wordnet-xl && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus namenet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus child",
150
+ "init:xxl": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --persona-size large && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus code && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus conceptnet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus wordnet-full && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus namenet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus child",
151
151
  "memory": "node --disable-warning=ExperimentalWarning bin/tmct.mjs memory",
152
152
  "memory:verbose": "node --disable-warning=ExperimentalWarning bin/tmct.mjs memory --verbose",
153
153
  "syllogise": "node --disable-warning=ExperimentalWarning bin/tmct.mjs syllogise",
@@ -0,0 +1,74 @@
1
+ // corpus/child-seed.mjs — the BULK reader for the shipped CHILD triples pack
2
+ // (corpus/child/), used when the pack seeds a store as an ordinary corpus band.
3
+ // src/adapters/corpus/child-pack.mjs is the other half: one term at a time, on a
4
+ // miss, for the clean-miss cascade. This one walks every shard once.
5
+ //
6
+ // The shards already carry tmct-vocabulary triples ({subject, predicate,
7
+ // object}), so there is no conceptnet-map.toml step here — the rows come out of
8
+ // the pack build already mapped.
9
+
10
+ import { readFileSync } from "node:fs";
11
+ import { gunzipSync } from "node:zlib";
12
+ import { join } from "node:path";
13
+ import { CHILD_SHARD_COUNT, isChildFactsRow } from "../../domain/child-pack.mjs";
14
+ import { appendNewFacts, preferThenLimit } from "./conceptnet.mjs";
15
+
16
+ /** Every shard basename the pack can hold, in order — the naming contract
17
+ * shardNameFor writes, walked rather than listed, so this reader needs no
18
+ * directory listing and runs in a browser bundle. */
19
+ function shardBasenames() {
20
+ return Array.from(
21
+ { length: CHILD_SHARD_COUNT },
22
+ (_, i) => `child-${i.toString(16).padStart(2, "0")}`,
23
+ );
24
+ }
25
+
26
+ /** Every distinct triple in `packDir`'s shards, in shard then row order, as
27
+ * appendFact-shaped facts. A term appears in one shard as a subject and in
28
+ * another as an object, so the same edge arrives twice — the first spelling
29
+ * wins and the duplicate is dropped here, before the store ever sees it.
30
+ * Absent or malformed shards contribute nothing rather than throwing, the same
31
+ * tolerance the per-term loader gives. */
32
+ export function loadChildPackFacts(packDir, provenancePrefix = "corpus:child") {
33
+ const facts = [];
34
+ const seen = new Set();
35
+ for (const shard of shardBasenames()) {
36
+ let body;
37
+ try {
38
+ body = gunzipSync(readFileSync(join(packDir, "shards", `${shard}.jsonl.gz`))).toString("utf8");
39
+ } catch {
40
+ continue;
41
+ }
42
+ for (const line of body.split("\n")) {
43
+ if (!line.trim()) continue;
44
+ let row;
45
+ try {
46
+ row = JSON.parse(line);
47
+ } catch {
48
+ continue;
49
+ }
50
+ if (!isChildFactsRow(row)) continue;
51
+ for (const f of row.facts) {
52
+ const key = `${f.subject} ${f.predicate} ${f.object}`;
53
+ if (seen.has(key)) continue;
54
+ seen.add(key);
55
+ facts.push({
56
+ subject: f.subject,
57
+ predicate: f.predicate,
58
+ object: f.object,
59
+ provenance: `${provenancePrefix} ${row.term}`,
60
+ });
61
+ }
62
+ }
63
+ }
64
+ return facts;
65
+ }
66
+
67
+ /** Seed `packDir`'s triples into `dir`'s memory through the same idempotent
68
+ * append every other corpus band uses. `limit`/`prefer` behave exactly as they
69
+ * do for a slice-shaped band. Returns { appended, skipped, total }. */
70
+ export async function seedChildPack(dir, { packDir, provenancePrefix, limit, prefer } = {}) {
71
+ const facts = preferThenLimit(loadChildPackFacts(packDir, provenancePrefix), prefer, limit);
72
+ const { appended, skipped } = await appendNewFacts(dir, facts);
73
+ return { appended, skipped, total: facts.length };
74
+ }
@@ -25,7 +25,7 @@ export const MAP_FILE = join(PKG_ROOT, "src", "adapters", "corpus", "conceptnet-
25
25
  // The tier-1 curated Software-Engineering ontology (SEON): concepts.jsonl shares
26
26
  // ConceptNet's slice shape and loads through the same loadSlice/loadMap/toFacts path.
27
27
  // definitions.jsonl is a separate {term, definition, sense} list for lexicon lookups.
28
- // Tier-2 corpuses (aws/python/java) share the slice shape too. The data lives under
28
+ // Tier-2 corpuses share the slice shape too. The data lives under
29
29
  // corpus/domains/code/ (the code domain pack's own directory); the bundle name
30
30
  // "seon" and its "corpus:seon" provenance prefix are unchanged by the move —
31
31
  // both are declared literally in src/services/extensions.mjs, not derived from
@@ -127,35 +127,30 @@ export function toFacts(assertions, map, provenancePrefix = "corpus:conceptnet")
127
127
  return facts;
128
128
  }
129
129
 
130
- /** Seed a repo's memory graph (<dir>/.tmct/memory/graph.json) from the committed slice.
131
- * `limit` caps the facts written; `prefer` stable-partitions facts by predicate (so a
132
- * capped seed favors the definitional band over whatever trivia the slice opens with).
133
- * Idempotent: pre-reads the store to skip triples already there, then writes survivors
134
- * in one batched appendFacts call. Returns { appended, skipped, total }.
135
- * `provenancePrefix` tags facts (default "corpus:conceptnet").
136
- *
137
- * `captureUnknownContext` (default false): also runs unknown-ingest.mjs's
138
- * ingestUnknownFromAssertions so a term that only appears in an `ace="none"` row (never
139
- * reified as a Fact) still lands in memory, tagged with the passage it was found in.
140
- * `unknownContextLimit` bounds how many distinct terms one call captures (default 500).
141
- * Loaded dynamically to avoid a load-time import cycle with unknown-ingest.mjs. */
142
- export async function seedMemory(dir, {
143
- limit, slicePath = SLICE_FILE, mapPath = MAP_FILE, prefer, provenancePrefix,
144
- captureUnknownContext = false, unknownContextLimit,
145
- } = {}) {
146
- const [assertions, map] = await Promise.all([loadSlice(slicePath), loadMap(mapPath)]);
147
- let facts = toFacts(assertions, map, provenancePrefix);
130
+ /** Stable-partition `facts` so the `prefer` predicates come first, then take the
131
+ * first `limit` of them. A capped band therefore buys its definitional backbone
132
+ * before it buys trivia. Either argument may be absent. */
133
+ export function preferThenLimit(facts, prefer, limit) {
134
+ let out = facts;
148
135
  if (Array.isArray(prefer) && prefer.length) {
149
136
  const rank = new Map(prefer.map((p, i) => [p, i]));
150
- facts = facts.slice().sort((a, b) => (rank.get(a.predicate) ?? prefer.length) - (rank.get(b.predicate) ?? prefer.length));
137
+ out = out.slice().sort((a, b) => (rank.get(a.predicate) ?? prefer.length) - (rank.get(b.predicate) ?? prefer.length));
151
138
  }
152
- if (limit !== undefined) facts = facts.slice(0, limit);
139
+ return limit === undefined ? out : out.slice(0, limit);
140
+ }
141
+
142
+ // Keyed with normFactTerm so it matches the store's own normalized read-back.
143
+ const factKey = (s, p, o) => `${normFactTerm(s)} ${p} ${normFactTerm(o)}`;
153
144
 
154
- // Keyed with normFactTerm so it matches the store's own normalized read-back.
155
- const factKey = (s, p, o) => `${normFactTerm(s)} ${p} ${normFactTerm(o)}`;
145
+ /** Write every fact `dir`'s store does not already hold, in one batched append,
146
+ * so re-seeding a band is idempotent and a term shared by two bands converges
147
+ * to one fact. `memory` is the store's already-loaded contents (callers that
148
+ * need it for their own work pass theirs rather than paying a second read).
149
+ * Returns { appended, skipped }. */
150
+ export async function appendNewFacts(dir, facts, memory) {
151
+ const store = memory ?? await loadMemory(dir);
156
152
  const existing = new Set();
157
- const memory = await loadMemory(dir);
158
- for (const ind of memory.individuals || []) {
153
+ for (const ind of store.individuals || []) {
159
154
  if (ind?.class !== "Fact") continue;
160
155
  const get = (key) => (ind.attributes || []).find((x) => x.key === key)?.value;
161
156
  existing.add(factKey(get("subject"), get("predicate"), get("object")));
@@ -173,6 +168,30 @@ export async function seedMemory(dir, {
173
168
  toWrite.push(fact);
174
169
  }
175
170
  const res = await appendFacts(dir, toWrite);
171
+ return { appended: res.appended, skipped: skipped + res.skipped };
172
+ }
173
+
174
+ /** Seed a repo's memory graph (<dir>/.tmct/memory/graph.json) from the committed slice.
175
+ * `limit` caps the facts written; `prefer` stable-partitions facts by predicate (so a
176
+ * capped seed favors the definitional band over whatever trivia the slice opens with).
177
+ * Idempotent: pre-reads the store to skip triples already there, then writes survivors
178
+ * in one batched appendFacts call. Returns { appended, skipped, total }.
179
+ * `provenancePrefix` tags facts (default "corpus:conceptnet").
180
+ *
181
+ * `captureUnknownContext` (default false): also runs unknown-ingest.mjs's
182
+ * ingestUnknownFromAssertions so a term that only appears in an `ace="none"` row (never
183
+ * reified as a Fact) still lands in memory, tagged with the passage it was found in.
184
+ * `unknownContextLimit` bounds how many distinct terms one call captures (default 500).
185
+ * Loaded dynamically to avoid a load-time import cycle with unknown-ingest.mjs. */
186
+ export async function seedMemory(dir, {
187
+ limit, slicePath = SLICE_FILE, mapPath = MAP_FILE, prefer, provenancePrefix,
188
+ captureUnknownContext = false, unknownContextLimit,
189
+ } = {}) {
190
+ const [assertions, map] = await Promise.all([loadSlice(slicePath), loadMap(mapPath)]);
191
+ const facts = preferThenLimit(toFacts(assertions, map, provenancePrefix), prefer, limit);
192
+
193
+ const memory = await loadMemory(dir);
194
+ const { appended, skipped } = await appendNewFacts(dir, facts, memory);
176
195
 
177
196
  let unknown;
178
197
  if (captureUnknownContext) {
@@ -185,7 +204,7 @@ export async function seedMemory(dir, {
185
204
  }
186
205
 
187
206
  return {
188
- appended: res.appended, skipped: skipped + res.skipped, total: facts.length,
207
+ appended, skipped, total: facts.length,
189
208
  ...(unknown ? { unknown } : {}),
190
209
  };
191
210
  }
@@ -27,6 +27,12 @@ const MAX_TOKENS_PER_BLOCK = 800; // beyond tokenizeProse's per-doc cap: union o
27
27
 
28
28
  const blocksDir = (dir) => join(dir, BLOCKS_DIR_REL);
29
29
 
30
+ // Codepoint order, never localeCompare — the block ids this settles ties on
31
+ // decide WHICH blocks retrieveBlocks hands back, so a locale-dependent
32
+ // comparison would let two readers retrieve different text from one store.
33
+ // src/domain/memory/fact-order.mjs states the rule for the fact rows.
34
+ const byCodepoint = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
35
+
30
36
  /** Only safe, filesystem-friendly block file names (ids are session uuids or
31
37
  * corpus slugs; anything else is normalized, never trusted into a path). */
32
38
  const safeName = (id) => String(id).replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 120) || "_";
@@ -216,7 +222,7 @@ export async function retrieveBlocks(dir, query, k = 3) {
216
222
  id, score: (idfSum * (1 + rank) * trustFactor) / Math.sqrt(1 + degree), rank, trust, file: b.file,
217
223
  });
218
224
  }
219
- scored.sort((a, b) => b.score - a.score || b.rank - a.rank || a.id.localeCompare(b.id));
225
+ scored.sort((a, b) => b.score - a.score || b.rank - a.rank || byCodepoint(a.id, b.id));
220
226
  const top = scored.slice(0, Math.max(1, k));
221
227
  for (const hit of top) {
222
228
  try { hit.text = await readFile(join(blocksDir(dir), hit.file), "utf8"); }