@polycode-projects/the-mechanical-code-talker 6.0.18 → 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 (76) 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 +6 -4
  18. package/src/adapters/corpus/child-seed.mjs +74 -0
  19. package/src/adapters/corpus/conceptnet.mjs +45 -26
  20. package/src/adapters/corpus/research-source.mjs +6 -2
  21. package/src/adapters/corpus/wikidata-live.mjs +92 -51
  22. package/src/adapters/memory/blocks.mjs +7 -1
  23. package/src/adapters/memory/core.mjs +505 -107
  24. package/src/adapters/memory/corpus-bands.mjs +27 -10
  25. package/src/adapters/memory/inspect.mjs +24 -5
  26. package/src/adapters/memory/rows.mjs +359 -30
  27. package/src/adapters/memory/shacl.mjs +10 -3
  28. package/src/domain/ask.mjs +27 -10
  29. package/src/domain/cli-verbs.mjs +3 -4
  30. package/src/domain/completions/group.mjs +8 -3
  31. package/src/domain/completions/infer.mjs +7 -2
  32. package/src/domain/completions/prune.mjs +5 -1
  33. package/src/domain/completions/rank.mjs +7 -2
  34. package/src/domain/digest/compose.mjs +5 -1
  35. package/src/domain/digest/select.mjs +12 -6
  36. package/src/domain/domain.mjs +15 -8
  37. package/src/domain/el-classify.mjs +11 -2
  38. package/src/domain/fact-phrase.mjs +86 -4
  39. package/src/domain/hash.mjs +9 -0
  40. package/src/domain/memory/bias.mjs +8 -4
  41. package/src/domain/memory/capability.mjs +12 -6
  42. package/src/domain/memory/fact-order.mjs +29 -0
  43. package/src/domain/memory/resolution.mjs +3 -0
  44. package/src/domain/news-feed.mjs +862 -92
  45. package/src/domain/reference-pack.mjs +5 -0
  46. package/src/domain/sense-gate.mjs +220 -0
  47. package/src/domain/sense-scope.mjs +116 -0
  48. package/src/domain/sense-split.mjs +1 -1
  49. package/src/domain/syllogise.mjs +60 -21
  50. package/src/domain/tableau.mjs +23 -14
  51. package/src/domain/term-ledger.mjs +16 -1
  52. package/src/domain/worlds-pack.mjs +5 -1
  53. package/src/services/adventure-autoplay.mjs +6 -1
  54. package/src/services/adventure-editor.mjs +43 -21
  55. package/src/services/adventure-viz.mjs +26 -9
  56. package/src/services/adventure.mjs +40 -10
  57. package/src/services/chat.mjs +270 -125
  58. package/src/services/extensions.mjs +51 -58
  59. package/src/services/extract-facts.mjs +906 -66
  60. package/src/services/init.mjs +4 -4
  61. package/src/services/ledger-viz.mjs +9 -4
  62. package/src/services/memory-panel-viz.mjs +4 -5
  63. package/src/services/mud-editor.mjs +40 -16
  64. package/src/services/mud-viz.mjs +8 -2
  65. package/src/services/mudiii-turn.mjs +5 -3
  66. package/src/services/mudiii-viz.mjs +8 -2
  67. package/src/services/news.mjs +306 -21
  68. package/src/services/research-viz.mjs +1 -1
  69. package/src/services/sprite-catalog-viz.mjs +10 -5
  70. package/src/surfaces/web/adventure-browser-entry.mjs +6 -12
  71. package/src/surfaces/web/memory-ask-browser.bundle.js +152 -151
  72. package/src/surfaces/web/mud-browser-entry.mjs +7 -11
  73. package/src/surfaces/web/research-browser-entry.mjs +5 -2
  74. package/corpus/tier2/aws.jsonl +0 -39
  75. package/corpus/tier2/java.jsonl +0 -31
  76. package/corpus/tier2/python.jsonl +0 -30
@@ -199,8 +199,8 @@ source = ${JSON.stringify(config.research.source)}
199
199
  if (!Object.keys(extras).length) return out;
200
200
  return `${out}
201
201
  # Extension packs + bias (src/services/extensions.mjs) — written by \`tmct init --with-persona\`
202
- # or a manual edit. Recognized names (human, seon, code, conceptnet, tier2-aws,
203
- # tier2-python, tier2-java, tier2-general) override the shipped defaults; any
202
+ # or a manual edit. Recognized names (human, seon, code, conceptnet, child,
203
+ # namenet, tier2-general) override the shipped defaults; any
204
204
  # other name declares a new host-supplied bundle (needs its own "kind").
205
205
  # [bias] is a flat bundle-name -> weight table consumed by
206
206
  # src/domain/memory/bias.mjs's ranking.
@@ -298,8 +298,8 @@ export async function initRepo(dir, { force = false, seed, env = process.env, pe
298
298
  "",
299
299
  "Corpus bundles (activate by id — no local copy needed):",
300
300
  " tmct import --corpus human | human-medium | human-large",
301
- " tmct import --corpus seon | conceptnet | aws | python | java | general",
302
- " tmct import --corpus wordnet-xl | wordnet-full | namenet (large: tens of thousands of facts)",
301
+ " tmct import --corpus code | seon | conceptnet | general",
302
+ " tmct import --corpus wordnet-xl | wordnet-full | namenet | child (large: tens of thousands of facts)",
303
303
  "",
304
304
  "Ontology / lexicon resources are file paths declared as extension entries:",
305
305
  " tmct import --ontology <path> | --lexicon <path>",
@@ -25,6 +25,11 @@ import { dirname, join } from "node:path";
25
25
 
26
26
  const LEDGER_ROW_LIMIT_DEFAULT = 20000;
27
27
 
28
+ /** Codepoint order, never localeCompare — every string sorted below (a fact
29
+ * row's id or createdAt, a term label, a source bundle key, a predicate) is
30
+ * read off the stored fact set, so two locales have to render the same page. */
31
+ const byCodepoint = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
32
+
28
33
  // The ledger reads as an observability dashboard (dense stat tiles, bar
29
34
  // panels, a log-style fact stream) rather than an editorial page, so its
30
35
  // chrome runs the system sans everywhere instead of THEME_TOKENS_CSS's
@@ -168,7 +173,7 @@ export function computeLedgerDataFromPayload(payload, { focus, term, rowLimit =
168
173
  src: r.provenance || (r.sourceTypes || []).join(" ") || "unrecorded",
169
174
  };
170
175
  });
171
- rows.sort((a, b) => (b.createdAt || "").localeCompare(a.createdAt || "") || a.id.localeCompare(b.id));
176
+ rows.sort((a, b) => byCodepoint(b.createdAt || "", a.createdAt || "") || byCodepoint(a.id, b.id));
172
177
 
173
178
  // Term index + adjacency.
174
179
  const termMap = new Map(); // term -> { degree, best: {trust, prov}, newest }
@@ -187,7 +192,7 @@ export function computeLedgerDataFromPayload(payload, { focus, term, rowLimit =
187
192
  }
188
193
  const terms = [...termMap.values()]
189
194
  .map(({ term: t, degree, prov, newest }) => ({ term: t, degree, prov, newest }))
190
- .sort((a, b) => b.degree - a.degree || a.term.localeCompare(b.term));
195
+ .sort((a, b) => b.degree - a.degree || byCodepoint(a.term, b.term));
191
196
  const edges = rows.map((r) => ({ s: r.s, o: r.o, id: r.id }));
192
197
 
193
198
  // Focus: the asked term when it resolves; otherwise the newest taught
@@ -382,11 +387,11 @@ export function computeLedgerStats(rows, terms, contradictions) {
382
387
  }
383
388
  const bundles = [...bundleCounts.entries()]
384
389
  .map(([key, count]) => ({ key, label: bundleLabelFor(key), count }))
385
- .sort((a, b) => b.count - a.count || a.key.localeCompare(b.key))
390
+ .sort((a, b) => b.count - a.count || byCodepoint(a.key, b.key))
386
391
  .slice(0, BUNDLE_TOP_N);
387
392
  const predicates = [...predicateCounts.entries()]
388
393
  .map(([predicate, v]) => ({ predicate, phrase: v.phrase, count: v.count }))
389
- .sort((a, b) => b.count - a.count || a.predicate.localeCompare(b.predicate))
394
+ .sort((a, b) => b.count - a.count || byCodepoint(a.predicate, b.predicate))
390
395
  .slice(0, PREDICATE_TOP_N);
391
396
 
392
397
  const totalDegree = (terms || []).reduce((sum, t) => sum + (t.degree || 0), 0);
@@ -23,10 +23,9 @@ export function bandLabelFor(key) {
23
23
  "human-large": "human persona (large)",
24
24
  seon: "seon ontology",
25
25
  conceptnet: "ConceptNet",
26
- "tier2-aws": "AWS",
27
- "tier2-python": "Python",
28
- "tier2-java": "Java",
29
26
  "wordnet-xl": "WordNet",
27
+ namenet: "NameNet",
28
+ child: "child vocabulary",
30
29
  };
31
30
  return BAND_LABELS[key] || key;
32
31
  }
@@ -39,7 +38,7 @@ export function bandLabelFor(key) {
39
38
  export function statsSummaryLine(stats, bandLabel) {
40
39
  const BAND_ORDER = [
41
40
  "human", "human-medium", "human-large", "seon", "conceptnet",
42
- "tier2-aws", "tier2-python", "tier2-java", "wordnet-xl",
41
+ "wordnet-xl", "namenet", "child",
43
42
  "taught this session", "other",
44
43
  ];
45
44
  if (!stats || !stats.total) return "no starter memory; starting empty";
@@ -172,7 +171,7 @@ export function renderStatsPanelInto(panelEl, stats, opts) {
172
171
  const { bandLabel, taughtHint, onForget = null, persistNote = null } = opts;
173
172
  const BAND_ORDER = [
174
173
  "human", "human-medium", "human-large", "seon", "conceptnet",
175
- "tier2-aws", "tier2-python", "tier2-java", "wordnet-xl",
174
+ "wordnet-xl", "namenet", "child",
176
175
  "taught this session", "other",
177
176
  ];
178
177
  function bandRow(label, count) {
@@ -24,10 +24,15 @@
24
24
  // Two predicate families sync differently, for the reason adventure-editor.mjs's
25
25
  // own header sets out:
26
26
  // - PLACEMENT (currently-in/located-in/fixed-in/hidden-in), OPENNESS and MASS
27
- // are fold-versioned — foldWorldState already reads the newest write as the
28
- // current truth, which is how every turn's own move and mass drain lands — so
29
- // an edit to one is a plain new write superseding the old, never a
30
- // retraction. Mass belongs here and not below precisely because it MOVES: a
27
+ // are fold-versioned — foldWorldState ranks these rows by the (epoch, turn)
28
+ // pair stamped on the subject, which is how every turn's own move and mass
29
+ // drain lands — so an edit to one is a plain new write superseding the old,
30
+ // never a retraction. The superseding write is stamped one turn past the
31
+ // world's own turn count, exactly the way every in-game action's commit()
32
+ // writes, so it OUTRANKS what it replaces instead of merely arriving after
33
+ // it — at equal turn the fold takes whichever row comes later in the array,
34
+ // and array order is nobody's to promise once a peer's facts have merged
35
+ // in. Mass belongs here and not below precisely because it MOVES: a
31
36
  // playing animal's weight is a fold over turn snapshots, so an editor
32
37
  // diffing raw mass rows would show the seed and fight the drain.
33
38
  // - Everything else (typing, the class hierarchy, exits, the predator flag,
@@ -37,6 +42,8 @@
37
42
  // that fails to parse this keystroke must never be read as "this fact is
38
43
  // gone".
39
44
 
45
+ import { snapshotSubject } from "../domain/world-snapshot.mjs";
46
+
40
47
  export { wordBeforeCursor } from "./viz-theme.mjs";
41
48
 
42
49
  const PLACEMENT_KIND = "placement";
@@ -296,15 +303,26 @@ export function editableMudOtherRows(rows) {
296
303
 
297
304
  /** Plan the fact-store writes one parsed edit implies, from already-parsed
298
305
  * `triples` against the world's current `rows`/`state`. Returns
299
- * `{ toAppend, toRemoveIds }` — pure, no I/O.
306
+ * `{ toAppend, toRemoveIds, editTurn }` — pure, no I/O. `toAppend` rows are
307
+ * store-ready: a fold-versioned one already carries its `subject@turnN`
308
+ * stamp, and `editTurn` is the turn every one of them was stamped at, for
309
+ * the caller's own provenance tag.
310
+ *
311
+ * Placement, openness and mass triples are never retracted: one joins
312
+ * `toAppend` only when it actually differs from the subject's current folded
313
+ * value, so re-asserting an unchanged line doesn't append a no-op duplicate
314
+ * per keystroke. Each one is stamped one turn past the world's own turn
315
+ * count, which is what makes it outrank the row it supersedes rather than
316
+ * tie with it and win on array position.
300
317
  *
301
- * Placement and openness triples are never retracted: one joins `toAppend` only
302
- * when it actually differs from the subject's current folded value, so
303
- * re-asserting an unchanged line doesn't append a no-op duplicate per keystroke.
304
- * Everything else gets a real add/remove diff but the CALLER decides whether
305
- * `toRemoveIds` is safe to apply, and must skip it whenever the parse reported
306
- * any unrecognized line. */
318
+ * Everything else keeps its bare subject every reader takes those raw, and
319
+ * a stamped one names a subject no verb resolves. They get a real add/remove
320
+ * diff but the CALLER decides whether `toRemoveIds` is safe to apply, and
321
+ * must skip it whenever the parse reported any unrecognized line. */
307
322
  export function planMudEditorSync(rows, state, triples) {
323
+ const editTurn = (state?.turnCount ?? 0) + 1;
324
+ const editEpoch = state?.epoch ?? 0;
325
+ const stampedForFold = (t) => ({ ...t, subject: snapshotSubject(t.subject, editTurn, editEpoch) });
308
326
  const toAppend = [];
309
327
  // One "already said this" set per fold-versioned family, never one shared set:
310
328
  // a subject can carry a placement, an openness and a mass at once, and a
@@ -320,13 +338,13 @@ export function planMudEditorSync(rows, state, triples) {
320
338
  seen.add(t.subject);
321
339
  if (t.kind === PLACEMENT_KIND) {
322
340
  const current = state.placements?.get(t.subject);
323
- if (!current || current.predicate !== t.predicate || current.object !== t.object) toAppend.push(t);
341
+ if (!current || current.predicate !== t.predicate || current.object !== t.object) toAppend.push(stampedForFold(t));
324
342
  } else if (t.kind === OPENNESS_KIND) {
325
343
  const current = state.openness?.get(t.subject);
326
- if (!current || current.open !== (t.object === "true")) toAppend.push(t);
344
+ if (!current || current.open !== (t.object === "true")) toAppend.push(stampedForFold(t));
327
345
  } else {
328
346
  const current = state.masses?.get(t.subject);
329
- if (!current || Number(current.value) !== Number(t.object)) toAppend.push(t);
347
+ if (!current || Number(current.value) !== Number(t.object)) toAppend.push(stampedForFold(t));
330
348
  }
331
349
  }
332
350
 
@@ -342,7 +360,7 @@ export function planMudEditorSync(rows, state, triples) {
342
360
  for (const [key, id] of currentKeys) {
343
361
  if (!newOtherKeys.has(key)) toRemoveIds.push(id);
344
362
  }
345
- return { toAppend, toRemoveIds };
363
+ return { toAppend, toRemoveIds, editTurn };
346
364
  }
347
365
 
348
366
  /** The additive half of planMudEditorSync, for ONE already-parsed triple: the
@@ -350,7 +368,13 @@ export function planMudEditorSync(rows, state, triples) {
350
368
  * document says what the world contains, so a fact missing from it has gone;
351
369
  * one sentence only ever says what it says, so nothing it leaves out is
352
370
  * evidence of anything. Re-asserting a fact the world already holds appends
353
- * nothing, and `reason` says which of the two happened. Pure. */
371
+ * nothing, and `reason` says which of the two happened. Pure.
372
+ *
373
+ * The triple comes back with its bare subject, unlike planMudEditorSync's
374
+ * rows: a taught sentence can MINT the thing it talks about, and the caller
375
+ * picks the fresh id, so only the caller knows which subject the fold stamp
376
+ * belongs on. world-teach.mjs stamps a fold-versioned one at `turnCount + 1`
377
+ * there, for the same reason planMudEditorSync does. */
354
378
  export function planTaughtMudTriple(rows, state, triple) {
355
379
  if (!triple?.subject || !triple?.object) return { toAppend: [], reason: "nothing parsed" };
356
380
  if (triple.kind === PLACEMENT_KIND) {
@@ -170,7 +170,10 @@ export function carriedItemsFor(rows, state, character) {
170
170
  return [...state.placements]
171
171
  .filter(([, p]) => p.predicate === "mgx:located-in" && p.object === character)
172
172
  .map(([subject]) => ({ subject, spriteClass: spriteClassForObject(rows, subject) }))
173
- .sort((a, b) => a.subject.localeCompare(b.subject));
173
+ // Codepoint order, never localeCompare — inlined, not a module-level
174
+ // helper, because this function is spliced verbatim into the page's own
175
+ // inline script and can carry no outer-scope reference with it.
176
+ .sort((a, b) => (a.subject < b.subject ? -1 : a.subject > b.subject ? 1 : 0));
174
177
  }
175
178
 
176
179
  /** Every room's depth level, `Map<roomId, number>`, BFS from `root` at level
@@ -1994,7 +1997,10 @@ function pageScript() {
1994
1997
  svg: creature ? spriteSvgFor(speciesOfCharacter(subject), editRows, subject) : objectSvgFor(subject, editRows),
1995
1998
  });
1996
1999
  }
1997
- here.sort(function (a, b) { return a.subject.localeCompare(b.subject); });
2000
+ // Codepoint order, never localeCompare a placed subject traces back to
2001
+ // a world fact, and two readers must render the same room-detail cast
2002
+ // order regardless of the browser's own locale.
2003
+ here.sort(function (a, b) { return a.subject < b.subject ? -1 : a.subject > b.subject ? 1 : 0; });
1998
2004
  castEl.innerHTML = here.map(function (t) { return editCardHtml(t.subject, t.label, t.svg); }).join("");
1999
2005
  const kind = roomKindForRoom(editRows, selectedRoomId);
2000
2006
  const ways = [...(editState.exits.get(selectedRoomId) || new Map()).keys()].sort();
@@ -805,10 +805,12 @@ const PLACED_BY_PREDICATE = "mgx:placed-by";
805
805
 
806
806
  // The families foldTownSquareState ranks by (epoch, turn). A row in one of
807
807
  // them needs a snapshot subject or it ranks as turn 0 and loses to anything
808
- // already played about the same thing. mgx:placed-by is read raw and keeps
809
- // its bare subject, exactly as placeFood writes it.
808
+ // already played about the same thing. mgx:placed-by is one of them: placeFood
809
+ // writes the item's first placer under a bare subject when it mints the item,
810
+ // so a taught placer that stayed bare too would tie with it at turn 0 and win
811
+ // only by sitting later in the array.
810
812
  const TAUGHT_SNAPSHOT_PREDICATES = new Set([
811
- PLACEMENT_PREDICATE, MASS_PREDICATE, MOOD_PREDICATE, FACING_PREDICATE,
813
+ PLACEMENT_PREDICATE, MASS_PREDICATE, MOOD_PREDICATE, FACING_PREDICATE, PLACED_BY_PREDICATE,
812
814
  ]);
813
815
 
814
816
  // The closed cast vocabulary a taught sentence may name, matching the lane's
@@ -230,7 +230,10 @@ export function propPlacementsFrom(rows, assetManifest) {
230
230
  asset: byKey.get(entry.model) || null,
231
231
  });
232
232
  }
233
- placements.sort((a, b) => a.id.localeCompare(b.id));
233
+ // Codepoint order, never localeCompare — inlined, not a module-level
234
+ // helper, because this function is spliced verbatim into the page's own
235
+ // inline script and can carry no outer-scope reference with it.
236
+ placements.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
234
237
  return placements;
235
238
  }
236
239
 
@@ -286,7 +289,10 @@ export function currentPlacementsFrom(rows) {
286
289
  for (const [subject, entry] of latest) {
287
290
  if (!gone.has(subject)) placements.push({ subject, cell: entry.cell });
288
291
  }
289
- placements.sort((a, b) => a.subject.localeCompare(b.subject));
292
+ // Codepoint order, never localeCompare — inlined, not a module-level
293
+ // helper, because this function is spliced verbatim into the page's own
294
+ // inline script and can carry no outer-scope reference with it.
295
+ placements.sort((a, b) => (a.subject < b.subject ? -1 : a.subject > b.subject ? 1 : 0));
290
296
  return placements;
291
297
  }
292
298