@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.
- package/README.md +24 -8
- package/bin/cli.mjs +92 -11
- package/package.json +48 -7
- package/src/ask-browser.bundle.js +776 -49
- package/src/browser.mjs +9 -3
- package/src/codegraph.mjs +126 -18
- package/src/cs_treesitter.mjs +58 -3
- package/src/extract.mjs +98 -23
- package/src/extract_lang.mjs +4 -2
- package/src/interfaces.mjs +110 -16
- package/src/jsts_tsc.mjs +51 -0
- package/src/schema-docs.mjs +33 -3
- package/src/server.mjs +51 -7
- package/src/summary.mjs +14 -3
- package/src/telemetry.mjs +31 -0
|
@@ -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
|
|
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
|
-
|
|
1222
|
+
hit2 = target;
|
|
1068
1223
|
tied = false;
|
|
1069
|
-
} else if (d === best && d <= bound && target !==
|
|
1224
|
+
} else if (d === best && d <= bound && target !== hit2) tied = true;
|
|
1070
1225
|
}
|
|
1071
|
-
return best <= bound && !tied ?
|
|
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 = (
|
|
1257
|
-
if (
|
|
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
|
|
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(
|
|
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
|
|
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)
|
|
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
|
|
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
|
-
|
|
2601
|
+
hit2 = { c, len: cw.length };
|
|
1966
2602
|
break;
|
|
1967
2603
|
}
|
|
1968
2604
|
}
|
|
1969
|
-
if (
|
|
2605
|
+
if (hit2 && i > start) {
|
|
1970
2606
|
branches.push(predWords.slice(start, i));
|
|
1971
|
-
ops.push(BOOLEAN_CONNECTIVES[
|
|
1972
|
-
i +=
|
|
2607
|
+
ops.push(BOOLEAN_CONNECTIVES[hit2.c]);
|
|
2608
|
+
i += hit2.len;
|
|
1973
2609
|
start = i;
|
|
1974
|
-
} else if (
|
|
1975
|
-
i +=
|
|
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
|
|
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)
|
|
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
|
|
2498
|
-
const
|
|
2499
|
-
|
|
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
|
-
|
|
2519
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}
|
|
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
|
|
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
|
-
|
|
3542
|
+
hit2 = target;
|
|
2816
3543
|
tied = false;
|
|
2817
|
-
} else if (d === best && d <= bound && target !==
|
|
3544
|
+
} else if (d === best && d <= bound && target !== hit2) tied = true;
|
|
2818
3545
|
}
|
|
2819
|
-
return best <= bound && !tied ?
|
|
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 = (
|
|
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
|
|
2896
|
-
if (
|
|
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
|
|
2922
|
-
if (
|
|
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
|
|
2936
|
-
if (
|
|
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
|
|
2949
|
-
if (
|
|
3675
|
+
const hit2 = attempt(["count", kindWords[0]]);
|
|
3676
|
+
if (hit2) {
|
|
2950
3677
|
steps.push(`bare kind "${kindWords[0]}" \u2192 count`);
|
|
2951
|
-
return done(
|
|
3678
|
+
return done(hit2);
|
|
2952
3679
|
}
|
|
2953
3680
|
}
|
|
2954
3681
|
return null;
|
|
@@ -3013,7 +3740,7 @@
|
|
|
3013
3740
|
};
|
|
3014
3741
|
}
|
|
3015
3742
|
|
|
3016
|
-
//
|
|
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();
|