@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 +1 -1
- package/src/services/chat.mjs +70 -12
- package/src/surfaces/web/memory-ask-browser.bundle.js +123 -121
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "2.11.
|
|
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.",
|
package/src/services/chat.mjs
CHANGED
|
@@ -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
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|