@polycode-projects/the-mechanical-code-talker 1.10.10 → 1.10.12
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 +26 -1
- package/src/grammar/ace.mjs +30 -12
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.12",
|
|
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
|
@@ -1532,7 +1532,7 @@ const WALL_MISS_ANYWHERE_RE = /couldn't parse this as a graph question\. Try:/;
|
|
|
1532
1532
|
// assert/memory path; when it can't be stored, say what CAN be remembered
|
|
1533
1533
|
// instead of the grammar wall or a silent data loss.
|
|
1534
1534
|
const TEACH_RE = /^(?:please\s+)?(?:i\s+(?:want|wanted)\s+you\s+to\s+|i(?:'d|\s+would)\s+like\s+you\s+to\s+)?(?:remember|note|keep in mind|jot down|for the record|fyi|learn)\b(?:\s+(?:this|that|also))?[:,]?\s*(?:that\s+)?(.+?)[.?!]*$/i;
|
|
1535
|
-
const BARE_DECLARATIVE_RE = /^(?:every |each |all |a |an )?[\w-]+ (?:is|are) (?:a |an )?[\w-]+$/i;
|
|
1535
|
+
const BARE_DECLARATIVE_RE = /^(?:every |each |all |a |an )?[\w-]+(?: [\w-]+)? (?:is|are) (?:a |an )?[\w-]+$/i;
|
|
1536
1536
|
/** Interrogative / auxiliary leads that make an "X is a Y"-shaped line a QUESTION
|
|
1537
1537
|
* ("what is a cache", "is a module a component"), never a teach declarative. */
|
|
1538
1538
|
const QUESTION_LEAD_RE = /^(?:what|who|which|where|when|why|how|is|are|do|does|did|can|could|should|would|will|has|have)\b/i;
|
|
@@ -5452,6 +5452,31 @@ async function factReadBack(memoryDir, query, envelope, miss, graph = null, focu
|
|
|
5452
5452
|
const premises = chain.map(factForStep);
|
|
5453
5453
|
if (premises.every(Boolean)) return { text: `yes — ${renderIsaChain(premises)}`, replace: true };
|
|
5454
5454
|
}
|
|
5455
|
+
// MIXED-SOURCE EXTENSION of the chase above. The taught-only filter
|
|
5456
|
+
// exists to stop PURE-ConceptNet coincidence chains, but it also blocked
|
|
5457
|
+
// the everyday case where the operator anchors a term onto a corpus
|
|
5458
|
+
// class ("every poodle is a dog") and asks up through the corpus's own
|
|
5459
|
+
// hierarchy ("is a poodle an animal"). Corpus isa facts already answer
|
|
5460
|
+
// the 1-hop direct question on their own, so letting them JOIN a chain
|
|
5461
|
+
// that contains AT LEAST ONE operator-taught premise adds no fabrication
|
|
5462
|
+
// surface — a chain of ONLY corpus edges still never answers here, and
|
|
5463
|
+
// every premise is cited with its own source, corpus ones included. The
|
|
5464
|
+
// shared taught-only rows above stay untouched: the disjoint and
|
|
5465
|
+
// someValuesFrom chases keep their original, narrower discipline.
|
|
5466
|
+
const mixedSubClassRows = isa.filter((f) => f.predicate === SC_PREDICATE);
|
|
5467
|
+
const mixedTypeRows = isa.filter((f) => f.predicate === RDF_TYPE_PREDICATE);
|
|
5468
|
+
const mixedFactForStep = (step) => (step.predicate === SC_PREDICATE ? mixedSubClassRows : mixedTypeRows)
|
|
5469
|
+
.find((f) => f.subject === step.subject && f.object === step.object);
|
|
5470
|
+
const mixedTypeEdges = mixedTypeRows.map((f) => [f.subject, f.object]);
|
|
5471
|
+
const mixedSubClassEdges = mixedSubClassRows.map((f) => [f.subject, f.object]);
|
|
5472
|
+
for (const subj of subjCandidates) {
|
|
5473
|
+
const chain = findIsaChain(subj, objVariants, mixedTypeEdges, mixedSubClassEdges, { maxHops: 2 });
|
|
5474
|
+
if (!chain) continue;
|
|
5475
|
+
const premises = chain.map(mixedFactForStep);
|
|
5476
|
+
if (premises.every(Boolean) && premises.some(isTaught)) {
|
|
5477
|
+
return { text: `yes — ${renderIsaChain(premises)}`, replace: true };
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5455
5480
|
// LIVE cax-dw PROOF CHASE: every "yes" strategy above missed — check whether X's taught type
|
|
5456
5481
|
// (lifted through its FULL ⊑-ancestor closure) is disjointWith the
|
|
5457
5482
|
// queried class, via syllogise.mjs's deriveDisjointViolations, LIVE and
|
package/src/grammar/ace.mjs
CHANGED
|
@@ -95,7 +95,7 @@ const stripDet = (tokens) =>
|
|
|
95
95
|
* with "a"/"an" — the one signal that a singular-plural-fold collision
|
|
96
96
|
* (die/dice, person/people, tooth/teeth) can be resolved by, rather than
|
|
97
97
|
* silently committing to whichever the lexicon happens to fold to first. */
|
|
98
|
-
function resolveNP(lexicon, tokensIn) {
|
|
98
|
+
function resolveNP(lexicon, tokensIn, { allowCompound = false } = {}) {
|
|
99
99
|
const ns = lexicon.ns;
|
|
100
100
|
const singularOnly = tokensIn.length > 1 && SINGULAR_ONLY_DET.has(tokensIn[0].toLowerCase());
|
|
101
101
|
const tokens = stripDet(tokensIn);
|
|
@@ -131,6 +131,26 @@ function resolveNP(lexicon, tokensIn) {
|
|
|
131
131
|
}
|
|
132
132
|
return { term, individual: false, noun, extras, unknown: [] };
|
|
133
133
|
}
|
|
134
|
+
// Two plain NOUNS in a row are ONE compound noun ("guinea pig", "sports
|
|
135
|
+
// car"), space-joined to match the corpus's own multi-word concepts
|
|
136
|
+
// ("schema person"), so the taught fact and the query side unify.
|
|
137
|
+
// STRICTLY OPT-IN per call site (allowCompound): only the patterns where
|
|
138
|
+
// a compound subject is safe request it — capability, quantified
|
|
139
|
+
// membership, disjointness, and the articled-complement copula. The
|
|
140
|
+
// generic relation walk and the bare-adjective copula never do, so a
|
|
141
|
+
// question lead ("does dog have…") or a property sentence ("checkout
|
|
142
|
+
// flow is deprecated") can never silently become an ACE teach. A
|
|
143
|
+
// DECLARED proper name in either slot ("GitLab pipeline") keeps the
|
|
144
|
+
// structural miss below — a name in the wrong slot, not a compound.
|
|
145
|
+
if (allowCompound
|
|
146
|
+
&& !lookupProperName(lexicon, tokens[0]) && !lookupProperName(lexicon, tokens[1])
|
|
147
|
+
&& /^[a-z][a-z'-]*$/i.test(tokens[0]) && /^[a-z][a-z'-]*$/i.test(tokens[1])) {
|
|
148
|
+
const n0 = lookupNoun(lexicon, tokens[0], { singularOnly: false });
|
|
149
|
+
const n1 = lookupNoun(lexicon, tokens[1], { singularOnly });
|
|
150
|
+
if (n0 && n1) {
|
|
151
|
+
return { term: `${ns}${tokens[0].toLowerCase()} ${tokens[1].toLowerCase()}`, individual: false, noun: n1, extras: [], unknown: [] };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
134
154
|
// only genuinely undeclared words are residue — a declared word in the
|
|
135
155
|
// wrong slot ("GitLab pipeline") is a structural miss, not an unknown
|
|
136
156
|
const unknown = tokens.filter((t) => !classify(t, lexicon));
|
|
@@ -326,13 +346,11 @@ function parseEvery(lexicon, toks, lower) {
|
|
|
326
346
|
}
|
|
327
347
|
const isIdx = lower.indexOf("is");
|
|
328
348
|
if (isIdx <= 1 || isIdx === toks.length - 1) return null;
|
|
329
|
-
const np1 = resolveNP(lexicon, toks.slice(1, isIdx));
|
|
330
349
|
const rest = toks.slice(isIdx + 1);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
const np2 = resolveNP(lexicon, rest);
|
|
350
|
+
const everyAdjOnly = rest.length === 1 ? lookupAdjective(lexicon, rest[0]) : null;
|
|
351
|
+
const np1 = resolveNP(lexicon, toks.slice(1, isIdx), { allowCompound: !everyAdjOnly });
|
|
352
|
+
if (everyAdjOnly) return adjectiveCopula(lexicon, PATTERN_ADJECTIVE, np1, everyAdjOnly);
|
|
353
|
+
const np2 = resolveNP(lexicon, rest, { allowCompound: true });
|
|
336
354
|
if (np1.term == null || np2.term == null) return missOrNull(PATTERN_SUB_CLASS_OF, [np1, np2]);
|
|
337
355
|
if (np1.individual || np2.individual) return null; // "every X is chat.mjs" — not the fragment
|
|
338
356
|
return hit(PATTERN_SUB_CLASS_OF, [np1, np2], [
|
|
@@ -344,8 +362,8 @@ function parseEvery(lexicon, toks, lower) {
|
|
|
344
362
|
function parseDisjoint(lexicon, toks, lower) {
|
|
345
363
|
const isIdx = lower.indexOf("is");
|
|
346
364
|
if (isIdx <= 1 || isIdx === toks.length - 1) return null;
|
|
347
|
-
const np1 = resolveNP(lexicon, toks.slice(1, isIdx));
|
|
348
|
-
const np2 = resolveNP(lexicon, toks.slice(isIdx + 1));
|
|
365
|
+
const np1 = resolveNP(lexicon, toks.slice(1, isIdx), { allowCompound: true });
|
|
366
|
+
const np2 = resolveNP(lexicon, toks.slice(isIdx + 1), { allowCompound: true });
|
|
349
367
|
if (np1.term == null || np2.term == null) return missOrNull(PATTERN_DISJOINT_WITH, [np1, np2]);
|
|
350
368
|
if (np1.individual || np2.individual) return null;
|
|
351
369
|
return hit(PATTERN_DISJOINT_WITH, [np1, np2], [
|
|
@@ -393,14 +411,14 @@ function parseOfForm(lexicon, toks, lower) {
|
|
|
393
411
|
|
|
394
412
|
/** Patterns 2 (class assertion), 1's bare-copula variant, and 8's copula arm. */
|
|
395
413
|
function parseCopula(lexicon, toks, lower, isIdx) {
|
|
396
|
-
const np1 = resolveNP(lexicon, toks.slice(0, isIdx));
|
|
397
414
|
const rest = toks.slice(isIdx + 1);
|
|
398
415
|
if (!rest.length) return null;
|
|
416
|
+
const np1 = resolveNP(lexicon, toks.slice(0, isIdx), { allowCompound: rest.length > 1 });
|
|
399
417
|
if (rest.length === 1) {
|
|
400
418
|
const adj = lookupAdjective(lexicon, rest[0]);
|
|
401
419
|
if (adj) return adjectiveCopula(lexicon, PATTERN_ADJECTIVE, np1, adj);
|
|
402
420
|
}
|
|
403
|
-
const np2 = resolveNP(lexicon, rest);
|
|
421
|
+
const np2 = resolveNP(lexicon, rest, { allowCompound: true });
|
|
404
422
|
if (np1.term == null || np2.term == null) {
|
|
405
423
|
return missOrNull(np1.individual ? PATTERN_TYPE_ASSERTION : PATTERN_SUB_CLASS_OF, [np1, np2]);
|
|
406
424
|
}
|
|
@@ -448,7 +466,7 @@ export function parseAce(sentence, lexicon = loadLexicon()) {
|
|
|
448
466
|
* has no negative-capability predicate, and a silently dropped negation
|
|
449
467
|
* would invert the taught meaning. */
|
|
450
468
|
function parseCapability(lexicon, toks, canIdx) {
|
|
451
|
-
const np1 = resolveNP(lexicon, toks.slice(0, canIdx));
|
|
469
|
+
const np1 = resolveNP(lexicon, toks.slice(0, canIdx), { allowCompound: true });
|
|
452
470
|
if (np1.term == null) return null;
|
|
453
471
|
// The capability's object is a VERB ("swim"), not a lexicon noun, so
|
|
454
472
|
// resolveNP is the wrong resolver for it: accept exactly one bare word,
|