@polycode-projects/seonix 0.9.0 → 0.10.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.
@@ -50,6 +50,8 @@
50
50
  };
51
51
  var createRequire2 = unavailable2("createRequire");
52
52
  var readFileSync2 = unavailable2("readFileSync");
53
+ var join = (...a) => a.join("/");
54
+ var dirname = (p) => String(p).replace(/\/[^/]*$/, "");
53
55
  var randomBytes2 = unavailable2("randomBytes");
54
56
 
55
57
  // node-stub:node:url
@@ -58,6 +60,7 @@
58
60
  };
59
61
  var createRequire3 = unavailable3("createRequire");
60
62
  var readFileSync3 = unavailable3("readFileSync");
63
+ var fileURLToPath = (u) => String(u);
61
64
  var randomBytes3 = unavailable3("randomBytes");
62
65
 
63
66
  // node_modules/@polycode-projects/the-mechanical-code-talker/src/codegraph.mjs
@@ -949,12 +952,62 @@
949
952
  );
950
953
  var MISSPELLING_RE = correctionRe(MISSPELLINGS);
951
954
  var WRONG_WORD_RE = correctionRe(WRONG_WORDS);
955
+ var RELATION_VERB_RE = new RegExp(
956
+ "\\b(?:" + Object.keys(VERB_TO_KIND).sort((a, b) => b.length - a.length).map(escapeRegex).join("|") + ")\\b",
957
+ "i"
958
+ );
959
+ var INTERROGATIVE_LEAD_RE = /^(?:which|what|who|whose|where|when|why|how)\b/i;
960
+ var LISTING_TAIL_KINDS = /* @__PURE__ */ new Set([
961
+ "modules",
962
+ "files",
963
+ "functions",
964
+ "methods",
965
+ "classes",
966
+ "attributes",
967
+ "fields",
968
+ "properties",
969
+ "variables",
970
+ "globals",
971
+ "commits",
972
+ "changes",
973
+ "tests",
974
+ "members"
975
+ ]);
976
+ var BARE_KIND_RE = /^(?:all\s+|the\s+)?(?:module|file|function|method|class|attribute|field|property|variable|global|commit|change|test|member)\??$/i;
977
+ var isListingRemainder = (rest) => {
978
+ if (BARE_KIND_RE.test(rest)) return true;
979
+ const words = rest.replace(/\?+\s*$/, "").trim().split(/\s+/);
980
+ return LISTING_TAIL_KINDS.has((words[words.length - 1] || "").toLowerCase());
981
+ };
982
+ var GREETING_PREAMBLE_RE = /^(?:hi|hiya|hello|hey|yo|howdy)(?:\s+there)?\s*[,—–-]\s*(?:(?:just\s+a\s+)?quick\s+question\s*[,:—–-]?\s*)?(.+)$/i;
983
+ var MODAL_WRAPPER_RE = /^(?:can|could|would|will)\s+you\s+(?:please\s+)?(.+?)(?:[,\s]+please)?\??$/i;
984
+ var SHOW_GIVE_ME_RE = /^(?:show|give)\s+me\s+(?:the\s+)?(.+?)\??$/i;
985
+ function applyPreambleFrames(text) {
986
+ let q = String(text || "");
987
+ for (let pass = 0; pass < 3; pass++) {
988
+ const before = q;
989
+ let m = q.match(GREETING_PREAMBLE_RE);
990
+ if (m) q = m[1].trim();
991
+ m = q.match(MODAL_WRAPPER_RE);
992
+ if (m) q = m[1].trim();
993
+ m = q.match(SHOW_GIVE_ME_RE);
994
+ if (m) {
995
+ const rest = m[1].trim();
996
+ if (!isListingRemainder(rest)) {
997
+ q = RELATION_VERB_RE.test(rest) || INTERROGATIVE_LEAD_RE.test(rest) ? rest : `describe ${rest}`;
998
+ }
999
+ }
1000
+ if (q === before) break;
1001
+ }
1002
+ return q;
1003
+ }
952
1004
  function normalizeQuery(text) {
953
1005
  let q = String(text || "");
954
1006
  q = q.replace(CONTRACTION_RE, (m) => CONTRACTIONS[m.toLowerCase()]);
955
1007
  q = q.replace(MISSPELLING_RE, (m) => MISSPELLINGS[m.toLowerCase()]);
956
1008
  q = q.replace(WRONG_WORD_RE, (m) => WRONG_WORDS[m.toLowerCase()]);
957
1009
  q = q.replace(G_DROP, "$1ing");
1010
+ q = applyPreambleFrames(q);
958
1011
  if (FILLER_WORDS.length) {
959
1012
  const fillerRe = new RegExp(
960
1013
  "\\b(" + [...FILLER_WORDS].sort((a, b) => b.length - a.length).map(escapeRegex).join("|") + ")\\b",
@@ -972,6 +1025,108 @@
972
1025
  }
973
1026
  return text;
974
1027
  }
1028
+ var PHRASING_FRAMES = Object.freeze([
1029
+ // MEMBERS-of-class → "what does X contain".
1030
+ // "what functions are in Task", "what methods are inside X", "what attributes are in X"
1031
+ { re: /^what\s+(?:functions?|methods?|members?|attributes?|fields?|properties)\s+(?:are|is)\s+(?:in|inside|within)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
1032
+ // "what functions does Task have", "what methods does X have"
1033
+ { re: /^what\s+(?:functions?|methods?|members?|attributes?|fields?|properties)\s+(?:does|do)\s+(.+?)\s+have\??$/i, to: (m) => `what does ${m[1]} contain` },
1034
+ // "what are the members of X", "what are the methods in X"
1035
+ { re: /^what\s+are\s+(?:the\s+)?(?:functions?|methods?|members?|attributes?|fields?|properties)\s+(?:of|in|inside|within)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
1036
+ // "members of X", "methods of X", "contents of X"
1037
+ { re: /^(?:the\s+)?(?:members?|methods?|attributes?|contents)\s+of\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
1038
+ // "what's in X" / "what is in X" (contraction already expanded; sha handled above)
1039
+ { re: /^what\s+is\s+(?:in|inside)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
1040
+ // WHERE-DEFINED → "where is X defined". PAST TENSE ONLY ("what defined X", "what
1041
+ // declared X"): the PRESENT "what defines X" already parses as a reverse-defines
1042
+ // query (the module defining symbol X — test/ask.test.mjs pins that), so rewriting
1043
+ // it would change that receipt. The past-tense form is the one that hit the wall.
1044
+ { re: /^what\s+(?:defined|declared)\s+(?:the\s+)?(?:function\s+|method\s+|class\s+|module\s+|variable\s+|constant\s+)?(.+?)\??$/i, to: (m) => `where is ${m[1]} defined` },
1045
+ // "where's X defined" (the "where's" contraction is not in the contraction table)
1046
+ { re: /^where'?s\s+(?:the\s+)?(.+?)\s+(defined|declared|located|implemented)\??$/i, to: (m) => `where is ${m[1]} ${m[2]}` },
1047
+ // PREDICATIVE QUALIFIER → the ATTRIBUTIVE form the grammar already answers. The
1048
+ // adjective-qualifier post-filters (ask-vocab.mjs QUALIFIERS: tested/untested,
1049
+ // public/private, exported, static/abstract/constant, …) parse in the ATTRIBUTIVE
1050
+ // slot — "untested modules", "public methods" — but a developer just as naturally
1051
+ // asks the PREDICATIVE "which modules are untested" / "what functions are tested",
1052
+ // which hit the grammar wall (and, worse, the wall's own hint SUGGESTED "which
1053
+ // functions are tested" — a shape it could not then answer). Rewriting the
1054
+ // predicative "<which|what> <kind> are <QUALIFIER>" to "<QUALIFIER> <kind>" routes
1055
+ // it onto the working attributive filter. Closed to the known qualifier adjectives
1056
+ // (not a general "… are X" catch), and the QUALIFIER must sit immediately after
1057
+ // are/is, so "which modules are NOT tested" never matches here — that keeps its own
1058
+ // set-complement handler (matchNegationSet, downstream in ask.mjs's parseNegation).
1059
+ {
1060
+ re: /^(?:which|what)\s+(?:the\s+|all\s+)?([a-z][a-z-]*?)\s+(?:are|is)\s+(public|private|protected|static|abstract|constant|exported|re-?exported|tested|covered|untested|uncovered)\??$/i,
1061
+ to: (m) => `${m[2].toLowerCase()} ${m[1].toLowerCase()}`
1062
+ },
1063
+ // BARE COVERAGE SURVEY (no entity kind) → the attributive "<qualifier> modules"
1064
+ // the grammar already answers. Once "what is a test" opens the topic, a developer
1065
+ // asks the survey the plainest way — "what is untested", "what's not tested",
1066
+ // "what isn't covered", "what is covered" — with NO entity noun at all, so the
1067
+ // predicative-qualifier frame above (which needs a KIND between what/which and
1068
+ // are/is) can't catch it, and it fell through to a soft wall ("no module matching
1069
+ // 'not'…" / the "I answer questions…" orientation). Default the surveyed kind to
1070
+ // modules (the same set "which modules are not tested" / "untested modules" return)
1071
+ // and fold the negation into the qualifier (not tested → untested, not covered →
1072
+ // uncovered). Anchored with no object, so "what tests cover X" / "what is a test"
1073
+ // never match here.
1074
+ {
1075
+ re: /^what\s+(?:is|are)\s+(not\s+)?(tested|untested|covered|uncovered)\??$/i,
1076
+ to: (m) => {
1077
+ const q = m[2].toLowerCase();
1078
+ const flipped = m[1] ? q === "tested" ? "untested" : q === "covered" ? "uncovered" : q : q;
1079
+ return `${flipped} modules`;
1080
+ }
1081
+ },
1082
+ // CO-CHANGE → the "co-changes with" canonical the RELATIONS table answers. The
1083
+ // cochange verb synonyms (ask-vocab.mjs) include "co-changes with" / "moves
1084
+ // together with" / "tends to change together with", but NOT the plainest form a
1085
+ // developer types — the one the README itself prints and the relation renders as:
1086
+ // "what does X change together with" / "what changes together with X". Both hit a
1087
+ // dead-end ("couldn't resolve one of the terms" / the grammar wall); rewriting them
1088
+ // onto "what co-changes with X" routes them to the working change-coupling query.
1089
+ { re: /^what\s+does\s+(.+?)\s+changes?\s+together\s+with\??$/i, to: (m) => `what co-changes with ${m[1]}` },
1090
+ { re: /^what\s+changes?\s+together\s+with\s+(.+?)\??$/i, to: (m) => `what co-changes with ${m[1]}` },
1091
+ // AUTHORSHIP → the "who touched X" churn query. "who touched X" now names the
1092
+ // commit author beside the sha (the 0.8.1 commit-ref quick-win), which invites the
1093
+ // synonyms a developer reaches for next — "who wrote X", "who authored X", "who is
1094
+ // the author of X" — and every one of them hit the grammar wall. tmct has no
1095
+ // separate authorship edge; "touched" IS the authorship signal (the churn commits
1096
+ // carry the author), so these are true synonyms of "who touched X", not a new
1097
+ // capability. Anaphora rides through untouched ("who wrote it" → "who touched it").
1098
+ // SHA GUARD (0.8.2 feel wave): a COMMIT object is NOT a synonym — "who is the
1099
+ // author of abc1234" rewritten to "who touched abc1234" dumps the commit's
1100
+ // touch-SET instead of naming its author. The negative lookahead refuses the
1101
+ // rewrite when the object is a bare (optionally "commit "-prefixed) 7-40 char
1102
+ // hex sha, leaving the un-rewritten form for the author lane to consume;
1103
+ // file/symbol objects (anything non-sha, e.g. "deadbeef.mjs") keep the rewrite.
1104
+ { re: /^who\s+(?:wrote|authored)\s+(?:the\s+)?(?!(?:commit\s+)?[0-9a-f]{7,40}\??$)(.+?)\??$/i, to: (m) => `who touched ${m[1]}` },
1105
+ { re: /^who\s+is\s+the\s+authors?\s+of\s+(?:the\s+)?(?!(?:commit\s+)?[0-9a-f]{7,40}\??$)(.+?)\??$/i, to: (m) => `who touched ${m[1]}` },
1106
+ // HAS-TESTS → the coverage question the RELATIONS table answers. "does X have
1107
+ // tests" parses "have" as a defines-verb (VERB_TO_KIND), producing the garbled
1108
+ // "No — no defines edge found from X to <whatever resolves>" receipt; "is X
1109
+ // tested" traverses tests edges from the WRONG side (subject = X). Both mean
1110
+ // the coverage question "what tests X" — rewrite onto it. Closed to a
1111
+ // tests/coverage object ("does X have methods/members" stays the members
1112
+ // family) and refuses any "not" in the subject span, so the set-complement
1113
+ // negations ("is X not tested") keep their own handler downstream.
1114
+ { re: /^(?:does|do)\s+(?!.*\bnot\b)(.+?)\s+have\s+(?:any\s+)?(?:tests?|test\s+coverage|coverage)\??$/i, to: (m) => `what tests ${m[1]}` },
1115
+ { re: /^(?:is|are)\s+(?!.*\bnot\b)(.+?)\s+tested\??$/i, to: (m) => `what tests ${m[1]}` },
1116
+ // NEEDS-TESTS → the untested-module survey. "what needs tests" / "what needs
1117
+ // testing" is the plainest way to ask which modules are uncovered, and it hit the
1118
+ // grammar wall ("no module matching 'needs'…"). Route it onto the same attributive
1119
+ // survey the bare "what is untested" frame lands on. Closed to the tests/coverage
1120
+ // object, so it can't swallow a general "what needs X".
1121
+ { re: /^what\s+needs\s+(?:to\s+be\s+)?(?:a\s+)?(?:tested|tests?|testing|coverage|covering)\??$/i, to: () => "untested modules" }
1122
+ ]);
1123
+ function applyPhrasingFrames(text) {
1124
+ for (const frame of PHRASING_FRAMES) {
1125
+ const m = text.match(frame.re);
1126
+ if (m) return frame.to(m).replace(/\s+/g, " ").trim();
1127
+ }
1128
+ return text;
1129
+ }
975
1130
  var NEGATION_SET_RE = new RegExp(
976
1131
  "^(?:which|what|who|list|show(?:\\s+me)?|find|give\\s+me)?\\s*(?:the\\s+|all\\s+)?([a-z][a-z-]*)\\s+(?:(?:that|which|who)\\s+)?(?:(?:do|does|did|are|is|was|were|have|has)\\s+)?not\\s+(.+)$",
977
1132
  // the negation marker + (2) the predicate
@@ -1058,17 +1213,17 @@
1058
1213
  function fuzzyVocabWord(w) {
1059
1214
  const bound = fuzzyBound(w);
1060
1215
  let best = bound + 1;
1061
- let hit = null;
1216
+ let hit2 = null;
1062
1217
  let tied = false;
1063
1218
  for (const target of FUZZY_TARGET_WORDS) {
1064
1219
  const d = editDistance(w, target, Math.min(best, bound));
1065
1220
  if (d < best) {
1066
1221
  best = d;
1067
- hit = target;
1222
+ hit2 = target;
1068
1223
  tied = false;
1069
- } else if (d === best && d <= bound && target !== hit) tied = true;
1224
+ } else if (d === best && d <= bound && target !== hit2) tied = true;
1070
1225
  }
1071
- return best <= bound && !tied ? hit : null;
1226
+ return best <= bound && !tied ? hit2 : null;
1072
1227
  }
1073
1228
 
1074
1229
  // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/strategies/grammar.mjs
@@ -1253,8 +1408,8 @@
1253
1408
  }
1254
1409
  }
1255
1410
  const consumed = /* @__PURE__ */ new Set();
1256
- const mark = (hit) => {
1257
- if (hit) for (let i = hit.start; i < hit.end; i += 1) consumed.add(i);
1411
+ const mark = (hit2) => {
1412
+ if (hit2) for (let i = hit2.start; i < hit2.end; i += 1) consumed.add(i);
1258
1413
  };
1259
1414
  mark(verbHit);
1260
1415
  const entityHit = findPhrase(canonWords, ENTITY_TO_TYPE, consumed);
@@ -1356,9 +1511,428 @@
1356
1511
  }
1357
1512
  };
1358
1513
 
1514
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/grammar/lexicon.mjs
1515
+ var import_meta = {};
1516
+ var CORE_FILE = join(dirname(fileURLToPath(import_meta.url)), "lexicon-core.json");
1517
+ var DETERMINERS = Object.freeze({
1518
+ every: "universal",
1519
+ a: "indefinite",
1520
+ an: "indefinite",
1521
+ the: "definite",
1522
+ no: "negative"
1523
+ });
1524
+ var QUANTIFIERS = Object.freeze({
1525
+ "at least": "owl:minCardinality",
1526
+ "at most": "owl:maxCardinality",
1527
+ exactly: "owl:cardinality"
1528
+ });
1529
+ var NUMBER_WORDS = Object.freeze({
1530
+ one: 1,
1531
+ two: 2,
1532
+ three: 3,
1533
+ four: 4,
1534
+ five: 5,
1535
+ six: 6,
1536
+ seven: 7,
1537
+ eight: 8,
1538
+ nine: 9,
1539
+ ten: 10
1540
+ });
1541
+ function numberOf(word) {
1542
+ const w = String(word ?? "").trim().toLowerCase();
1543
+ if (/^\d+$/.test(w)) return Number(w);
1544
+ return NUMBER_WORDS[w] ?? null;
1545
+ }
1546
+ function thirdPerson(base) {
1547
+ const b = String(base);
1548
+ if (b === "have") return "has";
1549
+ if (/[^aeiou]y$/.test(b)) return `${b.slice(0, -1)}ies`;
1550
+ if (/(s|x|z|ch|sh)$/.test(b)) return `${b}es`;
1551
+ return `${b}s`;
1552
+ }
1553
+ function predicateOf(verbEntry) {
1554
+ if (verbEntry.predicate) return verbEntry.predicate;
1555
+ const prep = verbEntry.prep ? verbEntry.prep[0].toUpperCase() + verbEntry.prep.slice(1) : "";
1556
+ return `tmct:${thirdPerson(verbEntry.lemma)}${prep}`;
1557
+ }
1558
+ function foldCandidates(word) {
1559
+ const w = String(word);
1560
+ const out = [w];
1561
+ if (w.length > 4 && /[a-z]ies$/.test(w)) out.push(`${w.slice(0, -3)}y`);
1562
+ if (/(ses|xes|zes|ches|shes)$/.test(w)) out.push(w.slice(0, -2));
1563
+ if (/[a-z]s$/.test(w) && !/ss$/.test(w)) out.push(w.slice(0, -1));
1564
+ if (w === "has") out.push("have");
1565
+ return out;
1566
+ }
1567
+ var NOUN_PROPERTY_TYPES = /* @__PURE__ */ new Set(["data", "object"]);
1568
+ var ADJECTIVE_TYPES = /* @__PURE__ */ new Set(["subclass", "data"]);
1569
+ function ingest(lex, raw = {}) {
1570
+ for (const [lemma, e] of Object.entries(raw.nouns || {})) {
1571
+ const entry = { lemma, ...e || {} };
1572
+ if (entry.property && !NOUN_PROPERTY_TYPES.has(entry.property)) {
1573
+ throw new Error(`lexicon noun "${lemma}": property must be "data" or "object", got ${JSON.stringify(entry.property)}`);
1574
+ }
1575
+ lex.nouns.set(lemma, entry);
1576
+ if (entry.plural) lex.nounPlurals.set(entry.plural, lemma);
1577
+ }
1578
+ for (const [lemma, e] of Object.entries(raw.verbs || {})) {
1579
+ lex.verbs.set(lemma, { lemma, ...e || {} });
1580
+ }
1581
+ for (const [lemma, e] of Object.entries(raw.adjectives || {})) {
1582
+ const entry = { lemma, ...e || {} };
1583
+ if (!ADJECTIVE_TYPES.has(entry.type)) {
1584
+ throw new Error(`lexicon adjective "${lemma}": type must be "subclass" or "data", got ${JSON.stringify(entry.type)}`);
1585
+ }
1586
+ lex.adjectives.set(lemma, entry);
1587
+ }
1588
+ for (const name of raw.properNames || []) {
1589
+ lex.properNames.set(String(name).toLowerCase(), String(name));
1590
+ }
1591
+ }
1592
+ var coreCache = null;
1593
+ function loadLexicon(extra) {
1594
+ if (!extra && coreCache) return coreCache;
1595
+ const raw = JSON.parse(readFileSync(CORE_FILE, "utf8"));
1596
+ const lex = {
1597
+ nouns: /* @__PURE__ */ new Map(),
1598
+ nounPlurals: /* @__PURE__ */ new Map(),
1599
+ verbs: /* @__PURE__ */ new Map(),
1600
+ adjectives: /* @__PURE__ */ new Map(),
1601
+ properNames: /* @__PURE__ */ new Map()
1602
+ // lowercased → canonical spelling
1603
+ };
1604
+ ingest(lex, raw);
1605
+ if (extra) {
1606
+ ingest(lex, extra);
1607
+ return lex;
1608
+ }
1609
+ coreCache = lex;
1610
+ return lex;
1611
+ }
1612
+ function lookupNoun(lexicon, word) {
1613
+ const w = String(word ?? "").toLowerCase();
1614
+ const irregular = lexicon.nounPlurals.get(w);
1615
+ if (irregular) return lexicon.nouns.get(irregular) ?? null;
1616
+ for (const cand of foldCandidates(w)) {
1617
+ const hit2 = lexicon.nouns.get(cand);
1618
+ if (hit2) return hit2;
1619
+ }
1620
+ return null;
1621
+ }
1622
+ function lookupVerb(lexicon, word) {
1623
+ const w = String(word ?? "").toLowerCase();
1624
+ for (const cand of foldCandidates(w)) {
1625
+ const hit2 = lexicon.verbs.get(cand);
1626
+ if (hit2) return hit2;
1627
+ }
1628
+ return null;
1629
+ }
1630
+ function lookupAdjective(lexicon, word) {
1631
+ return lexicon.adjectives.get(String(word ?? "").toLowerCase()) ?? null;
1632
+ }
1633
+ function lookupProperName(lexicon, word) {
1634
+ return lexicon.properNames.get(String(word ?? "").toLowerCase()) ?? null;
1635
+ }
1636
+ function classify(word, lexicon = loadLexicon()) {
1637
+ const w = String(word ?? "").trim();
1638
+ if (!w) return null;
1639
+ const lower = w.toLowerCase();
1640
+ if (DETERMINERS[lower]) return { pos: "determiner", type: DETERMINERS[lower] };
1641
+ if (QUANTIFIERS[lower]) return { pos: "quantifier", type: QUANTIFIERS[lower] };
1642
+ const n = numberOf(lower);
1643
+ if (n != null) return { pos: "number", type: "cardinal", value: n };
1644
+ const proper = lookupProperName(lexicon, w);
1645
+ if (proper) return { pos: "properName", type: "individual", canonical: proper };
1646
+ const noun = lookupNoun(lexicon, lower);
1647
+ if (noun) {
1648
+ return noun.property ? { pos: "noun", type: `${noun.property}-property`, lemma: noun.lemma, property: noun.property } : { pos: "noun", type: "class", lemma: noun.lemma };
1649
+ }
1650
+ const verb = lookupVerb(lexicon, lower);
1651
+ if (verb) {
1652
+ return { pos: "verb", type: "objectProperty", lemma: verb.lemma, predicate: predicateOf(verb), ...verb.prep ? { prep: verb.prep } : {} };
1653
+ }
1654
+ const adj = lookupAdjective(lexicon, lower);
1655
+ if (adj) return { pos: "adjective", type: adj.type, lemma: adj.lemma };
1656
+ return null;
1657
+ }
1658
+
1659
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/grammar/ace.mjs
1660
+ var DET = /* @__PURE__ */ new Set(["a", "an", "the"]);
1661
+ var CODE_REF = /[./\\#:@]/;
1662
+ function tokenize(sentence) {
1663
+ return String(sentence ?? "").replace(/[‘’]/g, "'").replace(/[,;]/g, " ").replace(/[?!.]+\s*$/, "").trim().split(/\s+/).filter(Boolean);
1664
+ }
1665
+ var local = (term) => String(term).replace(/^tmct:/, "");
1666
+ var stripDet = (tokens) => tokens.length > 1 && DET.has(tokens[0].toLowerCase()) ? tokens.slice(1) : tokens;
1667
+ function resolveNP(lexicon, tokensIn) {
1668
+ const tokens = stripDet(tokensIn);
1669
+ if (tokens.length === 1) {
1670
+ const t = tokens[0];
1671
+ const proper = lookupProperName(lexicon, t);
1672
+ if (proper) return { term: `tmct:${proper}`, individual: true, extras: [], unknown: [] };
1673
+ if (CODE_REF.test(t)) return { term: `tmct:${t}`, individual: true, extras: [], unknown: [] };
1674
+ const noun = lookupNoun(lexicon, t);
1675
+ if (noun) return { term: `tmct:${noun.lemma}`, individual: false, noun, extras: [], unknown: [] };
1676
+ return { term: null, individual: false, extras: [], unknown: [t] };
1677
+ }
1678
+ if (tokens.length === 2) {
1679
+ const adj = lookupAdjective(lexicon, tokens[0]);
1680
+ const noun = lookupNoun(lexicon, tokens[1]);
1681
+ if (adj && noun) {
1682
+ const term = `tmct:${adj.lemma}-${noun.lemma}`;
1683
+ const extras = [
1684
+ { subject: term, predicate: "rdfs:subClassOf", object: `tmct:${noun.lemma}`, kind: "rdfs:subClassOf" }
1685
+ ];
1686
+ if (adj.type === "subclass") {
1687
+ extras.push({ subject: term, predicate: "rdfs:subClassOf", object: `tmct:${adj.lemma}`, kind: "rdfs:subClassOf" });
1688
+ } else {
1689
+ const r = `tmct:has-${adj.lemma}`;
1690
+ extras.push(
1691
+ { subject: r, predicate: "rdf:type", object: "owl:Restriction", kind: "owl:hasValue" },
1692
+ { subject: r, predicate: "owl:onProperty", object: adj.property || `tmct:${adj.lemma}`, kind: "owl:hasValue" },
1693
+ { subject: r, predicate: "owl:hasValue", object: adj.value ?? "true", kind: "owl:hasValue" },
1694
+ { subject: term, predicate: "rdfs:subClassOf", object: r, kind: "owl:hasValue" }
1695
+ );
1696
+ }
1697
+ return { term, individual: false, noun, extras, unknown: [] };
1698
+ }
1699
+ const unknown = tokens.filter((t) => !classify(t, lexicon));
1700
+ return { term: null, individual: false, extras: [], unknown };
1701
+ }
1702
+ return { term: null, individual: false, extras: [], unknown: tokens.filter((t) => !classify(t, lexicon)) };
1703
+ }
1704
+ function missOrNull(pattern, nps, extraUnknown = []) {
1705
+ const residue = [...extraUnknown, ...nps.flatMap((np) => np.unknown)];
1706
+ return residue.length ? { pattern, triples: [], residue } : null;
1707
+ }
1708
+ var hit = (pattern, nps, triples, more = {}) => ({
1709
+ pattern,
1710
+ triples: [...nps.flatMap((np) => np.extras), ...triples],
1711
+ residue: [],
1712
+ ...more
1713
+ });
1714
+ function parseRelation(lexicon, toks, lower) {
1715
+ for (let i = 1; i < toks.length - 1; i += 1) {
1716
+ const verb = lookupVerb(lexicon, lower[i]);
1717
+ if (!verb) continue;
1718
+ let objStart = i + 1;
1719
+ if (verb.prep) {
1720
+ if (lower[objStart] !== verb.prep) continue;
1721
+ objStart += 1;
1722
+ if (objStart >= toks.length) continue;
1723
+ }
1724
+ const np1 = resolveNP(lexicon, toks.slice(0, i));
1725
+ const np2 = resolveNP(lexicon, toks.slice(objStart));
1726
+ if (np1.term == null || np2.term == null) return missOrNull("relation", [np1, np2]);
1727
+ return hit("relation", [np1, np2], [
1728
+ { subject: np1.term, predicate: predicateOf(verb), object: np2.term, kind: "owl:ObjectProperty" }
1729
+ ]);
1730
+ }
1731
+ const content = toks.filter((t) => !DET.has(t.toLowerCase()));
1732
+ if (content.length === 3 && !classify(content[1], lexicon)) {
1733
+ const np1 = resolveNP(lexicon, [content[0]]);
1734
+ const np2 = resolveNP(lexicon, [content[2]]);
1735
+ if (np1.term != null && np2.term != null) return { pattern: "relation", triples: [], residue: [content[1]] };
1736
+ }
1737
+ return null;
1738
+ }
1739
+ function adjectiveCopula(pattern, np1, adj) {
1740
+ if (np1.term == null) return missOrNull(pattern, [np1]);
1741
+ if (adj.type === "data") {
1742
+ return hit(pattern, [np1], [
1743
+ { subject: np1.term, predicate: adj.property || `tmct:${adj.lemma}`, object: adj.value ?? "true", kind: "owl:DatatypeProperty" }
1744
+ ]);
1745
+ }
1746
+ const predicate = np1.individual ? "rdf:type" : "rdfs:subClassOf";
1747
+ return hit(pattern, [np1], [
1748
+ { subject: np1.term, predicate, object: `tmct:${adj.lemma}`, kind: predicate }
1749
+ ]);
1750
+ }
1751
+ function parseRestriction(lexicon, toks, lower, thatIdx) {
1752
+ const isIdx = lower.indexOf("is", thatIdx + 2);
1753
+ if (isIdx < 0 || thatIdx + 1 >= isIdx) return null;
1754
+ const verb = lookupVerb(lexicon, lower[thatIdx + 1]);
1755
+ const np1 = resolveNP(lexicon, toks.slice(1, thatIdx));
1756
+ let objStart = thatIdx + 2;
1757
+ if (verb?.prep) {
1758
+ if (lower[objStart] !== verb.prep) return null;
1759
+ objStart += 1;
1760
+ }
1761
+ const np2 = resolveNP(lexicon, toks.slice(objStart, isIdx));
1762
+ const np3 = resolveNP(lexicon, toks.slice(isIdx + 1));
1763
+ if (!verb) return missOrNull("someValuesFrom", [np1, np2, np3], [toks[thatIdx + 1]]);
1764
+ if (np1.term == null || np2.term == null || np3.term == null) {
1765
+ return missOrNull("someValuesFrom", [np1, np2, np3]);
1766
+ }
1767
+ if (np1.individual || np2.individual || np3.individual) return null;
1768
+ const pred = predicateOf(verb);
1769
+ const k = "owl:someValuesFrom";
1770
+ const r = `tmct:some-${local(pred)}-${local(np2.term)}`;
1771
+ const inter = `tmct:${local(np1.term)}-that-${local(pred)}-${local(np2.term)}`;
1772
+ return hit("someValuesFrom", [np1, np2, np3], [
1773
+ { subject: r, predicate: "rdf:type", object: "owl:Restriction", kind: k },
1774
+ { subject: r, predicate: "owl:onProperty", object: pred, kind: k },
1775
+ { subject: r, predicate: "owl:someValuesFrom", object: np2.term, kind: k },
1776
+ { subject: inter, predicate: "owl:intersectionOf", object: np1.term, kind: k },
1777
+ { subject: inter, predicate: "owl:intersectionOf", object: r, kind: k },
1778
+ { subject: inter, predicate: "rdfs:subClassOf", object: np3.term, kind: k }
1779
+ ]);
1780
+ }
1781
+ function parseCardinality(lexicon, toks, lower, hasIdx) {
1782
+ let kind = null;
1783
+ let nIdx = -1;
1784
+ if (lower[hasIdx + 1] === "at" && lower[hasIdx + 2] === "least") {
1785
+ kind = "owl:minCardinality";
1786
+ nIdx = hasIdx + 3;
1787
+ } else if (lower[hasIdx + 1] === "at" && lower[hasIdx + 2] === "most") {
1788
+ kind = "owl:maxCardinality";
1789
+ nIdx = hasIdx + 3;
1790
+ } else if (lower[hasIdx + 1] === "exactly") {
1791
+ kind = "owl:cardinality";
1792
+ nIdx = hasIdx + 2;
1793
+ } else return null;
1794
+ const n = numberOf(lower[nIdx]);
1795
+ if (n == null || nIdx + 1 >= toks.length) return null;
1796
+ const np1 = resolveNP(lexicon, toks.slice(1, hasIdx));
1797
+ const np2 = resolveNP(lexicon, toks.slice(nIdx + 1));
1798
+ if (np1.term == null || np2.term == null) return missOrNull("cardinality", [np1, np2]);
1799
+ if (np1.individual || np2.individual) return null;
1800
+ const tag = { "owl:minCardinality": "min", "owl:maxCardinality": "max", "owl:cardinality": "exactly" }[kind];
1801
+ const r = `tmct:${tag}-${n}-${local(np2.term)}`;
1802
+ return hit("cardinality", [np1, np2], [
1803
+ { subject: r, predicate: "rdf:type", object: "owl:Restriction", kind },
1804
+ { subject: r, predicate: "owl:onProperty", object: "tmct:has", kind },
1805
+ { subject: r, predicate: kind, object: String(n), kind, n },
1806
+ { subject: r, predicate: "owl:onClass", object: np2.term, kind },
1807
+ { subject: np1.term, predicate: "rdfs:subClassOf", object: r, kind }
1808
+ ], { n });
1809
+ }
1810
+ function parseEvery(lexicon, toks, lower) {
1811
+ const thatIdx = lower.indexOf("that");
1812
+ if (thatIdx > 1) return parseRestriction(lexicon, toks, lower, thatIdx);
1813
+ const hasIdx = lower.indexOf("has");
1814
+ if (hasIdx > 1 && (lower[hasIdx + 1] === "at" || lower[hasIdx + 1] === "exactly")) {
1815
+ return parseCardinality(lexicon, toks, lower, hasIdx);
1816
+ }
1817
+ const isIdx = lower.indexOf("is");
1818
+ if (isIdx <= 1 || isIdx === toks.length - 1) return null;
1819
+ const np1 = resolveNP(lexicon, toks.slice(1, isIdx));
1820
+ const rest = toks.slice(isIdx + 1);
1821
+ if (rest.length === 1) {
1822
+ const adj = lookupAdjective(lexicon, rest[0]);
1823
+ if (adj) return adjectiveCopula("adjective", np1, adj);
1824
+ }
1825
+ const np2 = resolveNP(lexicon, rest);
1826
+ if (np1.term == null || np2.term == null) return missOrNull("subClassOf", [np1, np2]);
1827
+ if (np1.individual || np2.individual) return null;
1828
+ return hit("subClassOf", [np1, np2], [
1829
+ { subject: np1.term, predicate: "rdfs:subClassOf", object: np2.term, kind: "rdfs:subClassOf" }
1830
+ ]);
1831
+ }
1832
+ function parseDisjoint(lexicon, toks, lower) {
1833
+ const isIdx = lower.indexOf("is");
1834
+ if (isIdx <= 1 || isIdx === toks.length - 1) return null;
1835
+ const np1 = resolveNP(lexicon, toks.slice(1, isIdx));
1836
+ const np2 = resolveNP(lexicon, toks.slice(isIdx + 1));
1837
+ if (np1.term == null || np2.term == null) return missOrNull("disjointWith", [np1, np2]);
1838
+ if (np1.individual || np2.individual) return null;
1839
+ return hit("disjointWith", [np1, np2], [
1840
+ { subject: np1.term, predicate: "owl:disjointWith", object: np2.term, kind: "owl:disjointWith" }
1841
+ ]);
1842
+ }
1843
+ function buildPossessive(lexicon, ownerToks, headToks, valueToks) {
1844
+ const owner = resolveNP(lexicon, ownerToks);
1845
+ if (headToks.length !== 1) return null;
1846
+ const head = lookupNoun(lexicon, headToks[0]);
1847
+ if (!head) return missOrNull("possessive", [owner], [headToks[0]]);
1848
+ if (owner.term == null) return missOrNull("possessive", [owner]);
1849
+ if (!valueToks.length) return null;
1850
+ const predicate = `tmct:${head.lemma}`;
1851
+ if ((head.property || "data") === "object") {
1852
+ const value = resolveNP(lexicon, valueToks);
1853
+ if (value.term == null) return missOrNull("possessive", [owner, value]);
1854
+ return hit("possessive", [owner, value], [
1855
+ { subject: owner.term, predicate, object: value.term, kind: "owl:ObjectProperty" }
1856
+ ]);
1857
+ }
1858
+ return hit("possessive", [owner], [
1859
+ { subject: owner.term, predicate, object: valueToks.join(" "), kind: "owl:DatatypeProperty" }
1860
+ ]);
1861
+ }
1862
+ function parsePossessive(lexicon, toks, lower) {
1863
+ const ownerRaw = toks[0].replace(/'s$/i, "");
1864
+ const isIdx = lower.indexOf("is");
1865
+ if (isIdx < 2 || !ownerRaw) return null;
1866
+ return buildPossessive(lexicon, [ownerRaw], toks.slice(1, isIdx), toks.slice(isIdx + 1));
1867
+ }
1868
+ function parseOfForm(lexicon, toks, lower) {
1869
+ const ofIdx = lower.indexOf("of");
1870
+ const isIdx = lower.indexOf("is", ofIdx + 1);
1871
+ if (ofIdx < 2 || isIdx < ofIdx + 2) return null;
1872
+ return buildPossessive(lexicon, toks.slice(ofIdx + 1, isIdx), toks.slice(1, ofIdx), toks.slice(isIdx + 1));
1873
+ }
1874
+ function parseCopula(lexicon, toks, lower, isIdx) {
1875
+ const np1 = resolveNP(lexicon, toks.slice(0, isIdx));
1876
+ const rest = toks.slice(isIdx + 1);
1877
+ if (!rest.length) return null;
1878
+ if (rest.length === 1) {
1879
+ const adj = lookupAdjective(lexicon, rest[0]);
1880
+ if (adj) return adjectiveCopula("adjective", np1, adj);
1881
+ }
1882
+ const np2 = resolveNP(lexicon, rest);
1883
+ if (np1.term == null || np2.term == null) {
1884
+ return missOrNull(np1.individual ? "typeAssertion" : "subClassOf", [np1, np2]);
1885
+ }
1886
+ if (np2.individual) return null;
1887
+ if (np1.individual) {
1888
+ return hit("typeAssertion", [np1, np2], [
1889
+ { subject: np1.term, predicate: "rdf:type", object: np2.term, kind: "rdf:type" }
1890
+ ]);
1891
+ }
1892
+ return hit("subClassOf", [np1, np2], [
1893
+ { subject: np1.term, predicate: "rdfs:subClassOf", object: np2.term, kind: "rdfs:subClassOf" }
1894
+ ]);
1895
+ }
1896
+ function parseAce(sentence, lexicon = loadLexicon()) {
1897
+ const toks = tokenize(sentence);
1898
+ if (toks.length < 3) return null;
1899
+ const lower = toks.map((t) => t.toLowerCase());
1900
+ if (lower[0] === "every") return parseEvery(lexicon, toks, lower);
1901
+ if (lower[0] === "no") return parseDisjoint(lexicon, toks, lower);
1902
+ if (/'s$/.test(lower[0]) && lower[0].length > 2) return parsePossessive(lexicon, toks, lower);
1903
+ if (lower[0] === "the" && lower.includes("of") && lower.includes("is")) {
1904
+ return parseOfForm(lexicon, toks, lower);
1905
+ }
1906
+ const isIdx = lower.indexOf("is");
1907
+ if (isIdx > 0) return parseCopula(lexicon, toks, lower, isIdx);
1908
+ return parseRelation(lexicon, toks, lower);
1909
+ }
1910
+
1911
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/strategies/ace.mjs
1912
+ function runAce(text) {
1913
+ let parsed = null;
1914
+ try {
1915
+ parsed = parseAce(text);
1916
+ } catch {
1917
+ return null;
1918
+ }
1919
+ if (!parsed || !Array.isArray(parsed.triples) || parsed.triples.length === 0) return null;
1920
+ return { strategyId: "ace", class: "ace-fact", candidates: [{ parsed, confidence: 0.85, via: "exact", note: `ACE ${parsed.pattern}` }] };
1921
+ }
1922
+ var aceStrategy = {
1923
+ id: "ace",
1924
+ class: "ace-fact",
1925
+ // eslint-disable-next-line require-await
1926
+ async run(text) {
1927
+ return runAce(text);
1928
+ }
1929
+ };
1930
+
1359
1931
  // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/merge.mjs
1360
1932
  var DEFAULT_CONFIDENCE = 0.5;
1361
- var cmpTerm = (s) => String(s || "").trim().toLowerCase().replace(/\s+/g, " ").replace(/^commit\s+(?=[0-9a-f]{7,40}$)/, "");
1933
+ var cmpTerm = (s) => String(s || "").trim().toLowerCase().replace(/\s+/g, " ").replace(/^(?:the|a|an)\s+/, "").replace(/^commit\s+(?=[0-9a-f]{7,40}$)/, "");
1934
+ var LEADING_DET_RE = /^\s*(?:the|a|an)\s+/i;
1935
+ var detCount = (p) => [p?.subject, p?.object].filter((t) => LEADING_DET_RE.test(String(t || ""))).length;
1362
1936
  function sameParse(p, q) {
1363
1937
  if (p.shape !== q.shape || p.kind !== q.kind) return false;
1364
1938
  if (p.shape === "ask") return cmpTerm(p.subject) === cmpTerm(q.subject) && cmpTerm(p.object) === cmpTerm(q.object);
@@ -1398,6 +1972,7 @@
1398
1972
  if (dup) {
1399
1973
  dup.agreed += 1;
1400
1974
  dup.confidence = Math.max(dup.confidence, c.confidence);
1975
+ if (detCount(c.parsed) < detCount(dup.parsed)) dup.parsed = c.parsed;
1401
1976
  continue;
1402
1977
  }
1403
1978
  distinct.push({ ...c, agreed: 1 });
@@ -1422,7 +1997,7 @@
1422
1997
  var randomBytes4 = unavailable4("randomBytes");
1423
1998
 
1424
1999
  // node_modules/@polycode-projects/the-mechanical-code-talker/src/wink-model.mjs
1425
- var import_meta = {};
2000
+ var import_meta2 = {};
1426
2001
  var injected;
1427
2002
  var cached;
1428
2003
  function loadWinkModel() {
@@ -1436,7 +2011,7 @@
1436
2011
  return cached;
1437
2012
  }
1438
2013
  function nodeRequireWink() {
1439
- const require2 = createRequire4(import_meta.url);
2014
+ const require2 = createRequire4(import_meta2.url);
1440
2015
  return {
1441
2016
  winkNLP: require2("wink-nlp"),
1442
2017
  model: require2("wink-eng-lite-web-model")
@@ -1526,7 +2101,8 @@
1526
2101
  }
1527
2102
 
1528
2103
  // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/pipeline.mjs
1529
- var STRATEGIES = [grammarStrategy, keywordSpotStrategy, noiseStripStrategy];
2104
+ var OPTIONAL_STRATEGIES = typeof aceStrategy !== "undefined" ? [aceStrategy] : [];
2105
+ var STRATEGIES = [grammarStrategy, keywordSpotStrategy, noiseStripStrategy, ...OPTIONAL_STRATEGIES];
1530
2106
  function runStrategiesSync(text, ctx = {}, strategies = STRATEGIES) {
1531
2107
  const results = [];
1532
2108
  for (const s of strategies) {
@@ -1542,11 +2118,15 @@
1542
2118
  // node_modules/@polycode-projects/the-mechanical-code-talker/src/ask.mjs
1543
2119
  function edgesOfKind(graph, kind) {
1544
2120
  const out = [];
1545
- for (const g of graph.relations) if (relationKind(g) === kind) out.push(...g.edges);
2121
+ for (const g of graph.relations) {
2122
+ if (relationKind(g) !== kind) continue;
2123
+ for (const e of g.edges) out.push(e);
2124
+ }
1546
2125
  return out;
1547
2126
  }
1548
2127
  var SYMBOL_GRAIN_SIBLING = { calls: "callsSymbol", touches: "touchesSymbol" };
1549
2128
  var FINE_ENTITY_TYPES = /* @__PURE__ */ new Set(["Function", "Method", "Class", "Attribute", "GlobalVariable"]);
2129
+ var FINE_CLASS_SIBLING = { Function: "Method", Method: "Function" };
1550
2130
  var KIND_UNIONS = { uses: ["imports", "calls", "callsSymbol"] };
1551
2131
  var kindsFor = (kind) => KIND_UNIONS[kind] || [kind];
1552
2132
  var OVERFLOW_CAP = 12;
@@ -1570,6 +2150,10 @@
1570
2150
  function verbFor(kind) {
1571
2151
  return REVERSE_MISS_VERB[kind] || kind;
1572
2152
  }
2153
+ var LEADING_RELATION_VERB_RE = new RegExp(
2154
+ `^(?:${Object.keys(VERB_TO_KIND).sort((a, b) => b.length - a.length).map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:s|ing|ed)?\\s+`,
2155
+ "i"
2156
+ );
1573
2157
  function defaultNlp() {
1574
2158
  return typeof nlpAdapter === "function" ? nlpAdapter() : null;
1575
2159
  }
@@ -1577,7 +2161,7 @@
1577
2161
  const adapter = nlp === void 0 ? defaultNlp() : nlp;
1578
2162
  const raw = String(query || "").trim().replace(/\s+/g, " ");
1579
2163
  if (!raw) return null;
1580
- const text = applyNegationFrames(normalizeQuery(raw));
2164
+ const text = applyPhrasingFrames(applyNegationFrames(normalizeQuery(raw)));
1581
2165
  if (!text) return null;
1582
2166
  const composite = parseComposite(text, adapter);
1583
2167
  if (composite) return composite;
@@ -1596,7 +2180,7 @@
1596
2180
  function parseComposite(text, nlp) {
1597
2181
  const w = splitWords(text);
1598
2182
  const lc = w.map((x) => x.toLowerCase());
1599
- return parseNegation(text, nlp, 0) || parseForwardNegation(w, lc, nlp) || parseAnaphora(w, lc, nlp) || parseAggregate(w, lc, nlp) || parseSuperlative(w, lc, nlp) || parseList(w, lc, nlp, 0) || parseNested(w, lc, nlp, 0) || parseRelationalOrQualified(w, lc, nlp, 0);
2183
+ return parseNegation(text, nlp, 0) || parseForwardNegation(w, lc, nlp) || parseTemporal(w, lc, nlp, 0) || parseAnaphora(w, lc, nlp) || parseAggregate(w, lc, nlp) || parseSuperlative(w, lc, nlp) || parseList(w, lc, nlp, 0) || parseNested(w, lc, nlp, 0) || parseRelationalOrQualified(w, lc, nlp, 0);
1600
2184
  }
1601
2185
  function complementAst(entityType, diffAtom) {
1602
2186
  return {
@@ -1693,6 +2277,58 @@
1693
2277
  }
1694
2278
  return null;
1695
2279
  }
2280
+ var TEMPORAL_AUX = /* @__PURE__ */ new Set(["did", "was", "were", "do", "does", "has", "have", "had"]);
2281
+ var TEMPORAL_TAIL = /* @__PURE__ */ new Set([
2282
+ "change",
2283
+ "changed",
2284
+ "changes",
2285
+ "update",
2286
+ "updated",
2287
+ "updates",
2288
+ "modify",
2289
+ "modified",
2290
+ "modifies",
2291
+ "touch",
2292
+ "touched",
2293
+ "touches",
2294
+ "edit",
2295
+ "edited",
2296
+ "revise",
2297
+ "revised"
2298
+ ]);
2299
+ var TEMPORAL_TRAIL_FILLER = /* @__PURE__ */ new Set(["last", "recently", "ever", "get", "got", "been", "then", "now", "already"]);
2300
+ var TEMPORAL_DET = /* @__PURE__ */ new Set(["the", "a", "an", "all", "those", "these", "any"]);
2301
+ function parseTemporal(w, lc, nlp, depth = 0) {
2302
+ if (lc[0] !== "when") return null;
2303
+ let i = 1;
2304
+ if (!TEMPORAL_AUX.has(lc[i])) return null;
2305
+ i += 1;
2306
+ let t = -1;
2307
+ for (let k = lc.length - 1; k >= i; k -= 1) {
2308
+ if (TEMPORAL_TAIL.has(lc[k])) {
2309
+ t = k;
2310
+ break;
2311
+ }
2312
+ }
2313
+ if (t < 0) return null;
2314
+ let subjWords = w.slice(i, t);
2315
+ let subjLc = lc.slice(i, t);
2316
+ while (subjLc.length && TEMPORAL_TRAIL_FILLER.has(subjLc[subjLc.length - 1])) {
2317
+ subjWords = subjWords.slice(0, -1);
2318
+ subjLc = subjLc.slice(0, -1);
2319
+ }
2320
+ while (subjLc.length && TEMPORAL_DET.has(subjLc[0])) {
2321
+ subjWords = subjWords.slice(1);
2322
+ subjLc = subjLc.slice(1);
2323
+ }
2324
+ if (!subjWords.length) return null;
2325
+ if (!subjLc.some((x) => RELATIVE_PRONOUNS.includes(x))) return null;
2326
+ const framed = FRAME_WORDS.has(subjLc[0]) ? subjWords.join(" ") : `which ${subjWords.join(" ")}`;
2327
+ const inner = parseSetPhrase(framed, nlp, depth + 1);
2328
+ if (!inner || inner.node === "miss") return inner ? { node: "miss", reason: inner.reason || "the inner set of the temporal query didn't parse" } : { node: "miss", reason: "the inner set of the temporal query didn't parse" };
2329
+ const noun = entityNoun(subjLc[0]);
2330
+ return { node: "temporal", inner, entityType: noun && noun.entityType || null };
2331
+ }
1696
2332
  function parseAnaphora(w, lc, nlp) {
1697
2333
  let p = -1;
1698
2334
  let viaOf = false;
@@ -1958,21 +2594,21 @@
1958
2594
  let start = 0;
1959
2595
  let i = 0;
1960
2596
  while (i < predLc.length) {
1961
- let hit = null;
2597
+ let hit2 = null;
1962
2598
  for (const c of conns) {
1963
2599
  const cw = c.split(" ");
1964
2600
  if (predLc.slice(i, i + cw.length).join(" ") === c) {
1965
- hit = { c, len: cw.length };
2601
+ hit2 = { c, len: cw.length };
1966
2602
  break;
1967
2603
  }
1968
2604
  }
1969
- if (hit && i > start) {
2605
+ if (hit2 && i > start) {
1970
2606
  branches.push(predWords.slice(start, i));
1971
- ops.push(BOOLEAN_CONNECTIVES[hit.c]);
1972
- i += hit.len;
2607
+ ops.push(BOOLEAN_CONNECTIVES[hit2.c]);
2608
+ i += hit2.len;
1973
2609
  start = i;
1974
- } else if (hit) {
1975
- i += hit.len;
2610
+ } else if (hit2) {
2611
+ i += hit2.len;
1976
2612
  start = i;
1977
2613
  } else i += 1;
1978
2614
  }
@@ -1985,7 +2621,9 @@
1985
2621
  const edges2 = edgesOfKind(graph, symbolKind).filter((e) => objectIds.has(e.object));
1986
2622
  return uniqueById(edges2.map((e) => graph.byId.get(e.subject)).filter((s) => s && s.class === entityType));
1987
2623
  }
1988
- const edges = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => objectIds.has(e.object));
2624
+ const objHasFine = !!symbolKind && [...objectIds].some((id) => FINE_ENTITY_TYPES.has(graph.byId.get(id)?.class));
2625
+ const scanKinds = objHasFine ? [...kindsFor(kind), symbolKind] : kindsFor(kind);
2626
+ const edges = scanKinds.flatMap((k) => edgesOfKind(graph, k)).filter((e) => objectIds.has(e.object));
1989
2627
  const subjects = uniqueById(edges.map((e) => graph.byId.get(e.subject)).filter(Boolean));
1990
2628
  if (!entityType || entityType === "Change") return subjects;
1991
2629
  const direct = subjects.filter((s) => s.class === entityType);
@@ -2165,6 +2803,15 @@
2165
2803
  }
2166
2804
  return n;
2167
2805
  }
2806
+ function evalTemporal(graph, ast, opts) {
2807
+ const inner = evalSet(graph, ast.inner, opts);
2808
+ const ids = new Set(inner.map((i) => i.id));
2809
+ if (!ids.size) return { compositeKind: "temporal", matches: [], entityType: ast.entityType, innerCount: 0 };
2810
+ const commits = reverseOverSet(graph, "touches", "Commit", ids);
2811
+ const dateOf = (c) => String((c.attributes || []).find((a) => a.key === "date")?.value || "");
2812
+ commits.sort((a, b) => dateOf(b).localeCompare(dateOf(a)));
2813
+ return { compositeKind: "temporal", matches: commits, entityType: ast.entityType, innerCount: inner.length };
2814
+ }
2168
2815
  function evalSuperlative(graph, ast) {
2169
2816
  const pool = graph.individuals.filter((i) => i.class === ast.entityType);
2170
2817
  const scored = pool.map((ind) => ({ ind, score: degreeMetric(graph, ind, ast.metric) })).sort((a, z) => ast.extreme === "most" ? z.score - a.score : a.score - z.score);
@@ -2178,6 +2825,7 @@
2178
2825
  if (ast.node === "count") return { compositeKind: "count", count: evalSet(graph, ast.base, opts).length, entityType: ast.entityType, matches: [] };
2179
2826
  if (ast.node === "list") return { compositeKind: "list", matches: evalSet(graph, ast.base, opts), entityType: ast.entityType, scoped: ast.scoped };
2180
2827
  if (ast.node === "superlative") return evalSuperlative(graph, ast);
2828
+ if (ast.node === "temporal") return evalTemporal(graph, ast, opts);
2181
2829
  if (ast.node === "anaphora") return evalAnaphora(graph, ast, opts);
2182
2830
  return { compositeKind: "set", matches: evalSet(graph, ast, opts), entityType: ast.entityType || null };
2183
2831
  }
@@ -2215,6 +2863,31 @@
2215
2863
  matches: result.matches
2216
2864
  };
2217
2865
  }
2866
+ if (result.compositeKind === "temporal") {
2867
+ const n = result.innerCount || 0;
2868
+ const setNoun = result.entityType ? nounFor(result.entityType, n || 2) : n === 1 ? "entity" : "entities";
2869
+ const wasWere = n === 1 ? "was" : "were";
2870
+ if (!n) {
2871
+ return { content: `nothing in the index matches the inner set, so there is no change history to date.`, miss: true, ambiguous: false, matches: [] };
2872
+ }
2873
+ if (!result.matches.length) {
2874
+ return { content: `no recorded commit touched the ${n} ${setNoun} in that set in this index.`, miss: true, ambiguous: false, matches: [] };
2875
+ }
2876
+ const newest = result.matches[0];
2877
+ const date = (newest.attributes || []).find((a) => a.key === "date")?.value || "";
2878
+ if (!date) {
2879
+ return { content: `the ${setNoun} in that set ${wasWere} last touched by commit ${newest.label}, but this index records no commit dates \u2014 regenerate the graph to attach mgx:commitDate.`, miss: true, ambiguous: false, matches: result.matches };
2880
+ }
2881
+ const msg = (newest.attributes || []).find((a) => a.key === "message")?.value || "";
2882
+ const day = String(date).slice(0, 10);
2883
+ const more = result.matches.length - 1;
2884
+ return {
2885
+ content: `the ${setNoun} in that set ${wasWere} last touched by commit ${newest.label} on ${day}${msg ? ` ("${msg}")` : ""}${more ? `; ${more} earlier commit${more === 1 ? "" : "s"} recorded` : ""}.`,
2886
+ miss: false,
2887
+ ambiguous: false,
2888
+ matches: result.matches
2889
+ };
2890
+ }
2218
2891
  if (!result.matches.length) {
2219
2892
  return { content: `nothing in the index matches that${result.entityType ? ` (${nounFor(result.entityType, 2)})` : ""}.`, miss: true, ambiguous: false, matches: [] };
2220
2893
  }
@@ -2385,7 +3058,24 @@
2385
3058
  const token = (i.attributes || []).find((a) => a.key === "token")?.value;
2386
3059
  return token && String(token).toLowerCase() === termLc;
2387
3060
  });
2388
- if (!match) return { matches: [], objMatch: null, candidates: [], traversal: `schema lookup for "${term}"`, ambiguous: false };
3061
+ if (!match) {
3062
+ const classHits = (graph.individuals || []).filter((i) => i.class === "Class" && String(i.label).toLowerCase() === termLc);
3063
+ if (classHits.length === 1) {
3064
+ const hit2 = classHits[0];
3065
+ const mid = moduleIdOf2(graph, hit2);
3066
+ const modLabel = mid && graph.byId.get(mid)?.label || String((hit2.attributes || []).find((a) => a.key === "site")?.value || "").split(":")[0] || null;
3067
+ return {
3068
+ matches: [hit2],
3069
+ objMatch: hit2,
3070
+ candidates: [],
3071
+ ambiguous: false,
3072
+ metaCodeClass: true,
3073
+ metaModuleLabel: modLabel,
3074
+ traversal: `schema lookup for "${term}" (miss), then unique Class individual by label`
3075
+ };
3076
+ }
3077
+ return { matches: [], objMatch: null, candidates: [], traversal: `schema lookup for "${term}"`, ambiguous: false };
3078
+ }
2389
3079
  return {
2390
3080
  matches: [match],
2391
3081
  objMatch: match,
@@ -2494,9 +3184,13 @@
2494
3184
  return commitTouches(graph, objMatch, entityType, { candidates, ambiguous, matchedVia });
2495
3185
  }
2496
3186
  if (shape === "forward") {
2497
- const edges2 = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.subject === objMatch.id);
2498
- const matches2 = edges2.map((e) => graph.byId.get(e.object)).filter(Boolean);
2499
- return { matches: matches2, objMatch, candidates, traversal: `${kindsFor(kind).join("+")} edges where subject = ${objMatch.label}`, ambiguous, matchedVia };
3187
+ const fwdSibling = SYMBOL_GRAIN_SIBLING[kind];
3188
+ const subjIsFineSymbol = !!(fwdSibling && objMatch.class && FINE_ENTITY_TYPES.has(objMatch.class));
3189
+ const fwdKinds = subjIsFineSymbol ? [.../* @__PURE__ */ new Set([...kindsFor(kind), fwdSibling])] : kindsFor(kind);
3190
+ const edges2 = fwdKinds.flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.subject === objMatch.id);
3191
+ const targets = edges2.map((e) => graph.byId.get(e.object)).filter(Boolean);
3192
+ const matches2 = subjIsFineSymbol ? uniqueById(targets) : targets;
3193
+ return { matches: matches2, objMatch, candidates, traversal: `${fwdKinds.join("+")} edges where subject = ${objMatch.label}`, ambiguous, matchedVia };
2500
3194
  }
2501
3195
  if (parsed.modifier === "transitive") {
2502
3196
  const levels = impactClosure(graph, objMatch, { maxDepth: TRANSITIVE_MAX_DEPTH });
@@ -2515,8 +3209,17 @@
2515
3209
  if (symbolKind && (FINE_ENTITY_TYPES.has(entityType) || objIsFineSymbol)) {
2516
3210
  const edges2 = edgesOfKind(graph, symbolKind).filter((e) => e.object === objMatch.id);
2517
3211
  const subjects2 = uniqueById(edges2.map((e) => graph.byId.get(e.subject)).filter(Boolean));
2518
- const matches2 = !entityType || entityType === "Change" ? subjects2 : subjects2.filter((i) => i.class === entityType);
2519
- return { matches: matches2, objMatch, candidates, traversal: `${symbolKind} edges where object = ${objMatch.label}`, ambiguous, matchedVia };
3212
+ let matches2 = !entityType || entityType === "Change" ? subjects2 : subjects2.filter((i) => i.class === entityType);
3213
+ let widenNote = "";
3214
+ const siblingClass = FINE_CLASS_SIBLING[entityType];
3215
+ if (!matches2.length && siblingClass) {
3216
+ const widened = subjects2.filter((i) => i.class === siblingClass);
3217
+ if (widened.length) {
3218
+ matches2 = widened;
3219
+ widenNote = `, widened to ${siblingClass} subjects (no ${entityType} recorded)`;
3220
+ }
3221
+ }
3222
+ return { matches: matches2, objMatch, candidates, traversal: `${symbolKind} edges where object = ${objMatch.label}${widenNote}`, ambiguous, matchedVia };
2520
3223
  }
2521
3224
  let edges = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.object === objMatch.id);
2522
3225
  let extNote = "";
@@ -2562,6 +3265,11 @@
2562
3265
  const label = String(ind.label || ind.id || "");
2563
3266
  return ["Function", "Method"].includes(ind.class) ? `function ${label}()` : label;
2564
3267
  }
3268
+ function commitRefOf(ind) {
3269
+ const sha = String(ind.label || ind.id || "");
3270
+ const author = (ind.attributes || []).find((a) => a.key === "author")?.value;
3271
+ return author ? `${sha} (${author})` : sha;
3272
+ }
2565
3273
  function listJoin(syms) {
2566
3274
  return syms.length > 1 ? `${syms.slice(0, -1).join(", ")} and ${syms[syms.length - 1]}` : syms[0];
2567
3275
  }
@@ -2613,6 +3321,16 @@
2613
3321
  ambiguous: false
2614
3322
  };
2615
3323
  }
3324
+ if (result.metaCodeClass) {
3325
+ const label = result.objMatch.label;
3326
+ const definedIn = result.metaModuleLabel ? `, defined in ${result.metaModuleLabel}` : "";
3327
+ return {
3328
+ content: `${label} is a class in this codebase${definedIn} \u2014 try "describe ${label}" or "which classes inherit from ${label}".`,
3329
+ miss: false,
3330
+ ambiguous: false,
3331
+ matches: result.matches
3332
+ };
3333
+ }
2616
3334
  const doc = (result.objMatch.attributes || []).find((a) => a.key === "doc")?.value || "";
2617
3335
  const kindWord = result.objMatch.class === "SchemaClass" ? "a class in the graph's schema" : "a predicate (relation) in the graph's schema";
2618
3336
  return { content: `${result.objMatch.label} is ${kindWord}: ${doc}`, miss: false, ambiguous: false, matches: result.matches };
@@ -2620,7 +3338,7 @@
2620
3338
  if (result.mentionsShape) {
2621
3339
  if (!result.matches.length) {
2622
3340
  return {
2623
- content: `"${parsed.object}" is not mentioned in any indexed identifier or doc-comment prose. (traversal: ${result.traversal})`,
3341
+ content: `"${parsed.object}" is not mentioned in any indexed identifier or doc-comment prose.`,
2624
3342
  miss: true,
2625
3343
  ambiguous: false
2626
3344
  };
@@ -2677,7 +3395,7 @@
2677
3395
  if (result.whenShape) {
2678
3396
  const subject = result.objMatch.label;
2679
3397
  if (!result.matches.length) {
2680
- return { content: `no recorded commit touches ${subject} in this index. (traversal: ${result.traversal})`, miss: true, ambiguous: false };
3398
+ return { content: `no recorded commit touches ${subject} in this index.`, miss: true, ambiguous: false };
2681
3399
  }
2682
3400
  const newest = result.matches[0];
2683
3401
  const date = (newest.attributes || []).find((a) => a.key === "date")?.value || "";
@@ -2705,7 +3423,7 @@
2705
3423
  const cite = `commit ${result.objMatch.label}`;
2706
3424
  if (!result.matches.length) {
2707
3425
  return {
2708
- content: `${cite} touched nothing recorded in the index. (traversal: ${result.traversal})`,
3426
+ content: `${cite} touched nothing recorded in the index.`,
2709
3427
  miss: true,
2710
3428
  ambiguous: false
2711
3429
  };
@@ -2725,7 +3443,7 @@
2725
3443
  return { content: `couldn't resolve one of the terms in this question.`, miss: true, ambiguous: false };
2726
3444
  }
2727
3445
  return {
2728
- content: result.answer ? `Yes. (${result.traversal})` : `No \u2014 no ${parsed.kind} edge found from ${result.subjMatch.label} to ${result.objMatch.label}.`,
3446
+ content: result.answer ? `Yes \u2014 ${result.traversal}.` : `No \u2014 no ${parsed.kind} edge found from ${result.subjMatch.label} to ${result.objMatch.label}.`,
2729
3447
  miss: !result.answer,
2730
3448
  ambiguous: false
2731
3449
  };
@@ -2733,20 +3451,29 @@
2733
3451
  if (!result.matches.length) {
2734
3452
  if (parsed.shape === "forward") {
2735
3453
  return {
2736
- content: `${result.objMatch.label} has no ${parsed.kind} edges in the index. (traversal: ${result.traversal || "no traversal resolved"})`,
3454
+ content: `${result.objMatch.label} has no ${parsed.kind} edges in the index.`,
3455
+ miss: true,
3456
+ ambiguous: false
3457
+ };
3458
+ }
3459
+ if (parsed.kind === "tests" && !parsed.entityType) {
3460
+ const stripped = String(parsed.object || "").replace(LEADING_RELATION_VERB_RE, "").trim();
3461
+ const obj = stripped || String(parsed.object || "").trim();
3462
+ return {
3463
+ content: `No tests cover ${obj}.`,
2737
3464
  miss: true,
2738
3465
  ambiguous: false
2739
3466
  };
2740
3467
  }
2741
3468
  const entityWord = nounFor(parsed.entityType || "Module", 2);
2742
3469
  return {
2743
- content: `No ${entityWord} found whose module directly ${verbFor(parsed.kind)} ${parsed.object}. (traversal: ${result.traversal || "no traversal resolved"})`,
3470
+ content: `No ${entityWord} found whose module directly ${verbFor(parsed.kind)} ${parsed.object}.`,
2744
3471
  miss: true,
2745
3472
  ambiguous: false
2746
3473
  };
2747
3474
  }
2748
3475
  if (parsed.shape === "forward" || parsed.entityType === "Module" || result.matches.every((m) => !FINE_ENTITY_TYPES.has(m.class))) {
2749
- const shown = result.matches.slice(0, OVERFLOW_CAP).map((m) => m.label);
3476
+ const shown = result.matches.slice(0, OVERFLOW_CAP).map((m) => m.class === "Commit" ? commitRefOf(m) : m.label);
2750
3477
  const extra2 = result.matches.length > OVERFLOW_CAP ? `, \u2026and ${result.matches.length - OVERFLOW_CAP} more` : "";
2751
3478
  return { content: shown.join(" and ") + extra2 + ".", miss: false, ambiguous: false, matches: result.matches };
2752
3479
  }
@@ -2806,17 +3533,17 @@
2806
3533
  function fuzzyCascadeWord(w) {
2807
3534
  const bound = fuzzyBound(w);
2808
3535
  let best = bound + 1;
2809
- let hit = null;
3536
+ let hit2 = null;
2810
3537
  let tied = false;
2811
3538
  for (const target of CASCADE_FUZZY_TARGETS) {
2812
3539
  const d = editDistance(w, target, Math.min(best, bound));
2813
3540
  if (d < best) {
2814
3541
  best = d;
2815
- hit = target;
3542
+ hit2 = target;
2816
3543
  tied = false;
2817
- } else if (d === best && d <= bound && target !== hit) tied = true;
3544
+ } else if (d === best && d <= bound && target !== hit2) tied = true;
2818
3545
  }
2819
- return best <= bound && !tied ? hit : null;
3546
+ return best <= bound && !tied ? hit2 : null;
2820
3547
  }
2821
3548
  function schemaTypoTrap(resolution, term) {
2822
3549
  if (!resolution?.match || resolution.matchedVia !== "fuzzy" || resolution.ambiguous) return false;
@@ -2875,7 +3602,7 @@
2875
3602
  const rendered = render(p, traverse(graph, p, { contextId, prev }));
2876
3603
  return rendered.miss ? null : { parsed: p, text };
2877
3604
  };
2878
- const done = (hit) => ({ parsed: hit.parsed, from, to: hit.text, dropped: [...dropped], steps });
3605
+ const done = (hit2) => ({ parsed: hit2.parsed, from, to: hit2.text, dropped: [...dropped], steps });
2879
3606
  let guard = 0;
2880
3607
  const hardCap = Math.max(tokens.length, 1) + 12;
2881
3608
  for (; guard < hardCap; guard += 1) {
@@ -2892,8 +3619,8 @@
2892
3619
  tokens = tokens.filter((_, i) => i !== idx);
2893
3620
  dropped.push(removed);
2894
3621
  steps.push(`strip noise "${removed}" \u2192 "${tokens.join(" ")}"`);
2895
- const hit = attempt(tokens);
2896
- if (hit) return done(hit);
3622
+ const hit2 = attempt(tokens);
3623
+ if (hit2) return done(hit2);
2897
3624
  }
2898
3625
  const survivors = [];
2899
3626
  const nowDropped = [];
@@ -2918,8 +3645,8 @@
2918
3645
  dropped.push(...nowDropped);
2919
3646
  if (corrected.length) steps.push(`fuzzy-correct ${JSON.stringify(corrected)} \u2192 "${tokens.join(" ")}"`);
2920
3647
  if (nowDropped.length) steps.push(`drop unmatched ${JSON.stringify(nowDropped)} \u2192 "${tokens.join(" ")}"`);
2921
- const hit = attempt(tokens);
2922
- if (hit) return done(hit);
3648
+ const hit2 = attempt(tokens);
3649
+ if (hit2) return done(hit2);
2923
3650
  }
2924
3651
  let changed = false;
2925
3652
  const normed = tokens.map((t) => {
@@ -2932,8 +3659,8 @@
2932
3659
  });
2933
3660
  if (changed) {
2934
3661
  steps.push(`normalise synonyms \u2192 "${normed.join(" ")}"`);
2935
- const hit = attempt(normed);
2936
- if (hit) return done(hit);
3662
+ const hit2 = attempt(normed);
3663
+ if (hit2) return done(hit2);
2937
3664
  }
2938
3665
  const bareLc = splitWords(from).map((t) => t.toLowerCase());
2939
3666
  const kindWords = [];
@@ -2945,10 +3672,10 @@
2945
3672
  else others.push(t);
2946
3673
  }
2947
3674
  if (kindWords.length === 1 && others.length === 0) {
2948
- const hit = attempt(["count", kindWords[0]]);
2949
- if (hit) {
3675
+ const hit2 = attempt(["count", kindWords[0]]);
3676
+ if (hit2) {
2950
3677
  steps.push(`bare kind "${kindWords[0]}" \u2192 count`);
2951
- return done(hit);
3678
+ return done(hit2);
2952
3679
  }
2953
3680
  }
2954
3681
  return null;
@@ -3013,7 +3740,7 @@
3013
3740
  };
3014
3741
  }
3015
3742
 
3016
- // packages/seonix/src/ask-browser-entry.mjs
3743
+ // src/ask-browser-entry.mjs
3017
3744
  function parseEntities(payload) {
3018
3745
  const individuals = Array.isArray(payload?.individuals) ? payload.individuals : [];
3019
3746
  const byId = /* @__PURE__ */ new Map();