@polycode-projects/seonix 0.10.10 → 0.10.14
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/package.json +3 -2
- package/src/ask-browser.bundle.js +155 -11
- package/src/ask-browser.bundle.version +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/seonix",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "SEONIX — a deterministic, offline, $0 code-graph tool that makes a cheap coding agent edit like an expensive one. Indexes a repo with Python ast + git (zero model calls) and renders a bounded edit-digest.",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"setup": "node scripts/setup.mjs",
|
|
51
51
|
"bench": "node bench/run.mjs",
|
|
52
52
|
"bench:quick": "node bench/run.mjs --instances 1 --runs 1",
|
|
53
|
+
"bench:telemetry": "node bench/run.mjs --suite django-lh --arms seonix-ask --instances 1 --runs 1 --telemetry",
|
|
53
54
|
"join:telemetry": "node scripts/join-telemetry.mjs",
|
|
54
55
|
"bench:lite": "node bench/run.mjs --suite lite",
|
|
55
56
|
"bench:seonix": "node bench/run.mjs --suite django-lh --arms seonix,seonix-min --runs 5",
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
},
|
|
89
90
|
"dependencies": {
|
|
90
91
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
91
|
-
"@polycode-projects/the-mechanical-code-talker": "^0.
|
|
92
|
+
"@polycode-projects/the-mechanical-code-talker": "^1.0.4",
|
|
92
93
|
"cytoscape": "^3.30.0",
|
|
93
94
|
"smol-toml": "^1.7.0",
|
|
94
95
|
"tree-sitter": "^0.21.1",
|
|
@@ -590,6 +590,10 @@
|
|
|
590
590
|
"touchs": "touches",
|
|
591
591
|
"tuoches": "touches",
|
|
592
592
|
"touhced": "touched",
|
|
593
|
+
// WHERE_MARKERS typo (0.9.13 Tier-1 playtest): "defined" itself had no typo
|
|
594
|
+
// entry, so "where is it defned" fell through to the bare-object search path
|
|
595
|
+
// instead of the where-shape ("no module matching 'it defned' found").
|
|
596
|
+
"defned": "defined",
|
|
593
597
|
"chagned": "changed",
|
|
594
598
|
"chnaged": "changed",
|
|
595
599
|
"chagnes": "changes",
|
|
@@ -602,6 +606,17 @@
|
|
|
602
606
|
"whcih": "which",
|
|
603
607
|
"wich": "which",
|
|
604
608
|
"whihc": "which",
|
|
609
|
+
// "wehre"/"whre" (0.9.13 Tier-1 playtest, "where is it defined" drill-down):
|
|
610
|
+
// the WHERE-DEFINED shape's own anchor word had NO typo tolerance at all
|
|
611
|
+
// (unlike which/what/does/the above), so a plain dropped/transposed letter
|
|
612
|
+
// fell straight through resolveObject and hit either the grammar wall or a
|
|
613
|
+
// bogus "no module matching 'it defined'" search. "were" (the missing-h
|
|
614
|
+
// homophone slip) is NOT curated here — it's a real word already load-bearing
|
|
615
|
+
// as the TEMPORAL_AUX auxiliary ("when were the modules last touched"), so
|
|
616
|
+
// that one typo is handled by its own anchored phrasing frame instead
|
|
617
|
+
// (normalize.mjs PHRASING_FRAMES) to avoid clobbering the legitimate reading.
|
|
618
|
+
"wehre": "where",
|
|
619
|
+
"whre": "where",
|
|
605
620
|
// "wat" (chatbench cycle 2, tf-wat-calls): the internet-casual spelling of
|
|
606
621
|
// "what" — neither curated noise nor a restorable trigger typo, so "wat calls
|
|
607
622
|
// fnAlpha" used to die as "couldn't resolve one of the terms". Restored here
|
|
@@ -685,7 +700,8 @@
|
|
|
685
700
|
"kinda",
|
|
686
701
|
"sorta",
|
|
687
702
|
"btw",
|
|
688
|
-
"by the way"
|
|
703
|
+
"by the way",
|
|
704
|
+
"you"
|
|
689
705
|
]);
|
|
690
706
|
var CONTEXT_PRONOUNS = Object.freeze(["this", "it", "that", "here", "this one", "that one"]);
|
|
691
707
|
var NEGATION_FRAMES = Object.freeze([
|
|
@@ -992,9 +1008,10 @@
|
|
|
992
1008
|
const words = rest.replace(/\?+\s*$/, "").trim().split(/\s+/);
|
|
993
1009
|
return LISTING_TAIL_KINDS.has((words[words.length - 1] || "").toLowerCase());
|
|
994
1010
|
};
|
|
995
|
-
var GREETING_PREAMBLE_RE = /^(?:hi|hiya|hello|hey|yo|howdy)(?:\s+there)?\s*[
|
|
1011
|
+
var GREETING_PREAMBLE_RE = /^(?:hi|hiya|hello|hey|yo|howdy|g'?day|good\s+(?:morning|afternoon|evening|day)|greetings|salutations)(?:\s+there)?\s*[,.—–-]\s*(?:(?:just\s+a\s+)?quick\s+question\s*[,:—–-]?\s*)?(.+)$/i;
|
|
996
1012
|
var THANKS_PREAMBLE_RE = /^(?:thanks|thank\s+you|many\s+thanks|thx|ty|cheers)(?:\s+(?:so\s+much|a\s+lot|very\s+much|a\s+bunch))?\s*[,—–-]\s*(?:(?:just\s+a\s+)?quick\s+question\s*[,:—–-]?\s*)?(.+)$/i;
|
|
997
1013
|
var MODAL_WRAPPER_RE = /^(?:can|could|would|will)\s+you\s+(?:please\s+)?(.+?)(?:[,\s]+please)?\??$/i;
|
|
1014
|
+
var EXPLAIN_WRAPPER_RE = /^explain\s+(?:to\s+me\s+|please\s+)*(.+?)\??$/i;
|
|
998
1015
|
var SHOW_GIVE_ME_RE = /^(?:show|give)\s+me\s+(?:the\s+)?(.+?)\??$/i;
|
|
999
1016
|
function applyPreambleFrames(text) {
|
|
1000
1017
|
let q = String(text || "");
|
|
@@ -1006,6 +1023,8 @@
|
|
|
1006
1023
|
if (m) q = m[1].trim();
|
|
1007
1024
|
m = q.match(MODAL_WRAPPER_RE);
|
|
1008
1025
|
if (m) q = m[1].trim();
|
|
1026
|
+
m = q.match(EXPLAIN_WRAPPER_RE);
|
|
1027
|
+
if (m && INTERROGATIVE_LEAD_RE.test(m[1].trim())) q = m[1].trim();
|
|
1009
1028
|
m = q.match(SHOW_GIVE_ME_RE);
|
|
1010
1029
|
if (m) {
|
|
1011
1030
|
const rest = m[1].trim();
|
|
@@ -1027,6 +1046,18 @@
|
|
|
1027
1046
|
}
|
|
1028
1047
|
return q;
|
|
1029
1048
|
}
|
|
1049
|
+
var SELF_CORRECTION_RE = /^.+?(?:\s*(?:--|—|-)\s*)?\b(?:sorry|i\s+mean)\b\s*(?:--|—|-|,|:)\s*(.+)$/i;
|
|
1050
|
+
function applySelfCorrectionFrames(text) {
|
|
1051
|
+
let q = String(text || "");
|
|
1052
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
1053
|
+
const m = q.match(SELF_CORRECTION_RE);
|
|
1054
|
+
if (!m) break;
|
|
1055
|
+
const next = m[1].trim();
|
|
1056
|
+
if (!next || next === q) break;
|
|
1057
|
+
q = next;
|
|
1058
|
+
}
|
|
1059
|
+
return q;
|
|
1060
|
+
}
|
|
1030
1061
|
var CONDITIONAL_VERB_GERUND = Object.freeze({
|
|
1031
1062
|
imports: "importing",
|
|
1032
1063
|
calls: "calling",
|
|
@@ -1073,6 +1104,7 @@
|
|
|
1073
1104
|
q = q.replace(WRONG_WORD_RE, (m) => WRONG_WORDS[m.toLowerCase()]);
|
|
1074
1105
|
q = q.replace(G_DROP, "$1ing");
|
|
1075
1106
|
q = applyPreambleFrames(q);
|
|
1107
|
+
q = applySelfCorrectionFrames(q);
|
|
1076
1108
|
q = applySubordinationFrames(q);
|
|
1077
1109
|
q = applyConditionalFrames(q);
|
|
1078
1110
|
if (FILLER_WORDS.length) {
|
|
@@ -1111,6 +1143,15 @@
|
|
|
1111
1143
|
{ 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` },
|
|
1112
1144
|
// "where's X defined" (the "where's" contraction is not in the contraction table)
|
|
1113
1145
|
{ re: /^where'?s\s+(?:the\s+)?(.+?)\s+(defined|declared|located|implemented)\??$/i, to: (m) => `where is ${m[1]} ${m[2]}` },
|
|
1146
|
+
// "were is X defined" (0.9.13 Tier-1 playtest: the missing-h typo of "where").
|
|
1147
|
+
// NOT curated as a blanket MISSPELLINGS entry — "were" is a real word already
|
|
1148
|
+
// load-bearing as the TEMPORAL_AUX auxiliary ("when were the modules last
|
|
1149
|
+
// touched"), so a global word-boundary rewrite would clobber that reading.
|
|
1150
|
+
// This frame is anchored to the WHERE-DEFINED shape specifically ("were is
|
|
1151
|
+
// … defined/declared/located/implemented"), a construction no legitimate
|
|
1152
|
+
// temporal query produces ("were" as an auxiliary never leads directly into
|
|
1153
|
+
// a bare "is").
|
|
1154
|
+
{ re: /^were\s+is\s+(?:the\s+)?(.+?)\s+(defined|declared|located|implemented)\??$/i, to: (m) => `where is ${m[1]} ${m[2]}` },
|
|
1114
1155
|
// PREDICATIVE QUALIFIER → the ATTRIBUTIVE form the grammar already answers. The
|
|
1115
1156
|
// adjective-qualifier post-filters (ask-vocab.mjs QUALIFIERS: tested/untested,
|
|
1116
1157
|
// public/private, exported, static/abstract/constant, …) parse in the ATTRIBUTIVE
|
|
@@ -1304,11 +1345,13 @@
|
|
|
1304
1345
|
return /^[a-z]+$/.test(w) && !STOPWORDS2.has(w) && !VOCAB_WORDS.has(w);
|
|
1305
1346
|
}
|
|
1306
1347
|
function fuzzyVocabWord(w) {
|
|
1307
|
-
|
|
1348
|
+
return fuzzyMatchInSet(w, FUZZY_TARGET_WORDS, fuzzyBound(w));
|
|
1349
|
+
}
|
|
1350
|
+
function fuzzyMatchInSet(w, candidates, bound = fuzzyBound(w)) {
|
|
1308
1351
|
let best = bound + 1;
|
|
1309
1352
|
let hit2 = null;
|
|
1310
1353
|
let tied = false;
|
|
1311
|
-
for (const target of
|
|
1354
|
+
for (const target of candidates) {
|
|
1312
1355
|
const d = editDistance(w, target, Math.min(best, bound));
|
|
1313
1356
|
if (d < best) {
|
|
1314
1357
|
best = d;
|
|
@@ -2281,7 +2324,7 @@
|
|
|
2281
2324
|
}
|
|
2282
2325
|
var MAX_COMPOSE_DEPTH = 4;
|
|
2283
2326
|
var NEST_SENTINEL = "zzinnerset";
|
|
2284
|
-
var PRED_LEAD_SKIP = /* @__PURE__ */ new Set(["that", "which", "who", "are", "is", "was", "were", "do", "does", "also", "still", "both", "and"]);
|
|
2327
|
+
var PRED_LEAD_SKIP = /* @__PURE__ */ new Set(["that", "which", "who", "are", "is", "was", "were", "do", "does", "also", "still", "both", "and", "then", "though"]);
|
|
2285
2328
|
var FRAME_WORDS = /* @__PURE__ */ new Set(["which", "what", "who", "list", "show", "find", "give", "me", "us", "all"]);
|
|
2286
2329
|
var entityNoun = (w) => ENTITY_TO_TYPE[w] ? { entityType: ENTITY_TO_TYPE[w], placeholder: false } : PLACEHOLDER_NOUNS.includes(w) ? { entityType: null, placeholder: true } : null;
|
|
2287
2330
|
var isGerundVerb = (w) => !!VERB_TO_KIND[w] && w.endsWith("ing");
|
|
@@ -2291,7 +2334,7 @@
|
|
|
2291
2334
|
function parseComposite(text, nlp) {
|
|
2292
2335
|
const w = splitWords(text);
|
|
2293
2336
|
const lc = w.map((x) => x.toLowerCase());
|
|
2294
|
-
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) || parseFind(w, lc, nlp, 0) || parseList(w, lc, nlp, 0) || parseNested(w, lc, nlp, 0) || parseRelationalOrQualified(w, lc, nlp, 0);
|
|
2337
|
+
return parseExistence(w, lc) || 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) || parseFind(w, lc, nlp, 0) || parseList(w, lc, nlp, 0) || parseNested(w, lc, nlp, 0) || parseRelationalOrQualified(w, lc, nlp, 0);
|
|
2295
2338
|
}
|
|
2296
2339
|
function complementAst(entityType, diffAtom) {
|
|
2297
2340
|
return {
|
|
@@ -2441,6 +2484,9 @@
|
|
|
2441
2484
|
return { node: "temporal", inner, entityType: noun && noun.entityType || null };
|
|
2442
2485
|
}
|
|
2443
2486
|
function parseAnaphora(w, lc, nlp) {
|
|
2487
|
+
if (lc.length === 2 && lc[0] === "which" && (lc[1] === "ones" || lc[1] === "one")) {
|
|
2488
|
+
return { node: "anaphora", mode: "list", filter: { type: "all" } };
|
|
2489
|
+
}
|
|
2444
2490
|
let p = -1;
|
|
2445
2491
|
let viaOf = false;
|
|
2446
2492
|
for (let i = 1; i < lc.length; i += 1) {
|
|
@@ -2477,6 +2523,46 @@
|
|
|
2477
2523
|
}
|
|
2478
2524
|
return void 0;
|
|
2479
2525
|
}
|
|
2526
|
+
function parseExistence(w, lc) {
|
|
2527
|
+
let i;
|
|
2528
|
+
if (lc[0] === "is" && lc[1] === "there") i = 2;
|
|
2529
|
+
else if (lc[0] === "are" && lc[1] === "there") i = 2;
|
|
2530
|
+
else return null;
|
|
2531
|
+
const article = lc[i];
|
|
2532
|
+
if (article === "a" || article === "an" || article === "any") i += 1;
|
|
2533
|
+
else return null;
|
|
2534
|
+
const noun = i < lc.length ? entityNoun(lc[i]) : null;
|
|
2535
|
+
if (!noun || noun.placeholder || !noun.entityType) return null;
|
|
2536
|
+
const entityType = noun.entityType;
|
|
2537
|
+
i += 1;
|
|
2538
|
+
let rest = lc.slice(i);
|
|
2539
|
+
let restW = w.slice(i);
|
|
2540
|
+
if (rest.length && rest[rest.length - 1] === "anywhere") {
|
|
2541
|
+
rest = rest.slice(0, -1);
|
|
2542
|
+
restW = restW.slice(0, -1);
|
|
2543
|
+
} else if (rest.length >= 2 && rest[rest.length - 2] === "at" && rest[rest.length - 1] === "all") {
|
|
2544
|
+
rest = rest.slice(0, -2);
|
|
2545
|
+
restW = restW.slice(0, -2);
|
|
2546
|
+
}
|
|
2547
|
+
if (!rest.length) return { node: "exists", entityType, term: null, scopeModule: null };
|
|
2548
|
+
if (rest[0] === "called" || rest[0] === "named") {
|
|
2549
|
+
if (rest.length < 2) return { node: "miss", reason: `"${rest[0]}" needs a name afterward` };
|
|
2550
|
+
const inIdx = rest.indexOf("in", 1);
|
|
2551
|
+
if (inIdx > 0) {
|
|
2552
|
+
const term2 = restW.slice(1, inIdx).join(" ").trim();
|
|
2553
|
+
const scopeModule = restW.slice(inIdx + 1).join(" ").trim();
|
|
2554
|
+
if (!term2 || !scopeModule) return { node: "miss", reason: `a named existence check needs both a name and a module after "in"` };
|
|
2555
|
+
return { node: "exists", entityType, term: term2, scopeModule };
|
|
2556
|
+
}
|
|
2557
|
+
const term = restW.slice(1).join(" ").trim();
|
|
2558
|
+
return term ? { node: "exists", entityType, term, scopeModule: null } : { node: "miss", reason: `"${rest[0]}" needs a name afterward` };
|
|
2559
|
+
}
|
|
2560
|
+
if (rest[0] === "in") {
|
|
2561
|
+
const scopeModule = restW.slice(1).join(" ").trim();
|
|
2562
|
+
return scopeModule ? { node: "exists", entityType, term: null, scopeModule } : { node: "miss", reason: `"in" needs a module afterward` };
|
|
2563
|
+
}
|
|
2564
|
+
return null;
|
|
2565
|
+
}
|
|
2480
2566
|
var AGG_TAIL_FILLER = /* @__PURE__ */ new Set([
|
|
2481
2567
|
"total",
|
|
2482
2568
|
"altogether",
|
|
@@ -2492,7 +2578,9 @@
|
|
|
2492
2578
|
"index",
|
|
2493
2579
|
"codebase",
|
|
2494
2580
|
"repo",
|
|
2495
|
-
"repository"
|
|
2581
|
+
"repository",
|
|
2582
|
+
"this",
|
|
2583
|
+
"that"
|
|
2496
2584
|
]);
|
|
2497
2585
|
function parseAggregate(w, lc, nlp) {
|
|
2498
2586
|
const trig = AGGREGATE_TRIGGERS.find((t) => lc.slice(0, t.split(" ").length).join(" ") === t);
|
|
@@ -2524,6 +2612,7 @@
|
|
|
2524
2612
|
var LIST_SKIP = /* @__PURE__ */ new Set(["the", "a", "an", "all", "me", "us"]);
|
|
2525
2613
|
var LIST_TRIGGERS_SORTED = [...LIST_TRIGGERS].sort((a, b) => b.split(" ").length - a.split(" ").length);
|
|
2526
2614
|
var LISTABLE_KINDS = "functions, classes, methods, modules, attributes, variables, or commits";
|
|
2615
|
+
var SCOPE_PREPOSITIONS = /* @__PURE__ */ new Set(["in", "inside", "under"]);
|
|
2527
2616
|
function parseList(w, lc, nlp, depth) {
|
|
2528
2617
|
let i = 0;
|
|
2529
2618
|
let interrogative = false;
|
|
@@ -2560,11 +2649,19 @@
|
|
|
2560
2649
|
i += 1;
|
|
2561
2650
|
const tail = w.slice(i);
|
|
2562
2651
|
const tailMeaningful = lc.slice(i).some((t) => !STOPWORDS2.has(t) && !AGG_TAIL_FILLER.has(t));
|
|
2563
|
-
|
|
2652
|
+
let scopeTailLc = lc.slice(i);
|
|
2653
|
+
let scopeTailWords = tail;
|
|
2654
|
+
if (scopeTailLc[0] === "is" || scopeTailLc[0] === "are") {
|
|
2655
|
+
scopeTailLc = scopeTailLc.slice(1);
|
|
2656
|
+
scopeTailWords = scopeTailWords.slice(1);
|
|
2657
|
+
}
|
|
2658
|
+
const scopedException = interrogative && SCOPE_PREPOSITIONS.has(scopeTailLc[0]);
|
|
2659
|
+
if (interrogative && (tailMeaningful && !scopedException || tail.length === 0)) return null;
|
|
2564
2660
|
let base;
|
|
2565
2661
|
let scoped = false;
|
|
2566
2662
|
if (tailMeaningful) {
|
|
2567
|
-
const
|
|
2663
|
+
const useTail = scopedException ? scopeTailWords : tail;
|
|
2664
|
+
const setAst = parseSetPhrase(`which ${[...quals, entWord, ...useTail].join(" ")}`, nlp, (depth || 0) + 1);
|
|
2568
2665
|
if (!setAst || setAst.node === "miss") return { node: "miss", reason: setAst && setAst.reason || "the list filter didn't parse" };
|
|
2569
2666
|
base = setAst;
|
|
2570
2667
|
scoped = true;
|
|
@@ -2709,7 +2806,7 @@
|
|
|
2709
2806
|
const noun = i < lc.length ? entityNoun(lc[i]) : null;
|
|
2710
2807
|
if (!noun) {
|
|
2711
2808
|
const nextNoun = i + 1 < lc.length ? entityNoun(lc[i + 1]) : null;
|
|
2712
|
-
if ((framed || quals.length) && nextNoun && /^[a-z]+$/.test(lc[i]) && !VERB_TO_KIND[lc[i]] && !STOPWORDS2.has(lc[i])) {
|
|
2809
|
+
if ((framed || quals.length) && nextNoun && /^[a-z]+$/.test(lc[i]) && !VERB_TO_KIND[lc[i]] && !STOPWORDS2.has(lc[i]) && !CASCADE_NOISE_SET.has(lc[i])) {
|
|
2713
2810
|
return { node: "find", entityType: nextNoun.entityType, term: w[i] };
|
|
2714
2811
|
}
|
|
2715
2812
|
return null;
|
|
@@ -3139,8 +3236,33 @@
|
|
|
3139
3236
|
const winners = scored.filter((s) => s.score === best).map((s) => s.ind);
|
|
3140
3237
|
return { compositeKind: "superlative", entityType: ast.entityType, metricNoun: ast.metricNoun, extreme: ast.extreme, score: best, matches: winners };
|
|
3141
3238
|
}
|
|
3239
|
+
function evalExists(graph, ast) {
|
|
3240
|
+
const { entityType, term, scopeModule } = ast;
|
|
3241
|
+
let scopeMatch = null;
|
|
3242
|
+
if (scopeModule) {
|
|
3243
|
+
const r = resolveObject(graph, scopeModule, { expectedClass: "Module" });
|
|
3244
|
+
if (!r.match) return { compositeKind: "exists", entityType, term, scopeModule, scopeMiss: true, matches: [] };
|
|
3245
|
+
scopeMatch = r.match;
|
|
3246
|
+
}
|
|
3247
|
+
if (term) {
|
|
3248
|
+
const r = resolveObject(graph, term, { expectedClass: entityType });
|
|
3249
|
+
const inScope = !scopeMatch || r.match && moduleIdOf2(graph, r.match) === scopeMatch.id;
|
|
3250
|
+
const hit2 = r.match && inScope;
|
|
3251
|
+
return {
|
|
3252
|
+
compositeKind: "exists",
|
|
3253
|
+
entityType,
|
|
3254
|
+
term,
|
|
3255
|
+
scopeModule,
|
|
3256
|
+
scopeMatch,
|
|
3257
|
+
matches: hit2 ? [r.match] : []
|
|
3258
|
+
};
|
|
3259
|
+
}
|
|
3260
|
+
const pool = scopeMatch ? refineToEntities(graph, /* @__PURE__ */ new Set([scopeMatch.id]), entityType) : graph.individuals.filter((i) => i.class === entityType);
|
|
3261
|
+
return { compositeKind: "exists", entityType, term: null, scopeModule, scopeMatch, matches: pool };
|
|
3262
|
+
}
|
|
3142
3263
|
function evalComposite(graph, ast, opts = {}) {
|
|
3143
3264
|
if (ast.node === "miss") return { compositeMiss: true, reason: ast.reason || null, matches: [] };
|
|
3265
|
+
if (ast.node === "exists") return evalExists(graph, ast);
|
|
3144
3266
|
if (ast.node === "count") return { compositeKind: "count", count: evalSet(graph, ast.base, opts).length, entityType: ast.entityType, matches: [] };
|
|
3145
3267
|
if (ast.node === "list") return { compositeKind: "list", matches: evalSet(graph, ast.base, opts), entityType: ast.entityType, scoped: ast.scoped };
|
|
3146
3268
|
if (ast.node === "superlative") return evalSuperlative(graph, ast);
|
|
@@ -3175,6 +3297,27 @@
|
|
|
3175
3297
|
}
|
|
3176
3298
|
return { content: `couldn't compile this compositional question${result.reason ? ` (${result.reason})` : ""}. ${compositionalHint()}.`, miss: true, ambiguous: false };
|
|
3177
3299
|
}
|
|
3300
|
+
if (result.compositeKind === "exists") {
|
|
3301
|
+
if (result.scopeMiss) {
|
|
3302
|
+
return { content: `no module matching "${result.scopeModule}" found in the index.`, miss: true, ambiguous: false };
|
|
3303
|
+
}
|
|
3304
|
+
const kindSingular = nounFor(result.entityType, 1);
|
|
3305
|
+
const kindPlural = nounFor(result.entityType, 2);
|
|
3306
|
+
const scopeSuffix = result.scopeMatch ? ` in ${result.scopeMatch.label}` : "";
|
|
3307
|
+
if (result.term) {
|
|
3308
|
+
if (!result.matches.length) {
|
|
3309
|
+
return { content: `No \u2014 no ${kindSingular} named "${result.term}" found${scopeSuffix}.`, miss: true, ambiguous: false };
|
|
3310
|
+
}
|
|
3311
|
+
const hit2 = result.matches[0];
|
|
3312
|
+
const modLabel = moduleLabelOf(hit2);
|
|
3313
|
+
const definedIn = hit2.class === "Module" ? "" : modLabel && modLabel !== "(unknown module)" ? `, defined in ${modLabel}` : "";
|
|
3314
|
+
return { content: `Yes \u2014 ${hit2.label} is a ${kindSingular}${definedIn}.`, miss: false, ambiguous: false, matches: result.matches };
|
|
3315
|
+
}
|
|
3316
|
+
if (!result.matches.length) {
|
|
3317
|
+
return { content: `No \u2014 no ${kindPlural} found${scopeSuffix}.`, miss: true, ambiguous: false };
|
|
3318
|
+
}
|
|
3319
|
+
return { content: `Yes \u2014 ${compositeList(result.matches)}${scopeSuffix}.`, miss: false, ambiguous: false, matches: result.matches };
|
|
3320
|
+
}
|
|
3178
3321
|
if (result.compositeKind === "count") {
|
|
3179
3322
|
const noun = result.entityType ? nounFor(result.entityType, result.count) : result.count === 1 ? "result" : "results";
|
|
3180
3323
|
return { content: `${result.count} ${noun}.`, miss: false, ambiguous: false, matches: [] };
|
|
@@ -3771,7 +3914,8 @@
|
|
|
3771
3914
|
}
|
|
3772
3915
|
if (!result.objMatch && (!result.candidates || result.candidates.length === 0) && parsed.shape !== "ask") {
|
|
3773
3916
|
const objText = String(parsed.object || "").trim();
|
|
3774
|
-
const
|
|
3917
|
+
const fallback = parsed.entityType && PLURAL_FORMS[parsed.entityType] ? nounFor(parsed.entityType, 1) : "module";
|
|
3918
|
+
const what = /^(?:commit[:\s])?[0-9a-f]{7,40}$/i.test(objText) ? "commit" : !objText.includes("/") && /^[\w$]+(\.[\w$]+)+$/.test(objText) ? "symbol" : fallback;
|
|
3775
3919
|
return {
|
|
3776
3920
|
content: `no ${what} matching "${parsed.object}" found in the index.`,
|
|
3777
3921
|
miss: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
1.0.4
|