@polycode-projects/the-mechanical-code-talker 2.10.5 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +2 -2
  2. package/corpus/sprites/src/sprite-facts.jsonl +18 -0
  3. package/corpus/worlds/manifest.json +5 -5
  4. package/corpus/worlds/shards/ashcombe-hall.jsonl.gz +0 -0
  5. package/corpus/worlds/src/ashcombe-hall.jsonl +27 -0
  6. package/data/sprites/book-icon.toml +12 -0
  7. package/data/sprites/cellar-icon.toml +12 -0
  8. package/data/sprites/drawing-room-icon.toml +13 -0
  9. package/data/sprites/garden-icon.toml +12 -0
  10. package/data/sprites/kitchen-icon.toml +13 -0
  11. package/data/sprites/library-icon.toml +12 -0
  12. package/data/sprites/pan-icon.toml +11 -0
  13. package/data/sprites/study-icon.toml +12 -0
  14. package/data/templates/responses.jsonl +1 -0
  15. package/package.json +5 -2
  16. package/src/adapters/corpus/wikipedia-live.mjs +182 -26
  17. package/src/adapters/corpus/worlds-pack.mjs +8 -2
  18. package/src/adapters/toml-config.mjs +6 -0
  19. package/src/domain/ask-vocab.mjs +17 -0
  20. package/src/domain/ask.mjs +51 -1
  21. package/src/domain/grammar/ace.mjs +43 -3
  22. package/src/domain/interpret/normalize.mjs +6 -2
  23. package/src/domain/interpret/strategies/grammar.mjs +47 -17
  24. package/src/domain/interpret/strategies/keywords.mjs +53 -4
  25. package/src/domain/memory/trust.mjs +11 -0
  26. package/src/domain/router/registry.mjs +8 -1
  27. package/src/domain/worlds-pack.mjs +50 -0
  28. package/src/services/adventure-autoplay.mjs +5 -2
  29. package/src/services/adventure-viz.mjs +301 -33
  30. package/src/services/adventure.mjs +162 -14
  31. package/src/services/chat-page-viz.mjs +265 -189
  32. package/src/services/chat-session.mjs +15 -5
  33. package/src/services/chat.mjs +471 -79
  34. package/src/services/code-explorer-viz.mjs +183 -75
  35. package/src/services/extract-facts.mjs +118 -28
  36. package/src/services/ingest-viz.mjs +328 -79
  37. package/src/services/ledger-viz.mjs +99 -0
  38. package/src/services/memory-panel-viz.mjs +159 -0
  39. package/src/services/research.mjs +266 -0
  40. package/src/services/sentences.mjs +19 -0
  41. package/src/services/spider-fly-viz.mjs +21 -5
  42. package/src/surfaces/web/adventure-browser-entry.mjs +9 -5
  43. package/src/surfaces/web/chat-browser-entry.mjs +28 -11
  44. package/src/surfaces/web/code-explorer-browser-entry.mjs +27 -11
  45. package/src/surfaces/web/ingest-browser-entry.mjs +123 -41
  46. package/src/surfaces/web/ledger-browser-entry.mjs +10 -4
  47. package/src/surfaces/web/memory-ask-browser.bundle.js +116 -116
  48. package/src/surfaces/web/memory-stats.mjs +53 -0
@@ -8,7 +8,7 @@
8
8
  import {
9
9
  VERB_TO_KIND, ENTITY_TO_TYPE, MODIFIER_TO_KIND,
10
10
  WHERE_MARKERS, MENTION_MARKERS, PLACEHOLDER_NOUNS, PASSIVE_PARTICIPLE_TO_KIND,
11
- INHERITS_REVERSE_VERBS,
11
+ INHERITS_REVERSE_VERBS, HAS_FAMILY_VERBS,
12
12
  } from "../../ask-vocab.mjs";
13
13
  import { STOPWORDS } from "../normalize.mjs";
14
14
  import { VOCAB_WORDS, eligibleForCanon, fuzzyVocabWord } from "../fuzzy.mjs";
@@ -19,6 +19,10 @@ import { VOCAB_WORDS, eligibleForCanon, fuzzyVocabWord } from "../fuzzy.mjs";
19
19
  const PASSIVE_AUX = new Set(["is", "are", "was", "were", "be", "been", "being", "get", "gets", "got"]);
20
20
  const WH_WORDS = new Set(["which", "what", "who", "whom", "whose"]);
21
21
  const PLACEHOLDER_SET = new Set(PLACEHOLDER_NOUNS.map((w) => w.toLowerCase()));
22
+ // See ask-vocab.mjs's own HAS_FAMILY_VERBS for why a bare have-family verb
23
+ // never resolves to `defines` in this strategy's two-named-role "ask" shape
24
+ // below (the forward/reverse branches keep their own tested grain-check
25
+ // decline, per that constant's own docblock).
22
26
 
23
27
  /** Find the longest phrase from `table`'s keys that appears as a contiguous
24
28
  * run of `words` (case already lowercased by the caller). Longest-match-first
@@ -107,6 +111,13 @@ export function parseKeywordSpot(text, nlp = null) {
107
111
  fuzzyVerb = { from: lcWords[at], to: fuzzyWords[at] };
108
112
  }
109
113
  }
114
+ // Tracks whether verbHit came from PASSIVE_PARTICIPLE_TO_KIND's fallback
115
+ // rather than an active VERB_TO_KIND entry — that table's own header says
116
+ // it's only meant to fire once a passive auxiliary AND an agent-marking
117
+ // "by" are confirmed. A direct complement with no "by" ("my cat is called
118
+ // whiskers") is the naming sense of "call", not the invoke-relation passive,
119
+ // so this flag gates the ask-shape SVO fallback below from misreading it.
120
+ let verbFromParticiple = false;
110
121
  if (!verbHit) {
111
122
  // A participle with no active verb entry still marks a passive when a passive
112
123
  // auxiliary precedes it — with or without an agent "by" phrase. "is http.mjs
@@ -125,10 +136,28 @@ export function parseKeywordSpot(text, nlp = null) {
125
136
  for (let i = 0; i < lcWords.length; i += 1) {
126
137
  const k = PASSIVE_PARTICIPLE_TO_KIND[lcWords[i]];
127
138
  if (k && lcWords[i] === "used" && lcWords[i + 1] === "for") continue;
128
- if (k && lcWords.slice(0, i).some((w) => PASSIVE_AUX.has(w))) { verbHit = { kind: k, start: i, end: i + 1 }; break; }
139
+ if (k && lcWords.slice(0, i).some((w) => PASSIVE_AUX.has(w))) {
140
+ verbHit = { kind: k, start: i, end: i + 1 };
141
+ verbFromParticiple = true;
142
+ break;
143
+ }
129
144
  }
130
145
  }
131
146
  if (!verbHit) return null;
147
+ // A single-word verbHit whose LITERAL surface text (not the lemma the tier-2
148
+ // pass may have rewritten it to) is itself a PASSIVE_PARTICIPLE_TO_KIND entry,
149
+ // preceded by a passive auxiliary, is the same "needs a confirmed 'by' agent"
150
+ // case the fallback loop above already flags — checked here too because the
151
+ // lemma tier reaches it independently: lemma("called") is "call", an ACTIVE
152
+ // verb-table entry in its own right, so tier 2 resolves verbHit before the
153
+ // fallback loop ever runs, silently losing the participle reading. "my cat is
154
+ // called whiskers" (the naming sense of "call") must not be read as though
155
+ // "cat" were the active subject invoking "whiskers".
156
+ if (!verbFromParticiple && verbHit.end - verbHit.start === 1
157
+ && PASSIVE_PARTICIPLE_TO_KIND[lcWords[verbHit.start]]
158
+ && lcWords.slice(0, verbHit.start).some((w) => PASSIVE_AUX.has(w))) {
159
+ verbFromParticiple = true;
160
+ }
132
161
  // A tier-3 verb is a REPAIR, not a reading — downstream consumers (the teach
133
162
  // lane's canonical receipt, and the chat surface's fuzzy-verb decline) need
134
163
  // to know the difference AND which word was rewritten, so {from, to} rides
@@ -221,15 +250,35 @@ export function parseKeywordSpot(text, nlp = null) {
221
250
  const [patient, agent] = agentIsFronted
222
251
  ? [roleText(passiveAuxIdx + 1, words.length), roleText(byIdx + 1, passiveAuxIdx)]
223
252
  : [roleText(0, byIdx), roleText(byIdx + 1, words.length)];
224
- if (patient && agent) return stamp({ shape: "ask", entityType: null, modifier: "direct", kind, subject: agent, object: patient });
253
+ if (patient && agent) {
254
+ if (kind === "defines" && HAS_FAMILY_VERBS.has(canonWords.slice(verbHit.start, verbHit.end).join(" "))) return null;
255
+ return stamp({ shape: "ask", entityType: null, modifier: "direct", kind, subject: agent, object: patient });
256
+ }
225
257
  if (agent) return stamp({ shape: "forward", entityType, modifier, kind, object: agent });
226
258
  if (patient) return stamp({ shape: "reverse", entityType, modifier, kind, object: patient });
227
259
  }
228
260
 
261
+ // "called" specifically (never the rest of the participle family — "was it
262
+ // touched recently"/"is X used anywhere" are genuine bare-passive code
263
+ // queries with a trailing adverb, not a competing sense, and must keep
264
+ // reaching their existing reading) carries a whole separate NAMING sense
265
+ // ("my cat is called whiskers") distinct from the invoke-relation passive
266
+ // this table exists for. A following complement with no "by" agent is that
267
+ // naming sense, not "whiskers calls cat" — falling through to the
268
+ // active-SVO/reverse branches below would read the participle as if it
269
+ // were an active verb and answer a code-graph question nobody asked, so
270
+ // this misses honestly instead.
271
+ if (verbFromParticiple && byIdx < 0 && afterText && lcWords[verbHit.start] === "called") return null;
272
+
229
273
  if (beforeText && afterText) {
274
+ const verbPhrase = canonWords.slice(verbHit.start, verbHit.end).join(" ");
275
+ // The bare have-family "ask" shape ("does X have Y") declines here, same
276
+ // reasoning as grammar.mjs's T1 (see HAS_FAMILY_VERBS above) — never for
277
+ // the entityType-driven forward/reverse branches below, which keep their
278
+ // own tested grain-check decline.
279
+ if (kind === "defines" && HAS_FAMILY_VERBS.has(verbPhrase)) return null;
230
280
  // A semantically-reverse verb ("superclass of") swaps subject/object, same as
231
281
  // grammar.mjs's T1.
232
- const verbPhrase = canonWords.slice(verbHit.start, verbHit.end).join(" ");
233
282
  let subject = beforeText;
234
283
  let object = afterText;
235
284
  if (INHERITS_REVERSE_VERBS.includes(verbPhrase)) [subject, object] = [object, subject];
@@ -77,6 +77,17 @@ export function provenanceTagToSource(tag) {
77
77
  const pack = rest.slice(0, colon) || "unknown";
78
78
  return { kind: referenceKindFor(pack), pack, article: rest.slice(colon + 1) };
79
79
  }
80
+ // research:<topic>@<depth> — the research lane's Simple English Wikipedia
81
+ // loads. Live-fetched at query time like the wikipedia-live pack, so it
82
+ // scores at the same referenceLive prior, below every curated pack. Parsed
83
+ // from the FULL tag (a topic may contain spaces); the depth segment records
84
+ // how far the fan-out reached and is not part of the Source identity.
85
+ if (t.startsWith("research:")) {
86
+ const rest = t.slice("research:".length);
87
+ const at = rest.lastIndexOf("@");
88
+ const topic = (at >= 0 ? rest.slice(0, at) : rest).trim();
89
+ return { kind: "referenceLive", pack: "research", article: topic || "unknown" };
90
+ }
80
91
  const head = t.split(/\s+/)[0]; // drop trailing " /r/IsA" etc.
81
92
  if (head.startsWith("corpus-weak:")) return { kind: "corpusWeak", name: head.slice("corpus-weak:".length) || "unknown" };
82
93
  if (head.startsWith("corpus:")) return { kind: "corpus", name: head.slice("corpus:".length) || "unknown" };
@@ -242,11 +242,18 @@ export function registerCapability(cap) {
242
242
  }
243
243
 
244
244
  // ---- unregistered dispatch tools ---------------------------------------------
245
- // Dispatch tools not yet registered; each names the precondition work it needs first.
245
+ // Dispatch tools not yet registered; each names the work it needs first — a
246
+ // precondition/effect design, or resolver wiring, before it can join the registry.
246
247
  export const EXCLUDED_FROM_REGISTRY = Object.freeze({
247
248
  tmct_context: "unbounded edit-context bundle (multi-file); needs a size/budget precondition",
248
249
  tmct_context_more: "unbounded context continuation; same as tmct_context",
249
250
  tmct_snippet: "raw source-file read (reads the filesystem); needs a file-read + span precondition",
251
+ tmct_ask: "the plain-English question entry point itself — calls ask.mjs directly and bypasses the capability planner; not one of the planner's operators",
252
+ tmct_export: "reads the memory store's whole fact set with no discriminating param and no resolver goal frame; needs NL-reachability wiring before it can join",
253
+ tmct_ingest: "writes into the memory store (grounds facts); capability() only models read-only operators, so a write path needs its own precondition/effect design first",
254
+ tmct_file_history: "a module-grain cut of the same history edge tmct_history already models; no NL frame distinguishes the granularities yet",
255
+ tmct_method_history: "a method-grain cut of the same history edge tmct_history already models; no NL frame distinguishes the granularities yet",
256
+ tmct_class_history: "a class-grain cut of the same history edge tmct_history already models; no NL frame distinguishes the granularities yet",
250
257
  });
251
258
 
252
259
  /** The full registry as a plain frozen object (facts + index), for callers that
@@ -69,3 +69,53 @@ export function isWorldRow(row) {
69
69
  export function worldProvenanceTag(worldName) {
70
70
  return `world:${worldName}`;
71
71
  }
72
+
73
+ const DEFAULT_CONTAINS_PREDICATE = "mgx:default-contains";
74
+
75
+ /** Materialize a world's class-default contents: for every
76
+ * `<room> mgx:default-contains <class>` fact, mint one placed, portable
77
+ * instance of that class in that room unless the room already holds one. A
78
+ * minted book gets `rdf:type <class>` (so it resolves its own sprite),
79
+ * `rdf:type portable` (so the take family accepts it), and
80
+ * `mgx:located-in <room>` (so it shows up in the room and can be carried
81
+ * out). Pure and deterministic: defaults are visited in sorted order, and a
82
+ * collision mints `<class>-2`, `<class>-3`… so re-running never renames an
83
+ * earlier instance. Rows keep whatever world/kind wrapper the input rows
84
+ * carry, so both a loaded shard (world rows) and a bare triple list expand
85
+ * the same way. */
86
+ export function expandWorldDefaultContents(facts) {
87
+ const rows = facts || [];
88
+ const defaults = rows
89
+ .filter((r) => r.predicate === DEFAULT_CONTAINS_PREDICATE)
90
+ .sort((a, b) => `${a.subject}\0${a.object}`.localeCompare(`${b.subject}\0${b.object}`));
91
+ if (!defaults.length) return rows;
92
+
93
+ const instanceIds = new Set(rows.filter((r) => r.predicate === "rdf:type").map((r) => r.subject));
94
+ const placedIn = new Map();
95
+ for (const r of rows) {
96
+ if (r.predicate === "mgx:located-in") placedIn.set(r.subject, r.object);
97
+ }
98
+ const roomAlreadyHas = (klass, room) =>
99
+ rows.some((r) => r.predicate === "rdf:type" && r.object === klass && placedIn.get(r.subject) === room);
100
+
101
+ const wrapper = rows.find((r) => typeof r.world === "string" && r.kind === "fact");
102
+ const wrap = (subject, predicate, object) => ({
103
+ ...(wrapper ? { world: wrapper.world, kind: "fact" } : {}),
104
+ subject, predicate, object,
105
+ });
106
+
107
+ const minted = [];
108
+ for (const d of defaults) {
109
+ const room = d.subject;
110
+ const klass = d.object;
111
+ if (roomAlreadyHas(klass, room)) continue;
112
+ let id = klass;
113
+ let n = 1;
114
+ while (instanceIds.has(id)) { n += 1; id = `${klass}-${n}`; }
115
+ instanceIds.add(id);
116
+ minted.push(wrap(id, "rdf:type", klass));
117
+ minted.push(wrap(id, "rdf:type", "portable"));
118
+ minted.push(wrap(id, "mgx:located-in", room));
119
+ }
120
+ return minted.length ? [...rows, ...minted] : rows;
121
+ }
@@ -26,7 +26,7 @@
26
26
  // fact simply has nothing for this module to infer a goal toward.
27
27
  import { findActionPath } from "../domain/planning.mjs";
28
28
  import { loadMemory, readFactRows } from "../adapters/memory/core.mjs";
29
- import { foldWorldState, adventureTurn } from "./adventure.mjs";
29
+ import { foldWorldState, adventureTurn, worldActionRows } from "./adventure.mjs";
30
30
 
31
31
  const SNAPSHOT_RE = /^(.+)@turn(\d+)$/;
32
32
  const baseSubjectOf = (subject) => SNAPSHOT_RE.exec(subject)?.[1] ?? subject;
@@ -138,7 +138,10 @@ async function stepTowardThenAct({
138
138
  */
139
139
  export async function runAdventureAutoplayTick(memoryDir, opts = {}) {
140
140
  const { exposedRoomIds, planHolder, sessionId = "", env = {}, graph = null, cache = null } = opts;
141
- const rows = readFactRows(await loadMemory(memoryDir));
141
+ // Auto-play reasons over the game world only — the same isolation the manual
142
+ // command path uses, so a fact the player taught mid-game never steers the
143
+ // planner toward a room the world never placed anything in.
144
+ const rows = worldActionRows(readFactRows(await loadMemory(memoryDir)));
142
145
  const state = foldWorldState(rows);
143
146
  const here = state.placements.get("player")?.object ?? null;
144
147
  const exposed = new Set(exposedRoomIds || []);