@polycode-projects/the-mechanical-code-talker 1.10.11 → 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 +1 -1
- package/src/chat.mjs +82 -10
- package/src/finish.mjs +8 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "1.10.
|
|
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
|
|
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
|
-
|
|
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]+)
|
|
3733
|
-
|
|
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
|
|
|
@@ -5452,6 +5471,31 @@ async function factReadBack(memoryDir, query, envelope, miss, graph = null, focu
|
|
|
5452
5471
|
const premises = chain.map(factForStep);
|
|
5453
5472
|
if (premises.every(Boolean)) return { text: `yes — ${renderIsaChain(premises)}`, replace: true };
|
|
5454
5473
|
}
|
|
5474
|
+
// MIXED-SOURCE EXTENSION of the chase above. The taught-only filter
|
|
5475
|
+
// exists to stop PURE-ConceptNet coincidence chains, but it also blocked
|
|
5476
|
+
// the everyday case where the operator anchors a term onto a corpus
|
|
5477
|
+
// class ("every poodle is a dog") and asks up through the corpus's own
|
|
5478
|
+
// hierarchy ("is a poodle an animal"). Corpus isa facts already answer
|
|
5479
|
+
// the 1-hop direct question on their own, so letting them JOIN a chain
|
|
5480
|
+
// that contains AT LEAST ONE operator-taught premise adds no fabrication
|
|
5481
|
+
// surface — a chain of ONLY corpus edges still never answers here, and
|
|
5482
|
+
// every premise is cited with its own source, corpus ones included. The
|
|
5483
|
+
// shared taught-only rows above stay untouched: the disjoint and
|
|
5484
|
+
// someValuesFrom chases keep their original, narrower discipline.
|
|
5485
|
+
const mixedSubClassRows = isa.filter((f) => f.predicate === SC_PREDICATE);
|
|
5486
|
+
const mixedTypeRows = isa.filter((f) => f.predicate === RDF_TYPE_PREDICATE);
|
|
5487
|
+
const mixedFactForStep = (step) => (step.predicate === SC_PREDICATE ? mixedSubClassRows : mixedTypeRows)
|
|
5488
|
+
.find((f) => f.subject === step.subject && f.object === step.object);
|
|
5489
|
+
const mixedTypeEdges = mixedTypeRows.map((f) => [f.subject, f.object]);
|
|
5490
|
+
const mixedSubClassEdges = mixedSubClassRows.map((f) => [f.subject, f.object]);
|
|
5491
|
+
for (const subj of subjCandidates) {
|
|
5492
|
+
const chain = findIsaChain(subj, objVariants, mixedTypeEdges, mixedSubClassEdges, { maxHops: 2 });
|
|
5493
|
+
if (!chain) continue;
|
|
5494
|
+
const premises = chain.map(mixedFactForStep);
|
|
5495
|
+
if (premises.every(Boolean) && premises.some(isTaught)) {
|
|
5496
|
+
return { text: `yes — ${renderIsaChain(premises)}`, replace: true };
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5455
5499
|
// LIVE cax-dw PROOF CHASE: every "yes" strategy above missed — check whether X's taught type
|
|
5456
5500
|
// (lifted through its FULL ⊑-ancestor closure) is disjointWith the
|
|
5457
5501
|
// queried class, via syllogise.mjs's deriveDisjointViolations, LIVE and
|
|
@@ -6029,9 +6073,13 @@ async function factReadBack(memoryDir, query, envelope, miss, graph = null, focu
|
|
|
6029
6073
|
const verb = verbRaw.toLowerCase();
|
|
6030
6074
|
if (!GENERAL_VERB_EXCLUDE_RE.test(verb) && !GENERAL_VERB_QUERY_EXCLUDE_RE.test(verb)) {
|
|
6031
6075
|
const subject = subjectRaw.trim();
|
|
6032
|
-
|
|
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();
|
|
6033
6081
|
if (subject && object) {
|
|
6034
|
-
const predicate =
|
|
6082
|
+
const predicate = folded.predicate;
|
|
6035
6083
|
const subjVariants = factTermVariants(normFactTerm, subject);
|
|
6036
6084
|
const objVariants = factTermVariants(normFactTerm, object);
|
|
6037
6085
|
const hit = rows
|
|
@@ -6045,11 +6093,15 @@ async function factReadBack(memoryDir, query, envelope, miss, graph = null, focu
|
|
|
6045
6093
|
const genOpen = q.match(GENERAL_VERB_OPEN_RE);
|
|
6046
6094
|
if (genOpen && !GENERAL_VERB_ANYWHERE_EXCLUDE_RE.test(q)) {
|
|
6047
6095
|
const [, subjectRaw, verbRaw] = genOpen;
|
|
6048
|
-
|
|
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+/);
|
|
6049
6100
|
if (!GENERAL_VERB_EXCLUDE_RE.test(verb) && !GENERAL_VERB_QUERY_EXCLUDE_RE.test(verb)) {
|
|
6050
6101
|
const subject = subjectRaw.trim();
|
|
6051
6102
|
if (subject) {
|
|
6052
|
-
|
|
6103
|
+
let predicate = await generalVerbPredicate(verb);
|
|
6104
|
+
if (verbPrep && /^mgx:[a-z]+$/.test(predicate)) predicate = `${predicate}-${verbPrep}`;
|
|
6053
6105
|
const subjVariants = factTermVariants(normFactTerm, subject);
|
|
6054
6106
|
const hits = rankByBiasThenTrust(rows.filter((f) => f.predicate === predicate && subjVariants.has(f.subject)), biasByBundle);
|
|
6055
6107
|
if (hits.length) return { ...renderMany(hits), generalVerbQuery: true };
|
|
@@ -6057,6 +6109,26 @@ async function factReadBack(memoryDir, query, envelope, miss, graph = null, focu
|
|
|
6057
6109
|
}
|
|
6058
6110
|
}
|
|
6059
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
|
+
|
|
6060
6132
|
// (b) RECALL — "what did i tell you about X": every remembered fact mentioning X.
|
|
6061
6133
|
const told = q.match(TOLD_ABOUT_RE);
|
|
6062
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
|
-
|
|
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
|
-
|
|
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) : "";
|