@polycode-projects/the-mechanical-code-talker 1.8.17 → 1.8.20

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/ask.mjs +38 -16
  3. package/src/chat.mjs +33 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "1.8.17",
3
+ "version": "1.8.20",
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/ask.mjs CHANGED
@@ -3514,22 +3514,44 @@ export function traverse(graph, parsed, { contextId = null, prev = null, pinnedO
3514
3514
  widenNote = `, widened to ${siblingClass} subjects (no ${entityType} recorded)`;
3515
3515
  }
3516
3516
  }
3517
- // "touches" up-refine composability (HANDOVER 2026-07-12, Class-to-module
3518
- // up-refinement): an EMPTY touchesSymbol lookup on a resolved CLASS is not
3519
- // decisive the way it is for a Function/Method a Class reads naturally as
3520
- // "the file/unit that holds it", so "who touched <Class>" with no recorded
3521
- // symbol-precise touch should still answer from the class's containing
3522
- // module's real touches, rather than a confident-looking-but-possibly-wrong
3523
- // "nothing touched it". Fall through to the grain-aware up-refine below
3524
- // ONLY for that Class case. Deliberately NOT widened to every
3525
- // FINE_ENTITY_TYPES member: "how many commits touched fnAlpha" (a Function)
3526
- // is pinned elsewhere (ask-combo.test.mjs's grain-aware COUNT lever) to stay
3527
- // an honest 0 rather than a module-grain false hit symbol-level counting
3528
- // precision for functions/methods is a deliberate, separate guarantee this
3529
- // change must not erode. `calls` is untouched either way: an empty
3530
- // callsSymbol result stays decisive (call parsing isn't a best-effort
3531
- // heuristic the way commit-diff symbol attribution is).
3532
- if (matches.length || !(kind === "touches" && objMatch.class === "Class")) {
3517
+ // "touches"/"calls" up-refine composability (HANDOVER 2026-07-12, Class-to-
3518
+ // module up-refinement; extended to `calls` in the 2026-07-12 follow-up
3519
+ // "who calls Router" wrongly returned empty for a Class whose calls are only
3520
+ // recorded at module-coarse grain): an EMPTY touchesSymbol/callsSymbol lookup
3521
+ // on a resolved CLASS is not decisive the way it is for a Function/Method — a
3522
+ // Class reads naturally as "the file/unit that holds it", so "who touched
3523
+ // <Class>"/"who calls <Class>" with no recorded symbol-precise edge should
3524
+ // still answer from the class's containing module's real touches/calls,
3525
+ // rather than a confident-looking-but-possibly-wrong "nothing touched/calls
3526
+ // it". Deliberately NOT widened to every FINE_ENTITY_TYPES member: "how many
3527
+ // commits touched fnAlpha" (a Function) is pinned elsewhere (ask-combo.test.mjs's
3528
+ // grain-aware COUNT lever) to stay an honest 0 rather than a module-grain
3529
+ // false hit symbol-level counting precision for functions/methods is a
3530
+ // deliberate, separate guarantee this change must not erode.
3531
+ //
3532
+ // Up-refine eligibility is gated on the Class having NO recorded `contains`
3533
+ // members: a class the extractor actually populated (Widget, Logger — both
3534
+ // carry real `contains` edges) reads as a fully-modeled unit whose own empty
3535
+ // symbol-grain scan IS the honest answer; a class with zero recorded members
3536
+ // (Router, Button — the extractor only ever saw its declaration, never a
3537
+ // body) is exactly the shape where the coarser module-level edge is the only
3538
+ // real signal recorded at all.
3539
+ //
3540
+ // Even when eligible, only actually fall through to the shared grain-aware
3541
+ // up-refine block below when a containing module can be resolved RIGHT NOW.
3542
+ // That block's own "no containing module found" case renders a DIFFERENT,
3543
+ // wrongGrainMiss-shaped honest miss ("'Button' resolved to the class Button,
3544
+ // but this question needs a module…") than the plain zero-match miss this
3545
+ // symbol-grain scan already renders ("No modules found whose module directly
3546
+ // calls Button…"). A Class with no `contains` members AND no `defines` edge
3547
+ // naming its module (the synthetic chatflow-tier2 fixture's Button, which the
3548
+ // extractor recorded a bare declaration for but never wired into `defines`)
3549
+ // must keep the latter, plain-miss wording — eligibility alone doesn't
3550
+ // guarantee the up-refine can actually complete.
3551
+ const upRefineEligible = (kind === "touches" || kind === "calls") && objMatch.class === "Class"
3552
+ && !edgesOfKind(graph, "contains").some((e) => e.subject === objMatch.id);
3553
+ const upRefineModule = upRefineEligible ? graph.byId.get(moduleIdOf(graph, objMatch) || "") : null;
3554
+ if (matches.length || !(upRefineEligible && upRefineModule)) {
3533
3555
  return { matches, objMatch, candidates, traversal: `${symbolKind} edges where object = ${objMatch.label}${widenNote}`, ambiguous, matchedVia };
3534
3556
  }
3535
3557
  }
package/src/chat.mjs CHANGED
@@ -4501,6 +4501,13 @@ const FACT_ANSWER_CAP = 32;
4501
4501
  const CAN_ASK_RE = /^(?:can|could)\s+(?:an?\s+)?([\w'-]+(?:\s+[\w'-]+)*?)\s+([a-z]+)[?.!\s]*$/i;
4502
4502
  const WHAT_CAN_DO_RE = /^what\s+can\s+(?:an?\s+)?(.+?)\s+do[?.!\s]*$/i;
4503
4503
  const WHAT_HAS_RE = /^what\s+has\s+(?:an?\s+)?(.+?)[?.!\s]*$/i;
4504
+ // "what is used for riding" / "what can be used for riding" / "what is for
4505
+ // riding" — the reverse-by-object mirror of BUG 1's forward reader ("what is
4506
+ // a tree used for"), missing until now: BUG 1 only ever filtered a KNOWN
4507
+ // subject's facts down to mgx:usedFor; nothing answered the reverse question
4508
+ // (object known, subject unknown), so it fell all the way through to the
4509
+ // code-graph miss cascade — actively misleading for a pure vocabulary query.
4510
+ const WHAT_USED_FOR_RE = /^what\s+(?:(?:can\s+be|is)\s+used\s+for|is\s+for)\s+(.+?)[?.!\s]*$/i;
4504
4511
  // Widened 2026-07-11 (live-caught follow-up to the ambiguousParse fix, commit
4505
4512
  // 5c858bf): on the FIRST turn of a graph-less session, dispatchTool's
4506
4513
  // loadGraph() throws its own documented "the graph is empty... this repo
@@ -4548,6 +4555,32 @@ async function factAnswer(memoryDir, query, envelope, miss, biasByBundle = {}) {
4548
4555
  try { ({ normFactTerm } = await import("./memory/core.mjs")); } catch { return null; }
4549
4556
  const q = String(query).trim();
4550
4557
 
4558
+ // (a-pre) "what is used for riding" / "what can be used for riding" / "what
4559
+ // is for riding" — the reverse-by-OBJECT mirror of BUG 1's forward reader
4560
+ // ("what is a tree used for", the (a) block just below). Checked BEFORE (a)
4561
+ // deliberately: this phrasing's leading "what is …" ALSO matches (a)'s own
4562
+ // BARE_WHATIS_RE, which would otherwise greedily treat "used for riding" as
4563
+ // one literal term to define — a guaranteed miss, since no vocabulary term
4564
+ // is ever named "used for riding" — and (a) always returns (hit, honest
4565
+ // per-relation "no", or null), never falling through to a later reader. Only
4566
+ // takes over when it finds a REAL hit; a non-match or zero-hit case falls
4567
+ // through unchanged to (a) and beyond, so ordinary miss messaging is
4568
+ // untouched. mgx:usedFor instead of mgx:hasA; same filter/rank/render/
4569
+ // paginate recipe as (b4)'s WHAT_HAS_RE below.
4570
+ const usedForQ = q.match(WHAT_USED_FOR_RE);
4571
+ if (usedForQ) {
4572
+ const variants = factTermVariants(normFactTerm, usedForQ[1]);
4573
+ const hits = (await factRows(memoryDir)).filter((f) => f.predicate === "mgx:usedFor" && variants.has(f.object));
4574
+ if (hits.length) {
4575
+ const ranked = rankByBiasThenTrust(uniqueFacts(hits), biasByBundle);
4576
+ const lines = ranked.map(renderFactLine);
4577
+ const shown = lines.slice(0, FACT_ANSWER_CAP);
4578
+ const rest = lines.slice(FACT_ANSWER_CAP);
4579
+ const extra = rest.length ? `\n…and ${rest.length} more — say 'more' to see them.` : "";
4580
+ return { text: shown.join("\n") + extra, replace: true, ...(rest.length ? { pending: { items: rest, noun: "facts" } } : {}) };
4581
+ }
4582
+ }
4583
+
4551
4584
  // (a) meta-shaped questions ("what is a module", "what does cache mean") — the
4552
4585
  // parsed object term, matched against fact SUBJECTS; consulted for hits (append
4553
4586
  // alongside the schema-docs answer) and misses (facts answer alone) alike.