@polycode-projects/the-mechanical-code-talker 0.4.0 → 0.6.0
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/README.md +64 -28
- package/ROADMAP.md +6 -6
- package/bin/tmct.mjs +56 -0
- package/corpus/README.md +77 -5
- package/corpus/conceptnet/README.md +54 -2
- package/corpus/conceptnet/quality-filter.mjs +95 -0
- package/corpus/conceptnet/slice.jsonl +0 -378
- package/corpus/seon/LICENSE-NOTICE +37 -0
- package/corpus/seon/README.md +121 -0
- package/corpus/seon/concepts.jsonl +238 -0
- package/corpus/seon/definitions.jsonl +288 -0
- package/corpus/tier2/aws.jsonl +39 -0
- package/corpus/tier2/generate.mjs +253 -0
- package/corpus/tier2/java.jsonl +31 -0
- package/corpus/tier2/manifest.json +48 -0
- package/corpus/tier2/python.jsonl +30 -0
- package/data/templates/grammar-rules.toml +18 -10
- package/data/templates/responses.jsonl +2 -0
- package/package.json +10 -2
- package/src/ask-vocab.mjs +19 -1
- package/src/ask.mjs +90 -6
- package/src/chat.mjs +647 -60
- package/src/codegraph.mjs +28 -5
- package/src/conformance.mjs +166 -0
- package/src/corpus/conceptnet.mjs +24 -6
- package/src/grammar/lexicon-core.json +8 -0
- package/src/memory/inspect.mjs +25 -0
- package/src/server.mjs +88 -7
package/src/ask.mjs
CHANGED
|
@@ -245,6 +245,7 @@ function parseComposite(text, nlp) {
|
|
|
245
245
|
const w = splitWords(text);
|
|
246
246
|
const lc = w.map((x) => x.toLowerCase());
|
|
247
247
|
return parseNegation(text, nlp, 0)
|
|
248
|
+
|| parseForwardNegation(w, lc, nlp)
|
|
248
249
|
|| parseAnaphora(w, lc, nlp)
|
|
249
250
|
|| parseAggregate(w, lc, nlp)
|
|
250
251
|
|| parseSuperlative(w, lc, nlp)
|
|
@@ -318,6 +319,54 @@ function parseNegation(text, nlp, depth = 0) {
|
|
|
318
319
|
return complementAst(entityType, { op: "difference", kind: "set", ast: positive });
|
|
319
320
|
}
|
|
320
321
|
|
|
322
|
+
// B1 FORWARD NEGATION (Cycle 5, pron+neg) — the SUBJECT-side complement's mirror: "what
|
|
323
|
+
// does[n't] <subj> <verb>" ("what doesn't it import", "what does app/lib/e.mjs not import")
|
|
324
|
+
// is every individual of the verb's OBJECT grain that <subj> does NOT reach via that verb.
|
|
325
|
+
// Distinct from parseNegation (which negates a queried KIND — "which modules do not import
|
|
326
|
+
// X"): here the negation sits on a FORWARD clause whose subject is a named term or a focus
|
|
327
|
+
// pronoun, so the universe is inferred from the verb's own edges (imports → Module) rather
|
|
328
|
+
// than a stated kind noun. The subject is resolved LATE (at eval, through the same
|
|
329
|
+
// contextId a plain "it" uses), so pronoun-binding composes with the complement for free.
|
|
330
|
+
// Refused honestly (empty) when the verb's object grain is ambiguous or the subject can't
|
|
331
|
+
// resolve — never a guess. Runs AFTER parseNegation, so the stated-kind form is unaffected.
|
|
332
|
+
const FWD_NEG_FRAME = new Set(["what", "which", "thing", "things", "one", "ones", "stuff"]);
|
|
333
|
+
function parseForwardNegation(w, lc, nlp) {
|
|
334
|
+
let i = 0;
|
|
335
|
+
while (i < lc.length && FWD_NEG_FRAME.has(lc[i])) i += 1;
|
|
336
|
+
if (!["do", "does", "did"].includes(lc[i])) return null; // need the auxiliary lead
|
|
337
|
+
i += 1;
|
|
338
|
+
const rest = w.slice(i);
|
|
339
|
+
const restLc = lc.slice(i);
|
|
340
|
+
const notIdx = restLc.indexOf("not");
|
|
341
|
+
if (notIdx < 0) return null; // no negation → not this shape
|
|
342
|
+
const vh = findPhrase(restLc, VERB_TO_KIND);
|
|
343
|
+
if (!vh) return null; // no relation verb → not this shape
|
|
344
|
+
// the subject term is whatever survives after removing "not", the verb phrase, "from",
|
|
345
|
+
// and question scaffolding — a bare pronoun "it" (not a stopword) survives and binds to
|
|
346
|
+
// the focus at eval time; a named module/symbol survives and resolves directly.
|
|
347
|
+
const subjTokens = rest.filter((_, j) => j !== notIdx && (j < vh.start || j >= vh.end)
|
|
348
|
+
&& restLc[j] !== "from" && !STOPWORDS.has(restLc[j]));
|
|
349
|
+
const subjectTerm = subjTokens.join(" ").trim();
|
|
350
|
+
if (!subjectTerm) return null;
|
|
351
|
+
return { node: "forwardComplement", kind: vh.kind, subjectTerm };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** The single OBJECT class a forward relation kind points at across the loaded graph
|
|
355
|
+
* (imports → Module), or null when its objects span more than one class (an ambiguous
|
|
356
|
+
* grain the complement's universe can't be pinned to). Ext: endpoints have no individual,
|
|
357
|
+
* so they don't muddy the class vote. Used by the forwardComplement evaluator to bound
|
|
358
|
+
* the universe it differences the positive forward set out of. */
|
|
359
|
+
function kindObjectClass(graph, kind) {
|
|
360
|
+
const classes = new Set();
|
|
361
|
+
for (const k of kindsFor(kind)) {
|
|
362
|
+
for (const e of edgesOfKind(graph, k)) {
|
|
363
|
+
const o = graph.byId.get(e.object);
|
|
364
|
+
if (o && o.class) classes.add(o.class);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return classes.size === 1 ? [...classes][0] : null;
|
|
368
|
+
}
|
|
369
|
+
|
|
321
370
|
/** A set-producing sub-expression (used for nested inner clauses, boolean branches,
|
|
322
371
|
* and count restrictors): nested first, then the relational/qualifier/boolean
|
|
323
372
|
* parser, then a bare simple clause. Carries `depth` for the nesting cap. */
|
|
@@ -373,12 +422,21 @@ function parseNested(w, lc, nlp, depth) {
|
|
|
373
422
|
* uncompilable), or null. */
|
|
374
423
|
function parseAnaphora(w, lc, nlp) {
|
|
375
424
|
let p = -1;
|
|
425
|
+
let viaOf = false;
|
|
376
426
|
for (let i = 1; i < lc.length; i += 1) {
|
|
377
|
-
if (ANAPHORA_TRIGGERS.includes(lc[i])
|
|
427
|
+
if (!ANAPHORA_TRIGGERS.includes(lc[i])) continue;
|
|
428
|
+
if (lc[i - 1] === "of") { p = i; viaOf = true; break; } // "how many of those", "which of them"
|
|
429
|
+
// BARE anaphoric pronoun as the FINAL word, directly after a count/list trigger
|
|
430
|
+
// ("count them", "count those", "list them") — the discourse-reference count/list over
|
|
431
|
+
// the previous answer with no "of" (Cycle 5, disc+count). Pinned to the terminal
|
|
432
|
+
// position so a mid-sentence "these"/"those" used as a determiner ("list these
|
|
433
|
+
// functions") is left for the ordinary list/clause path, not seized as an anaphor.
|
|
434
|
+
const headSoFar = lc.slice(0, i).join(" ");
|
|
435
|
+
if (i === lc.length - 1 && (AGGREGATE_TRIGGERS.includes(headSoFar) || LIST_TRIGGERS.includes(headSoFar))) { p = i; break; }
|
|
378
436
|
}
|
|
379
437
|
if (p < 0) return null;
|
|
380
|
-
const head = lc.slice(0, p - 1).join(" ");
|
|
381
|
-
const mode = /^(how many|how much|count)\b/.test(head) ? "count" : "list";
|
|
438
|
+
const head = (viaOf ? lc.slice(0, p - 1) : lc.slice(0, p)).join(" ");
|
|
439
|
+
const mode = AGGREGATE_TRIGGERS.includes(head) || /^(how many|how much|count|number|quantity|total)\b/.test(head) ? "count" : "list";
|
|
382
440
|
const filter = parsePredicateFilter(w.slice(p + 1), nlp);
|
|
383
441
|
if (filter === undefined) return { node: "miss", reason: "the follow-up filter didn't parse" };
|
|
384
442
|
return { node: "anaphora", mode, filter };
|
|
@@ -772,6 +830,16 @@ function evalSet(graph, ast, opts) {
|
|
|
772
830
|
const subs = new Set(kindsFor(ast.kind).flatMap((k) => edgesOfKind(graph, k)).map((e) => e.subject));
|
|
773
831
|
return graph.individuals.filter((i) => subs.has(i.id) && (!ast.entityType || i.class === ast.entityType));
|
|
774
832
|
}
|
|
833
|
+
// forward complement: the verb's object-grain universe MINUS what the (late-resolved,
|
|
834
|
+
// focus-bindable) subject reaches via that verb — "what doesn't it import".
|
|
835
|
+
case "forwardComplement": {
|
|
836
|
+
const r = resolveTermOrContext(graph, ast.subjectTerm, opts && opts.contextId);
|
|
837
|
+
if (!r.match) return []; // unresolved subject / focus-less pronoun → honest empty
|
|
838
|
+
const universeType = kindObjectClass(graph, ast.kind);
|
|
839
|
+
if (!universeType) return []; // ambiguous object grain → refuse honestly
|
|
840
|
+
const positive = new Set(forwardOverSet(graph, ast.kind, new Set([r.match.id])).map((x) => x.id));
|
|
841
|
+
return graph.individuals.filter((i) => i.class === universeType && !positive.has(i.id));
|
|
842
|
+
}
|
|
775
843
|
case "reverseSet": {
|
|
776
844
|
const ids = new Set(evalSet(graph, ast.inner, opts).map((i) => i.id));
|
|
777
845
|
return reverseOverSet(graph, ast.kind, ast.entityType, ids);
|
|
@@ -1401,11 +1469,23 @@ export function traverse(graph, parsed, { contextId = null, prev = null } = {})
|
|
|
1401
1469
|
};
|
|
1402
1470
|
}
|
|
1403
1471
|
|
|
1404
|
-
// reverse: "which <entityType> R <objMatch>"
|
|
1472
|
+
// reverse: "which <entityType> R <objMatch>". GRAIN-AWARE (Cycle 5, lever 3): a kind
|
|
1473
|
+
// that carries a symbol-grain sibling reads off the SIBLING when a fine SUBJECT grain
|
|
1474
|
+
// was asked for ("which functions call X" → callsSymbol). It ALSO reads off the sibling
|
|
1475
|
+
// when the RESOLVED OBJECT is itself a fine symbol, for EVERY kind with a sibling — not
|
|
1476
|
+
// only touches: the module-coarse edge (calls Module→Module, touches Commit→Module) can
|
|
1477
|
+
// NEVER point at a function/method, so a bare "what calls fnAlpha" scanning the coarse
|
|
1478
|
+
// `calls` edges returned a FALSE empty ("No modules found …") while the graph records a
|
|
1479
|
+
// real symbol-level caller (Widget.render --callsSymbol--> fnAlpha). The honest answer
|
|
1480
|
+
// reads off callsSymbol at symbol grain; a truly-uncalled symbol still renders the
|
|
1481
|
+
// honest empty, now with the accurate callsSymbol receipt. (Previously scoped to touches
|
|
1482
|
+
// only, which left this exact callsSymbol caller invisible — a genuine correctness bug.)
|
|
1405
1483
|
const symbolKind = SYMBOL_GRAIN_SIBLING[kind];
|
|
1406
|
-
|
|
1484
|
+
const objIsFineSymbol = !!(objMatch.class && FINE_ENTITY_TYPES.has(objMatch.class));
|
|
1485
|
+
if (symbolKind && (FINE_ENTITY_TYPES.has(entityType) || objIsFineSymbol)) {
|
|
1407
1486
|
const edges = edgesOfKind(graph, symbolKind).filter((e) => e.object === objMatch.id);
|
|
1408
|
-
const
|
|
1487
|
+
const subjects = uniqueById(edges.map((e) => graph.byId.get(e.subject)).filter(Boolean));
|
|
1488
|
+
const matches = (!entityType || entityType === "Change") ? subjects : subjects.filter((i) => i.class === entityType);
|
|
1409
1489
|
return { matches, objMatch, candidates, traversal: `${symbolKind} edges where object = ${objMatch.label}`, ambiguous, matchedVia };
|
|
1410
1490
|
}
|
|
1411
1491
|
|
|
@@ -1662,6 +1742,10 @@ function renderCore(parsed, result) {
|
|
|
1662
1742
|
miss: true, ambiguous: false,
|
|
1663
1743
|
};
|
|
1664
1744
|
}
|
|
1745
|
+
// NOTE (Cycle 5): a voice-nit rephrasing ("that directly <verb>") was reverted —
|
|
1746
|
+
// the frozen v1 cases.jsonl pins the "whose module directly <verb>s X" wording
|
|
1747
|
+
// (hm-empty-result-calls / tf-wat-calls / ns-wondering), and the case set is
|
|
1748
|
+
// append-only/sacred mid-arc, so the honest-miss phrasing stays as-is.
|
|
1665
1749
|
const entityWord = nounFor(parsed.entityType || "Module", 2);
|
|
1666
1750
|
return {
|
|
1667
1751
|
content: `No ${entityWord} found whose module directly ${verbFor(parsed.kind)} ${parsed.object}. (traversal: ${result.traversal || "no traversal resolved"})`,
|