@polycode-projects/the-mechanical-code-talker 1.10.12 → 1.10.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "1.10.12",
3
+ "version": "1.10.13",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; no codebase index of its own.",
package/src/chat.mjs CHANGED
@@ -2354,10 +2354,10 @@ async function generalVerbTeach(payload) {
2354
2354
  if (verb === "cannot") return null;
2355
2355
  if (GENERAL_VERB_DETERMINER_RE.test(subjectRaw)) return null; // not a bare-name subject
2356
2356
  const subject = subjectRaw.trim();
2357
- const object = objectRaw.replace(/^an?\s+/i, "").trim();
2357
+ const folded = foldPrepositionIntoPredicate(await generalVerbPredicate(verb), objectRaw);
2358
+ const object = folded.object.replace(/^an?\s+/i, "").trim();
2358
2359
  if (!subject || !object) return null; // no well-formed triple — honest decline (point 6)
2359
- const predicate = await generalVerbPredicate(verb);
2360
- return { subject, predicate, object };
2360
+ return { subject, predicate: folded.predicate, object };
2361
2361
  }
2362
2362
 
2363
2363
  /** Is `word` a genuine NOUN/PROPN, per wink-nlp's optional POS tagger
@@ -2391,7 +2391,7 @@ async function subjectIsNounOrPropn(word) {
2391
2391
  // query is never shadowed. Reuses generalVerbTeach's own exclude guards and
2392
2392
  // generalVerbPredicate, plus the SAME adverb-skip, so the two never disagree. ----
2393
2393
  const GENERAL_VERB_YESNO_RE = new RegExp(`^(?:does|did)\\s+([\\w'-]+)\\s+${TEACH_ADVERB_SKIP_SRC}([a-z]+)\\s+(.+?)[?.!\\s]*$`, "i");
2394
- const GENERAL_VERB_OPEN_RE = new RegExp(`^what\\s+(?:does|did)\\s+([\\w'-]+)\\s+${TEACH_ADVERB_SKIP_SRC}([a-z]+)[?.!\\s]*$`, "i");
2394
+ const GENERAL_VERB_OPEN_RE = new RegExp(`^what\\s+(?:does|did)\\s+([\\w'-]+)\\s+${TEACH_ADVERB_SKIP_SRC}([a-z]+(?:\\s+(?:on|in|at|onto|upon|under|over|beside|near|behind|above|below|inside|outside))?)[?.!\\s]*$`, "i");
2395
2395
  /** GENERAL_VERB_EXCLUDE_RE was written for generalVerbTeach's fully-conjugated
2396
2396
  * declarative verb ("X OWNS Y", "X MAINTAINS Y") — but "does/did X <verb> Y"
2397
2397
  * captures the BARE INFINITIVE after do-support ("does X OWN Y", never "does X
@@ -2403,6 +2403,23 @@ const GENERAL_VERB_OPEN_RE = new RegExp(`^what\\s+(?:does|did)\\s+([\\w'-]+)\\s+
2403
2403
  * The query-side guard needs the bare-infinitive counterpart too. */
2404
2404
  const GENERAL_VERB_QUERY_EXCLUDE_RE = /^(?:be|own|maintain)$/i;
2405
2405
 
2406
+ /** Closed prepositions the general-verb teach/query lanes FOLD INTO the
2407
+ * minted predicate: "disk-1 rests on peg-a" stores mgx:rest-on with object
2408
+ * "peg-a", never mgx:rest with the meaning-bearing "on" buried inside the
2409
+ * object where no read-back can match it. */
2410
+ const GENERAL_VERB_PREP_RE = /^(on|in|at|onto|upon|under|over|beside|near|behind|above|below|inside|outside)\s+(.+)$/i;
2411
+ /** Fold a leading preposition from `objectRaw` into a minted mgx:<lemma>
2412
+ * predicate. Curated predicates (mgx:hasA, mgx:capableOf — anything not the
2413
+ * plain lowercase mint shape) are never suffixed. Returns {predicate,
2414
+ * object} either way. */
2415
+ function foldPrepositionIntoPredicate(predicate, objectRaw) {
2416
+ const prepM = String(objectRaw || "").match(GENERAL_VERB_PREP_RE);
2417
+ if (prepM && /^mgx:[a-z]+$/.test(predicate)) {
2418
+ return { predicate: `${predicate}-${prepM[1].toLowerCase()}`, object: prepM[2].trim() };
2419
+ }
2420
+ return { predicate, object: String(objectRaw || "").trim() };
2421
+ }
2422
+
2406
2423
  /** Sentence forms to try asserting for a teach payload: the payload as-is, and
2407
2424
  * (if it carries no determiner) its "every …" universal — the ACE-OWL shape the
2408
2425
  * grammar actually lands. */
@@ -3729,8 +3746,10 @@ function thirdPersonSingularSurface(lemma) {
3729
3746
  }
3730
3747
  function predicatePhrase(predicate) {
3731
3748
  if (FACT_PREDICATE_PHRASES[predicate]) return FACT_PREDICATE_PHRASES[predicate];
3732
- const m = /^mgx:([a-z]+)$/i.exec(String(predicate || ""));
3733
- return m ? thirdPersonSingularSurface(m[1]) : predicate;
3749
+ const m = /^mgx:([a-z]+)(?:-([a-z]+))?$/i.exec(String(predicate || ""));
3750
+ if (!m) return predicate;
3751
+ // a folded preposition renders back naturally: mgx:rest-on -> "rests on"
3752
+ return `${thirdPersonSingularSurface(m[1])}${m[2] ? ` ${m[2]}` : ""}`;
3734
3753
  }
3735
3754
  const factPhrase = (f) => `${f.subject} ${predicatePhrase(f.predicate)} ${f.object}`;
3736
3755
 
@@ -6054,9 +6073,13 @@ async function factReadBack(memoryDir, query, envelope, miss, graph = null, focu
6054
6073
  const verb = verbRaw.toLowerCase();
6055
6074
  if (!GENERAL_VERB_EXCLUDE_RE.test(verb) && !GENERAL_VERB_QUERY_EXCLUDE_RE.test(verb)) {
6056
6075
  const subject = subjectRaw.trim();
6057
- const object = objectRaw.replace(/^an?\s+/i, "").trim();
6076
+ // the SAME preposition fold the teach side applies, so "does disk-1
6077
+ // rest on peg-a" looks up mgx:rest-on/"peg-a", matching what
6078
+ // generalVerbTeach actually stored
6079
+ const folded = foldPrepositionIntoPredicate(await generalVerbPredicate(verb), objectRaw);
6080
+ const object = folded.object.replace(/^an?\s+/i, "").trim();
6058
6081
  if (subject && object) {
6059
- const predicate = await generalVerbPredicate(verb);
6082
+ const predicate = folded.predicate;
6060
6083
  const subjVariants = factTermVariants(normFactTerm, subject);
6061
6084
  const objVariants = factTermVariants(normFactTerm, object);
6062
6085
  const hit = rows
@@ -6070,11 +6093,15 @@ async function factReadBack(memoryDir, query, envelope, miss, graph = null, focu
6070
6093
  const genOpen = q.match(GENERAL_VERB_OPEN_RE);
6071
6094
  if (genOpen && !GENERAL_VERB_ANYWHERE_EXCLUDE_RE.test(q)) {
6072
6095
  const [, subjectRaw, verbRaw] = genOpen;
6073
- const verb = verbRaw.toLowerCase();
6096
+ // "what does disk-1 rest on" captures "rest on" — split the folded
6097
+ // preposition back off and suffix the minted predicate, mirroring the
6098
+ // teach side's foldPrepositionIntoPredicate
6099
+ const [verb, verbPrep] = verbRaw.toLowerCase().split(/\s+/);
6074
6100
  if (!GENERAL_VERB_EXCLUDE_RE.test(verb) && !GENERAL_VERB_QUERY_EXCLUDE_RE.test(verb)) {
6075
6101
  const subject = subjectRaw.trim();
6076
6102
  if (subject) {
6077
- const predicate = await generalVerbPredicate(verb);
6103
+ let predicate = await generalVerbPredicate(verb);
6104
+ if (verbPrep && /^mgx:[a-z]+$/.test(predicate)) predicate = `${predicate}-${verbPrep}`;
6078
6105
  const subjVariants = factTermVariants(normFactTerm, subject);
6079
6106
  const hits = rankByBiasThenTrust(rows.filter((f) => f.predicate === predicate && subjVariants.has(f.subject)), biasByBundle);
6080
6107
  if (hits.length) return { ...renderMany(hits), generalVerbQuery: true };
@@ -6082,6 +6109,26 @@ async function factReadBack(memoryDir, query, envelope, miss, graph = null, focu
6082
6109
  }
6083
6110
  }
6084
6111
 
6112
+ // "what rests on peg-a" / "who sits on the chair" — the reverse-by-OBJECT
6113
+ // mirror of the two general-verb readers above, for taught prepositional
6114
+ // facts. Only ever diverts on a REAL stored hit (the predicate is minted
6115
+ // from the surface verb + folded preposition, so a code question like
6116
+ // "what calls chat.mjs" — no such taught fact — falls through untouched;
6117
+ // this also outranks the spell-corrector's "rests"→"tests" misread, which
6118
+ // otherwise walls this exact phrasing).
6119
+ const genReverse = q.match(/^(?:what|who)\s+([a-z]+)\s+(on|in|at|onto|upon|under|over|beside|near|behind|above|below|inside|outside)\s+(.+?)[?.!\s]*$/i);
6120
+ if (genReverse && !GENERAL_VERB_ANYWHERE_EXCLUDE_RE.test(q)) {
6121
+ const [, verbSurface, prep, objectRaw] = genReverse;
6122
+ const verb = verbSurface.toLowerCase();
6123
+ if (!GENERAL_VERB_EXCLUDE_RE.test(verb) && !GENERAL_VERB_QUERY_EXCLUDE_RE.test(verb) && !GENERAL_VERB_NOT_A_VERB_RE.test(verb)) {
6124
+ let predicate = await generalVerbPredicate(verb);
6125
+ if (/^mgx:[a-z]+$/.test(predicate)) predicate = `${predicate}-${prep.toLowerCase()}`;
6126
+ const objVariants = factTermVariants(normFactTerm, objectRaw.replace(/^(?:an?|the)\s+/i, "").trim());
6127
+ const hits = rankByBiasThenTrust(rows.filter((f) => f.predicate === predicate && objVariants.has(f.object)), biasByBundle);
6128
+ if (hits.length) return { ...renderMany(hits), generalVerbQuery: true };
6129
+ }
6130
+ }
6131
+
6085
6132
  // (b) RECALL — "what did i tell you about X": every remembered fact mentioning X.
6086
6133
  const told = q.match(TOLD_ABOUT_RE);
6087
6134
  if (told) {
package/src/finish.mjs CHANGED
@@ -230,14 +230,18 @@ function ruleArticle(segments, rule) {
230
230
  for (let i = 0; i < out.length; i += 1) {
231
231
  const seg = out[i];
232
232
  if (seg.type !== "prose") continue;
233
- // (a) in-span "a artifact" / "an module" (case-insensitive; case preserved)
234
- seg.text = seg.text.replace(/\b(a|an)(\s+)([A-Za-z][\w-]*)/gi, (m, art, sp, word) => {
233
+ // (a) in-span "a artifact" / "an module" (case-insensitive; case preserved).
234
+ // A hyphen before the candidate article means it's the tail of a
235
+ // hyphenated NAME ("peg-a", "option-a"), not an article — rewriting it
236
+ // corrupts user-quoted data ("peg-a\nUtterance" read as "a Utterance").
237
+ seg.text = seg.text.replace(/(^|[^\w-])(a|an)(\s+)([A-Za-z][\w-]*)/gi, (m, pre, art, sp, word) => {
235
238
  const vowel = beginsWithVowelSound(word, rule);
236
239
  if (vowel === null) return m;
237
- return matchCase(art, vowel ? "an" : "a") + sp + word;
240
+ return pre + matchCase(art, vowel ? "an" : "a") + sp + word;
238
241
  });
239
242
  // (b) boundary: prose ends "…a " / "…an ", next span carries the word
240
- const bm = seg.text.match(/(^|[^\w])(a|an)(\s+)$/i);
243
+ // (same hyphenated-name guard as (a))
244
+ const bm = seg.text.match(/(^|[^\w-])(a|an)(\s+)$/i);
241
245
  if (bm) {
242
246
  const next = out[i + 1];
243
247
  const word = next && typeof next.text === "string" ? leadingWord(next.text) : "";