@polycode-projects/the-mechanical-code-talker 2.11.4 → 2.11.6

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": "2.11.4",
3
+ "version": "2.11.6",
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.",
@@ -6737,9 +6737,27 @@ const RELATION_WHO_ASK_RE =
6737
6737
  * ([\w-]+) so the 's split is unambiguous. */
6738
6738
  const GENITIVE_WHO_ASK_RE =
6739
6739
  /^(?:who|what)\s+(?:is|are|was|were)\s+([\w-]+(?:\s+[A-Z][\w-]*)?)'s\s+([a-z][\w-]*)[?.!\s]*$/i;
6740
- function matchGenitiveWhoAsk(q) {
6741
- const g = String(q).match(GENITIVE_WHO_ASK_RE);
6742
- return g ? [g[0], g[2], g[1]] : null;
6740
+ /** The fact lane reads the raw surface, so the ask path's contraction table
6741
+ * never reaches these readers — expand just the interrogative lead
6742
+ * ("who's"/"whos"/"what's"/"whats") so both relation-ask surfaces accept it.
6743
+ * "whose" never matches (the trailing "e" fails the boundary). */
6744
+ const WHO_WHAT_LEAD_CONTRACTION_RE = /^(who|what)'?s\s+/i;
6745
+ const expandWhoWhatLead = (q) => String(q).replace(WHO_WHAT_LEAD_CONTRACTION_RE, (full, w) => `${w} is `);
6746
+ /** The apostrophe-less genitive ("who is petes father") — accepted ONLY when
6747
+ * the stripped possessor is already a term some stored fact names, so a
6748
+ * plain plural or a pronoun ("who is his father") can never be mis-split
6749
+ * into a claimed-then-missed relation ask; this reader's own miss text is
6750
+ * definitive, never a fall-through, so the gate must sit at match time. */
6751
+ const GENITIVE_WHO_ASK_BARE_RE =
6752
+ /^(?:who|what)\s+(?:is|are|was|were)\s+([\w-]{2,}?)s\s+([a-z][\w-]*)[?.!\s]*$/i;
6753
+ function matchGenitiveWhoAsk(q, isKnownFactTerm = null) {
6754
+ const expanded = expandWhoWhatLead(q);
6755
+ const g = expanded.match(GENITIVE_WHO_ASK_RE);
6756
+ if (g) return [g[0], g[2], g[1]];
6757
+ if (!isKnownFactTerm) return null;
6758
+ const b = expanded.match(GENITIVE_WHO_ASK_BARE_RE);
6759
+ if (!b) return null;
6760
+ return isKnownFactTerm(b[1]) ? [b[0], b[2], b[1]] : null;
6743
6761
  }
6744
6762
 
6745
6763
  /** "list the descendants of ahab" — the REACHABILITY-SET list query: a
@@ -6886,6 +6904,7 @@ const WHAT_IS_PREP_FACT_RE = new RegExp(`^what(?:'s|\\s+is|\\s+are)\\s+(${PREP_S
6886
6904
  // the named kind via a direct isa-family fact.
6887
6905
  const DO_VERB_ASK_RE = /^(?:do|does)\s+(all\s+|every\s+)?(?:an?\s+|the\s+)?([\w'-]+(?:\s+[\w'-]+)*?)\s+([a-z-]+)[?.!\s]*$/i;
6888
6906
  const WHAT_CAN_VERB_RE = /^what\s+can\s+(?!be\s)(.+?)[?.!\s]*$/i;
6907
+ const WHAT_CANNOT_VERB_RE = /^what\s+(?:cannot|can't|cant|can\s+not)\s+(.+?)[?.!\s]*$/i;
6889
6908
  const WHICH_KIND_CAN_RE = /^(?:which|what)\s+([\w'-]+(?:\s+[\w'-]+)*?)\s+can\s+(.+?)[?.!\s]*$/i;
6890
6909
 
6891
6910
  /** The negative surface of a yes/no question asks the SAME question as its
@@ -6906,13 +6925,22 @@ const WHICH_KIND_CAN_RE = /^(?:which|what)\s+([\w'-]+(?:\s+[\w'-]+)*?)\s+can\s+(
6906
6925
  * trying the raw question first would take a garbage bind over the good one. */
6907
6926
  function positiveQuestionSurface(q) {
6908
6927
  const s = String(q || "")
6909
- .replace(/^(?:can't|cannot|can not)\s+/i, "can ")
6928
+ .replace(/^(?:can't|cannot|can not|cant)\s+/i, "can ")
6910
6929
  .replace(/^(?:doesn't|does not)\s+/i, "does ")
6911
6930
  .replace(/^(?:don't|do not)\s+/i, "do ")
6912
6931
  .replace(/^(?:didn't|did not)\s+/i, "did ")
6913
6932
  .replace(/\s+(?:not|never)\s+/i, " ");
6914
6933
  return s.replace(/\s+/g, " ").trim();
6915
6934
  }
6935
+ /** A negated surface ("can a dog not bark") is answered by the positive
6936
+ * reader (see positiveQuestionSurface's docblock), but a bare yes/no lead
6937
+ * then reads as agreeing with the asked polarity — drop the lead and let
6938
+ * the cited fact carry the real polarity on its own. */
6939
+ function withoutPolarityLead(reply) {
6940
+ const text = String(reply.text || "").replace(/^(?:yes|no) — /i, "");
6941
+ return text === reply.text ? reply : { ...reply, text };
6942
+ }
6943
+ const collapsedSurface = (q) => String(q || "").replace(/\s+/g, " ").trim();
6916
6944
 
6917
6945
  /** Cite an isa chain the way (b3b) already cites one — each step as its own
6918
6946
  * phrase plus verbatim source. Shared so the inherited-capability answers and
@@ -7184,7 +7212,7 @@ function withDeducedGoal(res, envelope, query) {
7184
7212
  if (!goal && res.generalVerbQuery) goal = TAUGHT_FACT_LOOKUP_GOAL;
7185
7213
  if (!goal) {
7186
7214
  const yesNo = q.match(RELATION_FACT_YESNO_RE);
7187
- const whoAsk = yesNo ? null : (q.match(RELATION_WHO_ASK_RE) || matchGenitiveWhoAsk(q));
7215
+ const whoAsk = yesNo ? null : (expandWhoWhatLead(q).match(RELATION_WHO_ASK_RE) || matchGenitiveWhoAsk(q));
7188
7216
  const role = yesNo ? yesNo[2] : whoAsk ? whoAsk[1] : null;
7189
7217
  if (role && !ISA_IDIOM_ROLE_WORDS.has(role.toLowerCase())) goal = TAUGHT_FACT_LOOKUP_GOAL;
7190
7218
  }
@@ -7589,11 +7617,13 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
7589
7617
  // capabilityReply). Mirrors the ISA_ASK_RE block just above on the "never a
7590
7618
  // guessed no" discipline: a "no" here is a REMEMBERED negative, never the
7591
7619
  // absence of a positive.
7592
- const can = positiveQuestionSurface(q).match(CAN_ASK_RE);
7620
+ const surfacedCan = positiveQuestionSurface(q);
7621
+ const can = surfacedCan.match(CAN_ASK_RE);
7593
7622
  if (can) {
7594
7623
  const facts = await factRows(memoryDir, cache);
7595
7624
  const canUniversal = can[1];
7596
- const reply = capabilityReply(can[2], can[3], facts);
7625
+ let reply = capabilityReply(can[2], can[3], facts);
7626
+ if (reply && surfacedCan !== collapsedSurface(q)) reply = withoutPolarityLead(reply);
7597
7627
  // Quantified ("can all/every X ..."): the stored facts are generic, and a
7598
7628
  // bare "yes" would claim universality the memory can't support — the same
7599
7629
  // hedge the do-support surface applies, echoing the quantifier as typed.
@@ -7693,7 +7723,8 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
7693
7723
  // (falls through instead): the shape is looser than (b2)'s, so a do-lead
7694
7724
  // question some later reader owns must keep its turn. The can't-confirm
7695
7725
  // branch is additionally miss-gated for the same reason.
7696
- const doAsk = positiveQuestionSurface(q).match(DO_VERB_ASK_RE);
7726
+ const surfacedDo = positiveQuestionSurface(q);
7727
+ const doAsk = surfacedDo.match(DO_VERB_ASK_RE);
7697
7728
  if (doAsk) {
7698
7729
  const facts = await factRows(memoryDir, cache);
7699
7730
  const universal = !!doAsk[1];
@@ -7701,7 +7732,8 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
7701
7732
  const obj = factTermVariants(normFactTerm, doAsk[3]);
7702
7733
  // the SAME resolver (b2) answers through, so "do penguins fly" and "can a
7703
7734
  // penguin fly" can never disagree in one session
7704
- const reply = capabilityReply(doAsk[2], doAsk[3], facts);
7735
+ let reply = capabilityReply(doAsk[2], doAsk[3], facts);
7736
+ if (reply && surfacedDo !== collapsedSurface(q)) reply = withoutPolarityLead(reply);
7705
7737
  if (reply && universal) {
7706
7738
  // Echo the quantifier as typed ("every dog", "all dogs") — "all dog"
7707
7739
  // for a singular every-question is a garbled echo.
@@ -7820,6 +7852,28 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
7820
7852
  }
7821
7853
  }
7822
7854
 
7855
+ // (b3c-neg) "what cannot fly" — the negative twin of (b3c): every stored
7856
+ // mgxneg:capableOf fact whose OBJECT matches. A matched shape with NO
7857
+ // stored negatives returns a definitive memory miss rather than falling
7858
+ // through — the conversational catch-all downstream misread this surface
7859
+ // as small talk and answered with the identity card.
7860
+ const cannotVerb = q.match(WHAT_CANNOT_VERB_RE);
7861
+ if (cannotVerb && cannotVerb[1].trim().split(/\s+/).at(-1)?.toLowerCase() !== "do") {
7862
+ const negVariants = factTermVariants(normFactTerm, cannotVerb[1]);
7863
+ const negHits = (await factRows(memoryDir, cache)).filter(
7864
+ (f) => f.predicate === "mgxneg:capableOf" && negVariants.has(f.object),
7865
+ );
7866
+ if (negHits.length) {
7867
+ const ranked = rankByBiasThenTrust(uniqueFacts(negHits), biasByBundle);
7868
+ const lines = ranked.map(renderFactLine);
7869
+ const shown = lines.slice(0, FACT_ANSWER_CAP);
7870
+ const rest = lines.slice(FACT_ANSWER_CAP);
7871
+ const extra = rest.length ? `\n…and ${rest.length} more — say 'more' to see them.` : "";
7872
+ return { text: shown.join("\n") + extra, replace: true, ...(rest.length ? { pending: { items: rest, noun: "facts" } } : {}) };
7873
+ }
7874
+ return { text: `nothing I remember says anything cannot ${cannotVerb[1].trim()}.`, replace: true, miss: true };
7875
+ }
7876
+
7823
7877
  // (b3c) "what can fly" — the unrestricted reverse-by-verb sibling of (b3b):
7824
7878
  // every capableOf fact whose OBJECT matches. The "… do" tail is (b3)'s
7825
7879
  // shape, guarded out so a zero-hit "what can a cat do" never gets misread
@@ -8624,7 +8678,10 @@ async function factReadBackReaders(memoryDir, query, envelope, miss, graph = nul
8624
8678
  // sharing it with (a0): RELATION_WHO_ASK_RE and RELATION_FACT_YESNO_RE never
8625
8679
  // both match the same query (one starts with "who", the other with
8626
8680
  // "is/are/was/were"), so the two blocks never run in the same call.
8627
- const whoAsk = qHedge.match(RELATION_WHO_ASK_RE) || matchGenitiveWhoAsk(qHedge);
8681
+ const whoAsk = expandWhoWhatLead(qHedge).match(RELATION_WHO_ASK_RE) || matchGenitiveWhoAsk(qHedge, (base) => {
8682
+ const known = factTermVariants(normFactTerm, base);
8683
+ return rows.some((f) => known.has(f.subject) || known.has(f.object));
8684
+ });
8628
8685
  if (whoAsk) {
8629
8686
  const relationName = whoAsk[1].trim().toLowerCase();
8630
8687
  const rawObject = whoAsk[2].trim();
@@ -8684,7 +8741,7 @@ async function factReadBackReaders(memoryDir, query, envelope, miss, graph = nul
8684
8741
  // oddly as "I don't know ANYONE who is the capital…" — the neutral
8685
8742
  // "nothing/anyone" split below matches whichever interrogative word the
8686
8743
  // query actually used.
8687
- const isWhatAsk = /^what\b/i.test(qHedge);
8744
+ const isWhatAsk = /^what\b/i.test(expandWhoWhatLead(qHedge));
8688
8745
  return {
8689
8746
  text: isWhatAsk
8690
8747
  ? `I don't know what the ${relationName} of ${object} is from what you've told me.`
@@ -12197,7 +12254,8 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
12197
12254
  // same divert-only-on-a-real-hit treatment as the reverse predicates
12198
12255
  // above.
12199
12256
  const capabilityAskShape = CAN_ASK_RE.test(gateQuery) || WHAT_CAN_DO_RE.test(gateQuery)
12200
- || DO_VERB_ASK_RE.test(gateQuery) || WHICH_KIND_CAN_RE.test(gateQuery) || WHAT_CAN_VERB_RE.test(gateQuery);
12257
+ || DO_VERB_ASK_RE.test(gateQuery) || WHICH_KIND_CAN_RE.test(gateQuery) || WHAT_CAN_VERB_RE.test(gateQuery)
12258
+ || WHAT_CANNOT_VERB_RE.test(gateQuery);
12201
12259
  // A bare "who is/was <name>" (no relational tail) is as short as the
12202
12260
  // vocabulary openers above and trips isConversational's word-count catch-all
12203
12261
  // the same way — factReadBack's bare-who reader surfaces the person's stored