@polycode-projects/the-mechanical-code-talker 0.9.11 → 1.0.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/ROADMAP.md CHANGED
@@ -890,6 +890,21 @@ stop signs (full detail + full citation lists in each owning doc):
890
890
  nobody has published the specific combination with tmct's multi-trust-tier, hard-budget
891
891
  requirement. Speculative angle: an ATMS-lite extension to `syllogise.mjs`'s currently-flat
892
892
  provenance tag, sketched but unbuilt.
893
+ - **A shared ~2M-word cross-domain ontology (1M general-English base + 1M
894
+ technical/scientific/engineering/programming-language/slang)** — `PLAN_ontology-hierarchies.md`
895
+ §7, additive to (not a revision of) that doc's existing track (e), which stays about importing
896
+ raw WordNet into tmct's own small tier-1 corpus specifically. Walked into, not avoided: merging
897
+ two 1M-word vocabularies collides senses of lexically-shared words (`class`, `cache`, `thread`,
898
+ `wave`, `cell`, `field`, `state`, …) across general/CS/physics/biology/slang registers —
899
+ knowledge-based (non-neural) WSD is real but measurably weaker than supervised/neural WSD (Lesk
900
+ 1986; Raganato, Camacho-Collados & Navigli, EACL 2017), and BabelNet (Navigli & Ponzetto,
901
+ *Artificial Intelligence* 193, 2012) proves automatic cross-resource sense merging at this scale
902
+ is achievable — but its own pipeline moved toward statistical/graph-ML methods as it scaled,
903
+ solves the cross-*lingual* not cross-*domain* axis, and carries a non-commercial licence, so it
904
+ is a precedent, not a usable vehicle. Speculative angle recorded: mutual disambiguation from
905
+ already-resolved neighbouring terms in tmct's own closed graph (a structurally-bounded,
906
+ deterministic reading of Gale/Church/Yarowsky's "one sense per discourse/collocation"
907
+ regularities) — not published anywhere found for this application.
893
908
 
894
909
  Every item above is honestly labeled speculative — a direction recorded so it isn't
895
910
  re-discovered from scratch, not a committed build plan. None of it is scheduled; the phases above
@@ -6,3 +6,4 @@
6
6
  {"relation":"defines","definition":"A definition is where a name (a class, function, or variable) is introduced.","sense":"software"}
7
7
  {"relation":"touches","definition":"A touch is a commit changing a file or a symbol in the codebase.","sense":"software"}
8
8
  {"relation":"cochange","definition":"Change-coupling is two files that tend to be changed together in the same commits.","sense":"software"}
9
+ {"relation":"reexports","definition":"A re-export is a module passing another module's definition through as part of its own public API.","sense":"software"}
@@ -60,10 +60,12 @@
60
60
  {"id":"conversational-greeting-good-evening","class":"conversational","register":"friendly","template":"Good evening. Ask me about this codebase, or /help."}
61
61
  {"id":"conversational-thanks","class":"conversational","register":"friendly","template":"Any time. Ask another, or /help for what I can do."}
62
62
  {"id":"conversational-farewell","class":"conversational","register":"friendly","template":"Bye — flushing the session log. Come back with a question any time."}
63
- {"id":"orientation-friendly","class":"orientation","register":"friendly","template":"I answer questions about THIS codebase's structure — imports, calls, definitions,\nhistory and counts. For example:\n which modules import {example1}\n what calls {example2}\n how many classes are there\n/help for commands, /stats for an overview of the graph."}
63
+ {"id":"orientation-friendly","class":"orientation","register":"friendly","template":"I'm tmct — a deterministic, offline code-graph assistant (no LLM). I answer questions about THIS codebase's structure — imports, calls, definitions,\nhistory and counts. For example:\n which modules import {example1}\n what calls {example2}\n how many classes are there\n/help for commands, /stats for an overview of the graph."}
64
64
  {"id":"miss-no-previous-answer","class":"miss","register":"friendly","template":"No previous answer to expand yet — ask me a question first, then say \"why\" or \"say more\"."}
65
- {"id":"conversational-greeting-empty","class":"conversational","register":"friendly","template":"Hi. There's no code graph loaded here — for code structure (imports, calls, definitions) I need a `.tmct/graph.json`: point me at one with `--repo <path>`, or try the shipped example `npm run example:mini`. (tmct reads graphs; it doesn't index code itself.) For general vocabulary, `tmct init` seeds concepts — try \"what is a cache\". /help for commands."}
66
- {"id":"orientation-empty","class":"orientation","register":"friendly","template":"There's no code graph loaded here, so I can't answer structure questions (imports, calls, definitions) yet.\nFor those I need a `.tmct/graph.json` produced by a graph producer — point me at one with `--repo <path>`, or try the shipped example `npm run example:mini`. tmct reads graphs; it doesn't index code itself.\nFor general vocabulary, `tmct init` seeds concepts — try \"what is a cache\". /help for commands, /memory for what I remember."}
65
+ {"id":"conversational-greeting-empty","class":"conversational","register":"friendly","template":"Hi. I'm tmct. {vocabHint} Point me at a repo with `--repo <path>` for code-structure questions too (imports, calls, definitions). /help for commands."}
66
+ {"id":"orientation-empty","class":"orientation","register":"friendly","template":"I'm tmct — a deterministic, offline chat assistant (no LLM). {vocabHint} /memory for what I remember.\nFor code structure (imports, calls, definitions) point me at a repo: `--repo <path>`, or try the shipped example `npm run example:mini`. tmct reads graphs; it doesn't index code itself.\n/help for commands."}
67
+ {"id":"identity-self","class":"conversational","register":"friendly","template":"I'm tmct — a deterministic, offline chat assistant. No LLM: wink-nlp parsing over a seeded ontology/lexicon, plus a code graph when you point me at a repo with `--repo <path>`. /help for commands, /stats for an overview."}
68
+ {"id":"identity-not-an-llm","class":"conversational","register":"friendly","template":"No — no LLM involved. tmct is deterministic: wink-nlp parsing over a graph/ontology, not a language model. /help for commands."}
67
69
  {"id":"technical-density","class":"count","register":"technical","template":"{subject} carries {count} {noun} across {scope} — a concentration well above what a codebase of this size typically sustains ({provenance})."}
68
70
  {"id":"technical-comparison","class":"count","register":"technical","template":"At {count} {noun}, {subject} sits {comparison} the comparable-project baseline, a divergence that reflects deliberate structure rather than measurement noise ({provenance})."}
69
71
  {"id":"technical-superlative","class":"count","register":"technical","template":"No {noun} in {scope} is more {metric} than {subject}; it leads the next candidate by a clear margin of {count} ({provenance})."}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "0.9.11",
3
+ "version": "1.0.0",
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-vocab.mjs CHANGED
@@ -316,6 +316,10 @@ export const MISSPELLINGS = Object.freeze({
316
316
  "extands": "extends", "extneds": "extends",
317
317
  "depnds": "depends",
318
318
  "touchs": "touches", "tuoches": "touches", "touhced": "touched",
319
+ // WHERE_MARKERS typo (0.9.13 Tier-1 playtest): "defined" itself had no typo
320
+ // entry, so "where is it defned" fell through to the bare-object search path
321
+ // instead of the where-shape ("no module matching 'it defned' found").
322
+ "defned": "defined",
319
323
  "chagned": "changed", "chnaged": "changed",
320
324
  "chagnes": "changes", "chnages": "changes",
321
325
  "calles": "calls",
@@ -323,6 +327,16 @@ export const MISSPELLINGS = Object.freeze({
323
327
  "tets": "tests",
324
328
  // grammar anchor words
325
329
  "whcih": "which", "wich": "which", "whihc": "which",
330
+ // "wehre"/"whre" (0.9.13 Tier-1 playtest, "where is it defined" drill-down):
331
+ // the WHERE-DEFINED shape's own anchor word had NO typo tolerance at all
332
+ // (unlike which/what/does/the above), so a plain dropped/transposed letter
333
+ // fell straight through resolveObject and hit either the grammar wall or a
334
+ // bogus "no module matching 'it defined'" search. "were" (the missing-h
335
+ // homophone slip) is NOT curated here — it's a real word already load-bearing
336
+ // as the TEMPORAL_AUX auxiliary ("when were the modules last touched"), so
337
+ // that one typo is handled by its own anchored phrasing frame instead
338
+ // (normalize.mjs PHRASING_FRAMES) to avoid clobbering the legitimate reading.
339
+ "wehre": "where", "whre": "where",
326
340
  // "wat" (chatbench cycle 2, tf-wat-calls): the internet-casual spelling of
327
341
  // "what" — neither curated noise nor a restorable trigger typo, so "wat calls
328
342
  // fnAlpha" used to die as "couldn't resolve one of the terms". Restored here
@@ -381,12 +395,21 @@ export const G_DROP = /\b([a-z]{3,})in'/gi;
381
395
  /** Words stripped during normalization/keyword-spotting once they carry no
382
396
  * grammatical weight for this grammar — greetings, politeness, hedges, and
383
397
  * the discourse fillers a spoken-style question picks up. Never strips a
384
- * relation verb, entity noun, or modifier (those are checked first). */
398
+ * relation verb, entity noun, or modifier (those are checked first).
399
+ * Bare "you" (0.9.14 Tier-2 playtest, §3b ESL angle): a non-native word-order
400
+ * slip ("please you tell me what is Class", "you tell me what is Class") left
401
+ * a leading "you" that "could you"/"can you"/"would you" don't cover (those
402
+ * are anchored WRAPPERS requiring the verb-first order) — the leftover
403
+ * pronoun broke the bare "what is Class" no-article count reading, which
404
+ * (unlike the "what is a Class" meta form) requires the WHOLE normalized
405
+ * string to match, not just a substring. "you" carries no grammatical weight
406
+ * in this code-graph grammar (never a real entity/relation term), so it is
407
+ * safe to strip anywhere, same trade as every other word in this list. */
385
408
  export const FILLER_WORDS = Object.freeze([
386
409
  "um", "uh", "erm", "so", "like", "yo", "hey", "bru", "bro", "fam", "mate",
387
410
  "please", "could you", "can you", "would you", "tell me", "i wonder",
388
411
  "just wondering", "quickly", "real quick", "kinda", "sorta",
389
- "btw", "by the way",
412
+ "btw", "by the way", "you",
390
413
  ]);
391
414
 
392
415
  /** Deictic/pronoun terms that refer to a context entity rather than naming one
package/src/ask.mjs CHANGED
@@ -280,7 +280,16 @@ const MAX_COMPOSE_DEPTH = 4;
280
280
  // treats it as an ordinary object term rather than dropping it.
281
281
  const NEST_SENTINEL = "zzinnerset";
282
282
  // Filler words dropped at the front of a relative predicate / anaphora filter.
283
- const PRED_LEAD_SKIP = new Set(["that", "which", "who", "are", "is", "was", "were", "do", "does", "also", "still", "both", "and"]);
283
+ // "then"/"though" (Tier-2 playtest, 5th pass): a trailing discourse tag on an
284
+ // otherwise-bare anaphora follow-up — "how many of those THEN", "which of
285
+ // them THOUGH" — used to be read as an (uncompilable) filter clause instead
286
+ // of being dropped as filler, so the follow-up MISSED at PARSE time with a
287
+ // generic "the follow-up filter didn't parse" instead of reaching the
288
+ // friendly eval-time "needs a previous answer" nudge when there was truly no
289
+ // prior set (or the correct count/list when there was one) — the exact same
290
+ // discourse-tag tolerance WHAT_ABOUT_RE already carries for "what about X
291
+ // then"/"what about X though".
292
+ const PRED_LEAD_SKIP = new Set(["that", "which", "who", "are", "is", "was", "were", "do", "does", "also", "still", "both", "and", "then", "though"]);
284
293
  const FRAME_WORDS = new Set(["which", "what", "who", "list", "show", "find", "give", "me", "us", "all"]);
285
294
 
286
295
  const entityNoun = (w) => (ENTITY_TO_TYPE[w] ? { entityType: ENTITY_TO_TYPE[w], placeholder: false }
@@ -519,6 +528,19 @@ function parseTemporal(w, lc, nlp, depth = 0) {
519
528
  * fires). Returns a {node:"anaphora"} (mode count|list), a miss (filter present but
520
529
  * uncompilable), or null. */
521
530
  function parseAnaphora(w, lc, nlp) {
531
+ // "which ones"/"which one" — a bare anaphoric re-LIST of the previous result set,
532
+ // phrased as a QUESTION rather than the imperative ("list them") or pronoun-tail
533
+ // ("count them") shapes the loop below already covers. Found live (0.9.14 Tier-2
534
+ // playtest, third pass, numeric/quantifier relation touches): after "how many
535
+ // modules import app/lib/a.mjs" / "which modules import app/lib/a.mjs", the
536
+ // completely natural follow-up "which ones" fell straight through to the generic
537
+ // orientation card — "ones" isn't an ANAPHORA_TRIGGERS pronoun and bare "which"
538
+ // isn't a LIST_TRIGGERS head, so neither existing branch below ever fires for it.
539
+ // Pinned to the WHOLE query (exactly two words) so it never shadows an ordinary
540
+ // "which one of these two functions …" clause, which has more words after "one".
541
+ if (lc.length === 2 && lc[0] === "which" && (lc[1] === "ones" || lc[1] === "one")) {
542
+ return { node: "anaphora", mode: "list", filter: { type: "all" } };
543
+ }
522
544
  let p = -1;
523
545
  let viaOf = false;
524
546
  for (let i = 1; i < lc.length; i += 1) {
@@ -564,10 +586,16 @@ function parsePredicateFilter(words, nlp) {
564
586
  * and it must NOT be mistaken for a restrictor (that's the exact bug behind "how many
565
587
  * classes are there" → the count-restrictor miss). Combined with STOPWORDS (which
566
588
  * already carries are/there/is/in/the/…) at the call site, so only the non-stopword
567
- * extras live here. A tail with ANY word outside this ∪ STOPWORDS is a real restrictor. */
589
+ * extras live here. A tail with ANY word outside this ∪ STOPWORDS is a real restrictor.
590
+ * "this"/"that" (0.9.14 Tier-2 playtest): "which classes exist IN THIS codebase"/
591
+ * "which methods exist in this codebase" used to miss — "codebase" alone was already
592
+ * filler, but the demonstrative right before it ("this"/"that") is not a STOPWORDS
593
+ * entry, so the tail read as non-filler and the whole thing fell through to the
594
+ * grammar wall instead of the bare list. */
568
595
  const AGG_TAIL_FILLER = new Set([
569
596
  "total", "altogether", "overall", "exist", "exists", "existing", "present",
570
597
  "here", "now", "currently", "graph", "index", "codebase", "repo", "repository",
598
+ "this", "that",
571
599
  ]);
572
600
 
573
601
  /** AGGREGATE / COUNT: "how many <entity> [<restrictor>]", "count <entity>",
@@ -605,6 +633,12 @@ const LIST_SKIP = new Set(["the", "a", "an", "all", "me", "us"]);
605
633
  const LIST_TRIGGERS_SORTED = [...LIST_TRIGGERS].sort((a, b) => b.split(" ").length - a.split(" ").length);
606
634
  // The listable node classes, named in the honest miss and the empty-index message.
607
635
  const LISTABLE_KINDS = "functions, classes, methods, modules, attributes, variables, or commits";
636
+ // SCOPE PREPOSITIONS (HANDOVER item 12.2/11.1): a leading "in"/"inside"/"under" right
637
+ // after the entity noun (past an optional copula) is an unambiguous LOCATION-SCOPE
638
+ // tail, never a reverse-clause predicate object — "which modules import X" has no
639
+ // preposition there at all. Narrowly scoped to just these three words so the
640
+ // interrogative exception below can't be mistaken for a general tail-acceptance.
641
+ const SCOPE_PREPOSITIONS = new Set(["in", "inside", "under"]);
608
642
 
609
643
  /** LIST: "list <kind>", "show me the <kind>s", "what are the <kind>", "list <kind> in
610
644
  * <module>". A sibling of the count node — it enumerates the individuals of a class
@@ -616,7 +650,14 @@ const LISTABLE_KINDS = "functions, classes, methods, modules, attributes, variab
616
650
  * (reusing membership/relational/boolean), and its `scoped` flag suppresses the
617
651
  * "narrow with …" hint. An unknown kind after a clear imperative trigger ("list
618
652
  * bananas") is an honest miss naming the listable kinds; anything less certain falls
619
- * through (null) to the existing parser/cascade rather than guessing. */
653
+ * through (null) to the existing parser/cascade rather than guessing.
654
+ *
655
+ * A single narrowly-scoped EXCEPTION to the interrogative gate (HANDOVER item
656
+ * 12.2/11.1): "what/which <kind> [is|are] in|inside|under <scope>" — a leading scope
657
+ * preposition, past an optional copula, straight after the entity noun — is routed the
658
+ * SAME way the imperative "list <kind> in <scope>" already is. This does NOT widen the
659
+ * general decline: "which modules import X"/"which functions call X" have a VERB there,
660
+ * not a scope preposition, so they still fall through untouched. */
620
661
  function parseList(w, lc, nlp, depth) {
621
662
  let i = 0;
622
663
  let interrogative = false;
@@ -650,18 +691,33 @@ function parseList(w, lc, nlp, depth) {
650
691
  i += 1;
651
692
  const tail = w.slice(i);
652
693
  const tailMeaningful = lc.slice(i).some((t) => !STOPWORDS.has(t) && !AGG_TAIL_FILLER.has(t));
694
+ // SCOPE-PREPOSITION exception (HANDOVER 12.2/11.1): past an optional copula
695
+ // ("is"/"are"), does the tail open with "in"/"inside"/"under"? If so this is a
696
+ // location-scope tail ("what modules ARE IN app/lib"), not a reverse-clause
697
+ // predicate — strip only the copula (the imperative form never has one: "list
698
+ // modules in app/lib") so the membership-tail text this builds below is byte-
699
+ // identical in shape to what the imperative path already builds.
700
+ let scopeTailLc = lc.slice(i);
701
+ let scopeTailWords = tail;
702
+ if (scopeTailLc[0] === "is" || scopeTailLc[0] === "are") {
703
+ scopeTailLc = scopeTailLc.slice(1);
704
+ scopeTailWords = scopeTailWords.slice(1);
705
+ }
706
+ const scopedException = interrogative && SCOPE_PREPOSITIONS.has(scopeTailLc[0]);
653
707
  // The bare interrogative "what/which <kind>" is a list ONLY with an explicit
654
- // list-confirming filler tail ("… are there", "… that exist"): a real predicate
655
- // ("which functions call X") is a reverse query, and a *bare* "which methods" is left
656
- // alone deliberately otherwise the relaxation cascade could drop an unknown
657
- // qualifier ("which shiny methods" → "which methods") and silently list everything,
658
- // erasing the honest "unknown qualifier" miss. Imperative triggers ("list methods")
659
- // carry their own list intent, so they need no such tail.
660
- if (interrogative && (tailMeaningful || tail.length === 0)) return null;
708
+ // list-confirming filler tail ("… are there", "… that exist") or the scoped
709
+ // exception just above: a real predicate ("which functions call X") is a reverse
710
+ // query, and a *bare* "which methods" is left alone deliberately — otherwise the
711
+ // relaxation cascade could drop an unknown qualifier ("which shiny methods" →
712
+ // "which methods") and silently list everything, erasing the honest "unknown
713
+ // qualifier" miss. Imperative triggers ("list methods") carry their own list
714
+ // intent, so they need no such tail.
715
+ if (interrogative && ((tailMeaningful && !scopedException) || tail.length === 0)) return null;
661
716
  let base;
662
717
  let scoped = false;
663
718
  if (tailMeaningful) {
664
- const setAst = parseSetPhrase(`which ${[...quals, entWord, ...tail].join(" ")}`, nlp, (depth || 0) + 1);
719
+ const useTail = scopedException ? scopeTailWords : tail;
720
+ const setAst = parseSetPhrase(`which ${[...quals, entWord, ...useTail].join(" ")}`, nlp, (depth || 0) + 1);
665
721
  if (!setAst || setAst.node === "miss") return { node: "miss", reason: (setAst && setAst.reason) || "the list filter didn't parse" };
666
722
  base = setAst;
667
723
  scoped = true;