@polycode-projects/the-mechanical-code-talker 0.9.12 → 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/corpus/seon/relations.jsonl +1 -0
- package/package.json +1 -1
- package/src/ask-vocab.mjs +25 -2
- package/src/ask.mjs +67 -11
- package/src/chat.mjs +590 -56
- package/src/concept.mjs +50 -9
- package/src/finish.mjs +1 -1
- package/src/interpret/normalize.mjs +90 -1
|
@@ -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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "0.
|
|
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
|
-
|
|
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")
|
|
655
|
-
// ("which functions call X") is a reverse
|
|
656
|
-
//
|
|
657
|
-
// qualifier ("which shiny methods" →
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
|
|
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
|
|
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;
|