@polycode-projects/the-mechanical-code-talker 1.8.15 → 1.8.16
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 +99 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.16",
|
|
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
|
@@ -1977,23 +1977,33 @@ function singularizeSurface(word) {
|
|
|
1977
1977
|
const SOME_A_FEW_RE = /^(some|a few)\s+([\w-]+)\s+are\s+([\w-]+)$/i;
|
|
1978
1978
|
|
|
1979
1979
|
/** "(every|each|all|a|an )?X is/are (a|an )?Y" — the shape the unknown-subject
|
|
1980
|
-
* fallback recognizes (group 2 = X, group
|
|
1980
|
+
* fallback recognizes (group 2 = X, group 4 = Y); group 1 (when present)
|
|
1981
1981
|
* names the determiner, so the caller can tell a genuine "every" universal
|
|
1982
1982
|
* apart from a singular/specific-entity "a"/bare reading (only "every" gets a
|
|
1983
1983
|
* recorded quantifier here — this function's OWN caller passes it through to
|
|
1984
1984
|
* teachFact; assertTurn, below, records the same "every" quantifier
|
|
1985
|
-
* independently for the pre-existing ACE-success path).
|
|
1986
|
-
*
|
|
1987
|
-
*
|
|
1988
|
-
*
|
|
1989
|
-
*
|
|
1990
|
-
*
|
|
1991
|
-
*
|
|
1992
|
-
*
|
|
1993
|
-
*
|
|
1994
|
-
*
|
|
1995
|
-
* the
|
|
1996
|
-
|
|
1985
|
+
* independently for the pre-existing ACE-success path). Group 3 is the
|
|
1986
|
+
* copula itself (is/are) — CAPTURED (not just matched) so a caller can tell
|
|
1987
|
+
* a genuinely PLURAL subject phrasing ("all men ARE mortal") apart from a
|
|
1988
|
+
* singular one ("redis IS a cache"): singularizing the subject before
|
|
1989
|
+
* storage is only ever correct for the former (see unknownSubjectFallback's
|
|
1990
|
+
* and unknownObjectFallback's own docblocks — this is the "men"->"man" fix's
|
|
1991
|
+
* own safety gate, added after singularizing unconditionally was found live
|
|
1992
|
+
* to corrupt the pre-existing "redis is a cache" pinned case: "redis", a
|
|
1993
|
+
* proper noun that happens to end in "s", naively strips to "redi" if
|
|
1994
|
+
* singularized on an "is" sentence, where no such fold was ever needed).
|
|
1995
|
+
* Y (the object) is a single token, same as parseAce's own copula fragments;
|
|
1996
|
+
* X (the subject) is ONE OR TWO tokens (Tier-5 playtest fix: "vulcan gizmo
|
|
1997
|
+
* is a tool"/"remember vulcan gizmo is a tool" fell straight to a "teach me"
|
|
1998
|
+
* nudge that offered THIS EXACT phrasing as the fix, then itself failed when
|
|
1999
|
+
* tried — a single-token-only subject was too narrow for a natural 2-word
|
|
2000
|
+
* noun phrase, the same class of gap OWNS_TEACH_RE's own object had before
|
|
2001
|
+
* its own Tier-5 widening, above). The greedy quantifier tries the longer
|
|
2002
|
+
* 2-word subject first, backtracking to 1 word only if the tail doesn't then
|
|
2003
|
+
* start with is/are — the "is/are" anchor immediately after the subject
|
|
2004
|
+
* removes the ambiguity a fully free-form multi-word subject would
|
|
2005
|
+
* otherwise have. */
|
|
2006
|
+
const UNKNOWN_SUBJECT_RE = /^(every\s+|each\s+|all\s+|a\s+|an\s+)?([\w-]+(?:\s+[\w-]+)?)\s+(is|are)\s+(?:an?\s+)?([\w-]+)$/i;
|
|
1997
2007
|
|
|
1998
2008
|
/** ISA-family predicates (mirrors the private ISA_PREDICATES set defined near
|
|
1999
2009
|
* memoryFacts, below, at module scope — both are simple top-level consts
|
|
@@ -2092,7 +2102,7 @@ async function ungroundedPairHint(payload, lexicon, memoryDir) {
|
|
|
2092
2102
|
if (!memoryDir) return "";
|
|
2093
2103
|
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
2094
2104
|
if (!m) return "";
|
|
2095
|
-
const [, , subjectRaw, objectRaw] = m;
|
|
2105
|
+
const [, , subjectRaw, , objectRaw] = m;
|
|
2096
2106
|
const { loadLexicon } = await import("./grammar/lexicon.mjs");
|
|
2097
2107
|
const lex = lexicon || loadLexicon();
|
|
2098
2108
|
if (await isGroundedTerm(subjectRaw, lex, memoryDir)) return "";
|
|
@@ -2144,12 +2154,29 @@ async function unknownSubjectFallback(payload, { memoryDir, sessionId, lexicon }
|
|
|
2144
2154
|
if (!memoryDir) return null;
|
|
2145
2155
|
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
2146
2156
|
if (!m) return null;
|
|
2147
|
-
const [, det, subjectRaw, objectRaw] = m;
|
|
2157
|
+
const [, det, subjectRaw, verb, objectRaw] = m;
|
|
2148
2158
|
const { loadLexicon, lookupNoun, lookupAdjective, classify } = await import("./grammar/lexicon.mjs");
|
|
2149
2159
|
const lex = lexicon || loadLexicon();
|
|
2150
2160
|
// A known X's own ACE miss is a real miss — never silently reinterpreted here.
|
|
2151
2161
|
if (classify(subjectRaw, lex)) return null;
|
|
2152
2162
|
const quantifier = /^every$/i.test((det || "").trim()) ? "every" : "";
|
|
2163
|
+
// Singularize the SUBJECT before storage, but ONLY on a genuinely PLURAL
|
|
2164
|
+
// phrasing ("all men ARE mortal", verb "are") — mirrors SOME_A_FEW_RE's own
|
|
2165
|
+
// singularizeSurface() call (above), which is safe unconditionally there
|
|
2166
|
+
// only because that shape's own regex requires "are" by construction. This
|
|
2167
|
+
// shape (UNKNOWN_SUBJECT_RE) also matches singular "is" sentences ("redis
|
|
2168
|
+
// is a cache"), where singularizing must NEVER run — "redis" naively folds
|
|
2169
|
+
// to "redi" under the same naive -s-strip, a real regression caught live by
|
|
2170
|
+
// the pinned "redis is a cache" tests when this fix first applied
|
|
2171
|
+
// unconditionally. So "all men are mortal" stores under "man" (matching
|
|
2172
|
+
// whatever "john is a man" already typed John as), not the raw plural
|
|
2173
|
+
// "men", while "redis is a cache" stores "redis" untouched. Without this,
|
|
2174
|
+
// findIsaChain's 2-hop proof (john->man, man->mortal) can never join, since
|
|
2175
|
+
// the second fact was keyed on a different string ("men") than the first
|
|
2176
|
+
// fact's object ("man"). classify(subjectRaw, lex) above already folds
|
|
2177
|
+
// plurals for the "is this a real miss" check, so singularizing only the
|
|
2178
|
+
// STORED value here is safe and doesn't change that check's behavior.
|
|
2179
|
+
const subject = /^are$/i.test(verb) ? singularizeSurface(subjectRaw) : subjectRaw;
|
|
2153
2180
|
// Point 2 (mint-extension): a PRIOR turn's minted term, or a
|
|
2154
2181
|
// GENERIC_ANCHOR_NOUNS root, grounds Y just as legitimately as a static
|
|
2155
2182
|
// lexicon noun — both are always treated as class-level (never property),
|
|
@@ -2157,14 +2184,14 @@ async function unknownSubjectFallback(payload, { memoryDir, sessionId, lexicon }
|
|
|
2157
2184
|
if (lookupNoun(lex, objectRaw) || GENERIC_ANCHOR_NOUNS.has(String(objectRaw).toLowerCase())
|
|
2158
2185
|
|| (await isGroundedByFact(objectRaw, memoryDir))) {
|
|
2159
2186
|
return teachFact(memoryDir, sessionId, {
|
|
2160
|
-
subject
|
|
2187
|
+
subject, predicate: SUBCLASS_PREDICATE, object: objectRaw, quantifier,
|
|
2161
2188
|
});
|
|
2162
2189
|
}
|
|
2163
2190
|
if (lookupAdjective(lex, objectRaw)) {
|
|
2164
2191
|
// property assertions are about ONE specific entity — never a quantifier,
|
|
2165
2192
|
// even when phrased with "every" (point 3).
|
|
2166
2193
|
return teachFact(memoryDir, sessionId, {
|
|
2167
|
-
subject
|
|
2194
|
+
subject, predicate: HAS_PROPERTY_PREDICATE, object: objectRaw,
|
|
2168
2195
|
});
|
|
2169
2196
|
}
|
|
2170
2197
|
return null; // Y unknown too — decline honestly, never guess
|
|
@@ -2245,9 +2272,9 @@ async function unknownObjectFallback(payload, { memoryDir, sessionId, lexicon })
|
|
|
2245
2272
|
if (!memoryDir) return null;
|
|
2246
2273
|
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
2247
2274
|
if (!m) return null;
|
|
2248
|
-
const [, det, subjectRaw, objectRaw] = m;
|
|
2275
|
+
const [, det, subjectRaw, verb, objectRaw] = m;
|
|
2249
2276
|
if (!/^(?:every|each|all)$/i.test((det || "").trim())) return null; // class-level mint needs a real universal quantifier
|
|
2250
|
-
const { loadLexicon } = await import("./grammar/lexicon.mjs");
|
|
2277
|
+
const { loadLexicon, lookupNoun } = await import("./grammar/lexicon.mjs");
|
|
2251
2278
|
const lex = lexicon || loadLexicon();
|
|
2252
2279
|
const subjectGrounded = await isGroundedTerm(subjectRaw, lex, memoryDir);
|
|
2253
2280
|
if (!subjectGrounded) return null; // ungrounded subject isn't this fallback's asymmetry — never a guessed mint
|
|
@@ -2255,8 +2282,32 @@ async function unknownObjectFallback(payload, { memoryDir, sessionId, lexicon })
|
|
|
2255
2282
|
if (objectGrounded) return null; // object already known — nothing to mint
|
|
2256
2283
|
if (await objectReadsAsNonNoun(objectRaw)) return null; // reads like an adjective/verb, not a class noun — defer to unknownAdjectiveFallback
|
|
2257
2284
|
const quantifier = /^every$/i.test((det || "").trim()) ? "every" : "";
|
|
2285
|
+
// Singularize the SUBJECT before storage, ONLY on a genuinely PLURAL
|
|
2286
|
+
// phrasing (verb "are") — same bug class and same "is"-vs-"are" safety gate
|
|
2287
|
+
// as unknownSubjectFallback, above (see UNKNOWN_SUBJECT_RE's own docblock).
|
|
2288
|
+
// This is the fallback the canonical "all men are mortal" sentence
|
|
2289
|
+
// ACTUALLY goes through: "men" is grounded here via classify's own
|
|
2290
|
+
// IRREGULAR plural-fold (lexicon-core.json declares "man"/plural "men"), so
|
|
2291
|
+
// unknownSubjectFallback itself already declines for it (its own
|
|
2292
|
+
// classify(subjectRaw) check reads "men" as the known noun "man") and hands
|
|
2293
|
+
// off to this mirror fallback instead. A naive suffix-strip
|
|
2294
|
+
// (singularizeSurface — SOME_A_FEW_RE's own tool, reused as the fallback for
|
|
2295
|
+
// a genuinely novel REGULAR plural not in the lexicon, e.g. "zorps") can't
|
|
2296
|
+
// undo an IRREGULAR plural like "men" -> "man" — only the lexicon's own
|
|
2297
|
+
// noun table (lookupNoun, already resolved by isGroundedTerm/classify to
|
|
2298
|
+
// decide this subject counts as grounded in the first place) carries that
|
|
2299
|
+
// mapping, so storage must consult the SAME source of truth: prefer the
|
|
2300
|
+
// lexicon lemma, falling back to singularizeSurface for a subject grounded
|
|
2301
|
+
// only via a PRIOR taught fact (isGroundedByFact) or a regular-plural
|
|
2302
|
+
// lexicon fold. Gated on verb "are" for the identical reason
|
|
2303
|
+
// unknownSubjectFallback gates it: an already-singular grounded subject
|
|
2304
|
+
// that happens to end in "s" (e.g. a fact-grounded "gas") must never be
|
|
2305
|
+
// naively stripped on an "is" sentence ("every gas is a chemical").
|
|
2306
|
+
const subject = /^are$/i.test(verb)
|
|
2307
|
+
? (lookupNoun(lex, subjectRaw)?.lemma || singularizeSurface(subjectRaw))
|
|
2308
|
+
: subjectRaw;
|
|
2258
2309
|
return teachFact(memoryDir, sessionId, {
|
|
2259
|
-
subject
|
|
2310
|
+
subject, predicate: SUBCLASS_PREDICATE, object: objectRaw, quantifier,
|
|
2260
2311
|
});
|
|
2261
2312
|
}
|
|
2262
2313
|
|
|
@@ -2323,7 +2374,7 @@ async function unknownAdjectiveFallback(payload, { memoryDir, sessionId, lexicon
|
|
|
2323
2374
|
if (!memoryDir) return null;
|
|
2324
2375
|
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
2325
2376
|
if (!m) return null;
|
|
2326
|
-
const [, , subjectRaw, objectRaw] = m;
|
|
2377
|
+
const [, , subjectRaw, , objectRaw] = m;
|
|
2327
2378
|
const { loadLexicon, lookupNoun, classify } = await import("./grammar/lexicon.mjs");
|
|
2328
2379
|
const lex = lexicon || loadLexicon();
|
|
2329
2380
|
// Y already a known NOUN or a fact-grounded CLASS term — a genuine class-
|
|
@@ -5916,6 +5967,33 @@ async function factReadBack(memoryDir, query, envelope, miss, graph = null, focu
|
|
|
5916
5967
|
replace: true,
|
|
5917
5968
|
};
|
|
5918
5969
|
}
|
|
5970
|
+
// No property hit — but a bare "is X Y" (no article) is exactly the
|
|
5971
|
+
// same claim as "is X a Y" would have been had the user included the
|
|
5972
|
+
// article (ISA_ASK_RE's own territory, above): SKILL_BENCHMARK_CONVERSATION.md's
|
|
5973
|
+
// canonical syllogism ("john is a man" / "all men are mortal" / "is
|
|
5974
|
+
// john mortal") is asked this bare way, and "mortal" was taught as a
|
|
5975
|
+
// CLASS (rdfs:subClassOf), not a property — so it can only ever be
|
|
5976
|
+
// found by the SAME 2-hop TAUGHT-only findIsaChain proof-chase isaAsk
|
|
5977
|
+
// uses above, never by propertyMatch. Tried here as an ADDITIONAL
|
|
5978
|
+
// attempt, never a replacement: on no chain either, this falls through
|
|
5979
|
+
// to the ordinary property-miss handling just below, unchanged.
|
|
5980
|
+
{
|
|
5981
|
+
const { findIsaChain: chaseAdj, SUBCLASS_PREDICATE: SC_PREDICATE_ADJ, TYPE_PREDICATE: TYPE_PREDICATE_ADJ } = await import("./syllogise.mjs");
|
|
5982
|
+
const isTaughtAdj = (f) => !f.sourceTypes?.includes("corpus") && !f.sourceTypes?.includes("web");
|
|
5983
|
+
const chainSubClassRowsAdj = rows.filter((f) => f.predicate === SC_PREDICATE_ADJ && isTaughtAdj(f));
|
|
5984
|
+
const chainTypeRowsAdj = rows.filter((f) => f.predicate === TYPE_PREDICATE_ADJ && isTaughtAdj(f));
|
|
5985
|
+
const chainSubClassEdgesAdj = chainSubClassRowsAdj.map((f) => [f.subject, f.object]);
|
|
5986
|
+
const chainTypeEdgesAdj = chainTypeRowsAdj.map((f) => [f.subject, f.object]);
|
|
5987
|
+
const factForStepAdj = (step) => (step.predicate === SC_PREDICATE_ADJ ? chainSubClassRowsAdj : chainTypeRowsAdj)
|
|
5988
|
+
.find((f) => f.subject === step.subject && f.object === step.object);
|
|
5989
|
+
const adjObjVariants = factTermVariants(normFactTerm, adjective);
|
|
5990
|
+
for (const subj of subjVariants) {
|
|
5991
|
+
const chain = chaseAdj(subj, adjObjVariants, chainTypeEdgesAdj, chainSubClassEdgesAdj, { maxHops: 2 });
|
|
5992
|
+
if (!chain) continue;
|
|
5993
|
+
const premises = chain.map(factForStepAdj);
|
|
5994
|
+
if (premises.every(Boolean)) return { text: `yes — ${renderIsaChain(premises)}`, replace: true };
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
5919
5997
|
// no hit on THIS property — never a guessed "no" (see
|
|
5920
5998
|
// IS_ADJECTIVE_YESNO_RE's own docblock for why this stays silent on a
|
|
5921
5999
|
// truth claim, unlike its ownership/general-verb siblings above). But a
|