@polycode-projects/the-mechanical-code-talker 1.10.7 → 1.10.8
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 +72 -0
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.8",
|
|
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
|
@@ -4166,6 +4166,42 @@ const REVERSE_PREDICATE_MARKERS = Object.entries(FACT_PREDICATE_PHRASES)
|
|
|
4166
4166
|
re: new RegExp(`^what\\s+${escapeRegex(phrase)}\\s+(.+?)[?.!\\s]*$`, "i"),
|
|
4167
4167
|
}))
|
|
4168
4168
|
.sort((a, b) => b.re.source.length - a.re.source.length); // longest phrase first
|
|
4169
|
+
|
|
4170
|
+
// The FORWARD yes/no mirror of REVERSE_PREDICATE_MARKERS: one derived
|
|
4171
|
+
// "is/are X <phrase> Y" (copula phrases), "can X be Y" (receivesAction), or
|
|
4172
|
+
// "does/do X <base-verb> Y" (verb phrases, naive de-3sg fold) reader per
|
|
4173
|
+
// FACT_PREDICATE_PHRASES entry — so every relation the table can RENDER can
|
|
4174
|
+
// also be ASKED as a forward yes/no, instead of each one needing its own
|
|
4175
|
+
// hand-written lane. Excluded: the isa family and hasProperty (ISA_ASK_RE /
|
|
4176
|
+
// IS_ADJECTIVE_YESNO_RE territory), hasA and capableOf (their dedicated
|
|
4177
|
+
// readers above carry teach hints these derived ones deliberately don't —
|
|
4178
|
+
// no derived hint is emitted because no teach phrasing for these relations
|
|
4179
|
+
// is verified to round-trip).
|
|
4180
|
+
const FORWARD_YESNO_EXCLUDE = new Set([
|
|
4181
|
+
"rdfs:subClassOf", "rdf:type", "owl:disjointWith", "mgx:hasProperty",
|
|
4182
|
+
"mgx:hasA", "mgx:capableOf",
|
|
4183
|
+
// ownership's dedicated reader (OWNS_YESNO_RE) answers a confident
|
|
4184
|
+
// closed-world "no" — a stronger contract than the derived "can't
|
|
4185
|
+
// confirm", so the derived reader must never intercept it.
|
|
4186
|
+
"mgx:ownedBy",
|
|
4187
|
+
]);
|
|
4188
|
+
const FORWARD_YESNO_MARKERS = Object.entries(FACT_PREDICATE_PHRASES)
|
|
4189
|
+
.filter(([predicate]) => !FORWARD_YESNO_EXCLUDE.has(predicate))
|
|
4190
|
+
.map(([predicate, phrase]) => {
|
|
4191
|
+
let re;
|
|
4192
|
+
if (phrase === "can be") {
|
|
4193
|
+
re = new RegExp("^can\\s+(?:an?\\s+|the\\s+)?(.+?)\\s+be\\s+(.+?)[?.!\\s]*$", "i");
|
|
4194
|
+
} else if (phrase.startsWith("is ")) {
|
|
4195
|
+
const rest = escapeRegex(phrase.slice(3));
|
|
4196
|
+
re = new RegExp(`^(?:is|are)\\s+(?:an?\\s+|the\\s+)?(.+?)\\s+${rest}\\s+(?:an?\\s+|the\\s+)?(.+?)[?.!\\s]*$`, "i");
|
|
4197
|
+
} else {
|
|
4198
|
+
const [head, ...tail] = phrase.split(" ");
|
|
4199
|
+
const base = [head.replace(/s$/, ""), ...tail].map(escapeRegex).join("\\s+");
|
|
4200
|
+
re = new RegExp(`^(?:does|do)\\s+(?:an?\\s+|the\\s+)?(.+?)\\s+${base}\\s+(?:an?\\s+|the\\s+)?(.+?)[?.!\\s]*$`, "i");
|
|
4201
|
+
}
|
|
4202
|
+
return { predicate, phrase, re };
|
|
4203
|
+
})
|
|
4204
|
+
.sort((a, b) => b.re.source.length - a.re.source.length); // longest phrase first
|
|
4169
4205
|
// On the FIRST turn of a graph-less session, `envelope` stays null for the
|
|
4170
4206
|
// whole turn (dispatchTool's loadGraph() throws its own documented empty-graph
|
|
4171
4207
|
// ToolError, self-correcting from turn 2 on), so this regex is the ONLY path
|
|
@@ -4330,6 +4366,42 @@ export async function factAnswer(memoryDir, query, envelope, miss, biasByBundle
|
|
|
4330
4366
|
}
|
|
4331
4367
|
if (!miss) return null;
|
|
4332
4368
|
|
|
4369
|
+
// (b0) Derived forward yes/no readers — FORWARD_YESNO_MARKERS, one per
|
|
4370
|
+
// renderable relation. Runs BEFORE the isa lane because ISA_ASK_RE's lazy
|
|
4371
|
+
// subject otherwise swallows these shapes whole ("is a wheel part of a
|
|
4372
|
+
// car" reads as subject "wheel part of") and ends the cascade. A real fact
|
|
4373
|
+
// answers yes; a subject known under the SAME relation gets an honest miss
|
|
4374
|
+
// citing those facts; a subject known at all (with no structural parse
|
|
4375
|
+
// standing) gets a bare honest miss; anything else leaves the standing
|
|
4376
|
+
// miss text alone — so a code-shaped query with a real parse is never
|
|
4377
|
+
// hijacked.
|
|
4378
|
+
for (const { predicate, phrase, re } of FORWARD_YESNO_MARKERS) {
|
|
4379
|
+
const m = q.match(re);
|
|
4380
|
+
if (!m) continue;
|
|
4381
|
+
const facts = await memoryFacts(memoryDir);
|
|
4382
|
+
const subj = factTermVariants(normFactTerm, m[1]);
|
|
4383
|
+
const obj = factTermVariants(normFactTerm, m[2]);
|
|
4384
|
+
const hit = facts.find((f) => f.predicate === predicate && subj.has(f.subject) && obj.has(f.object));
|
|
4385
|
+
if (hit) return { text: `yes — ${renderFactLine(hit)}`, replace: true };
|
|
4386
|
+
const sameRelation = facts.filter((f) => f.predicate === predicate && subj.has(f.subject));
|
|
4387
|
+
if (sameRelation.length) {
|
|
4388
|
+
const shown = sameRelation.slice(0, 3).map(renderFactLine).join("; ");
|
|
4389
|
+
return {
|
|
4390
|
+
text: `I can't confirm that — nothing I remember says ${m[1]} ${phrase} ${m[2]}. I do know: ${shown}.`,
|
|
4391
|
+
replace: true,
|
|
4392
|
+
miss: true,
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4395
|
+
if (!envelope?.parsed && facts.some((f) => subj.has(f.subject))) {
|
|
4396
|
+
return {
|
|
4397
|
+
text: `I can't confirm that — nothing I remember says ${m[1]} ${phrase} ${m[2]}.`,
|
|
4398
|
+
replace: true,
|
|
4399
|
+
miss: true,
|
|
4400
|
+
};
|
|
4401
|
+
}
|
|
4402
|
+
break; // shape matched, nothing honest to add — the standing miss stands
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4333
4405
|
// (b) "is a module a component" — yes iff a remembered isa-family fact says so.
|
|
4334
4406
|
// Also accepts "why is X a Y" / "explain how you know X is Y" — see matchWhyIsa.
|
|
4335
4407
|
const isa = q.match(ISA_ASK_RE) || matchWhyIsa(q);
|