@polycode-projects/seonix 0.10.9 → 0.10.11
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 +4 -3
- package/src/ask-browser.bundle.js +437 -18
- package/src/ask-browser.bundle.version +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/seonix",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.11",
|
|
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.",
|
|
@@ -65,11 +65,12 @@
|
|
|
65
65
|
"verify:query": "node scripts/verify-query.mjs",
|
|
66
66
|
"verify:review": "node scripts/verify-review.mjs",
|
|
67
67
|
"verify:loop": "node scripts/verify-loop.mjs",
|
|
68
|
+
"verify:shipped": "node scripts/verify-shipped.mjs",
|
|
68
69
|
"refs": "node scripts/fetch-references.mjs",
|
|
69
70
|
"refs:embeddings": "node scripts/fetch-embeddings.mjs",
|
|
70
71
|
"audit": "npm audit --audit-level=high",
|
|
71
72
|
"audit:fix": "npm audit fix",
|
|
72
|
-
"test:root": "node --test \"test/**/*.test.mjs\"",
|
|
73
|
+
"test:root": "GIT_CONFIG_GLOBAL=\"$PWD/test/fixtures/global-gitconfig\" node --test \"test/**/*.test.mjs\"",
|
|
73
74
|
"test": "npm run test:root && npm run chrono:test",
|
|
74
75
|
"chrono:build": "node chronograph/build.mjs",
|
|
75
76
|
"chrono:serve": "node chronograph/serve.mjs",
|
|
@@ -87,7 +88,7 @@
|
|
|
87
88
|
},
|
|
88
89
|
"dependencies": {
|
|
89
90
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
90
|
-
"@polycode-projects/the-mechanical-code-talker": "^0.9.
|
|
91
|
+
"@polycode-projects/the-mechanical-code-talker": "^0.9.11",
|
|
91
92
|
"cytoscape": "^3.30.0",
|
|
92
93
|
"smol-toml": "^1.7.0",
|
|
93
94
|
"tree-sitter": "^0.21.1",
|
|
@@ -108,6 +108,9 @@
|
|
|
108
108
|
if (/\b(inherit|subclass|extend|specializ)/.test(pred)) return "inherits";
|
|
109
109
|
return null;
|
|
110
110
|
}
|
|
111
|
+
function normPath(s) {
|
|
112
|
+
return String(s || "").trim().toLowerCase().replace(/^\.\//, "").replace(/^\//, "");
|
|
113
|
+
}
|
|
111
114
|
function siteOf(ind) {
|
|
112
115
|
const a = (ind?.attributes || []).find((x) => x.key === "site");
|
|
113
116
|
if (!a) return null;
|
|
@@ -413,7 +416,15 @@
|
|
|
413
416
|
"tends to change together with",
|
|
414
417
|
"tend to change together with",
|
|
415
418
|
"moves together with",
|
|
416
|
-
"move together with"
|
|
419
|
+
"move together with",
|
|
420
|
+
// Track-1 trio (temporal lever): the bare "changed/change together with" form
|
|
421
|
+
// (no "tends to"/"tend to" prefix) was missing outright — "which modules
|
|
422
|
+
// changed together with X" fell through to the "touch(ed)" verb instead (a
|
|
423
|
+
// Commit->Module kind, structurally unable to match a Module subject), always
|
|
424
|
+
// producing a confidently-empty answer regardless of real cochange data.
|
|
425
|
+
"changed together with",
|
|
426
|
+
"change together with",
|
|
427
|
+
"changes together with"
|
|
417
428
|
]
|
|
418
429
|
},
|
|
419
430
|
reexports: {
|
|
@@ -672,7 +683,9 @@
|
|
|
672
683
|
"quickly",
|
|
673
684
|
"real quick",
|
|
674
685
|
"kinda",
|
|
675
|
-
"sorta"
|
|
686
|
+
"sorta",
|
|
687
|
+
"btw",
|
|
688
|
+
"by the way"
|
|
676
689
|
]);
|
|
677
690
|
var CONTEXT_PRONOUNS = Object.freeze(["this", "it", "that", "here", "this one", "that one"]);
|
|
678
691
|
var NEGATION_FRAMES = Object.freeze([
|
|
@@ -980,6 +993,7 @@
|
|
|
980
993
|
return LISTING_TAIL_KINDS.has((words[words.length - 1] || "").toLowerCase());
|
|
981
994
|
};
|
|
982
995
|
var GREETING_PREAMBLE_RE = /^(?:hi|hiya|hello|hey|yo|howdy)(?:\s+there)?\s*[,—–-]\s*(?:(?:just\s+a\s+)?quick\s+question\s*[,:—–-]?\s*)?(.+)$/i;
|
|
996
|
+
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;
|
|
983
997
|
var MODAL_WRAPPER_RE = /^(?:can|could|would|will)\s+you\s+(?:please\s+)?(.+?)(?:[,\s]+please)?\??$/i;
|
|
984
998
|
var SHOW_GIVE_ME_RE = /^(?:show|give)\s+me\s+(?:the\s+)?(.+?)\??$/i;
|
|
985
999
|
function applyPreambleFrames(text) {
|
|
@@ -988,6 +1002,8 @@
|
|
|
988
1002
|
const before = q;
|
|
989
1003
|
let m = q.match(GREETING_PREAMBLE_RE);
|
|
990
1004
|
if (m) q = m[1].trim();
|
|
1005
|
+
m = q.match(THANKS_PREAMBLE_RE);
|
|
1006
|
+
if (m) q = m[1].trim();
|
|
991
1007
|
m = q.match(MODAL_WRAPPER_RE);
|
|
992
1008
|
if (m) q = m[1].trim();
|
|
993
1009
|
m = q.match(SHOW_GIVE_ME_RE);
|
|
@@ -1001,6 +1017,55 @@
|
|
|
1001
1017
|
}
|
|
1002
1018
|
return q;
|
|
1003
1019
|
}
|
|
1020
|
+
var SUBORDINATION_FRAMES_RE = /^(?:since|although|though|while|because|whereas|given\s+that|now\s+that)\s+.+?,\s*(.+)$/i;
|
|
1021
|
+
function applySubordinationFrames(text) {
|
|
1022
|
+
let q = String(text || "");
|
|
1023
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
1024
|
+
const m = q.match(SUBORDINATION_FRAMES_RE);
|
|
1025
|
+
if (!m) break;
|
|
1026
|
+
q = m[1].trim();
|
|
1027
|
+
}
|
|
1028
|
+
return q;
|
|
1029
|
+
}
|
|
1030
|
+
var CONDITIONAL_VERB_GERUND = Object.freeze({
|
|
1031
|
+
imports: "importing",
|
|
1032
|
+
calls: "calling",
|
|
1033
|
+
touches: "touching",
|
|
1034
|
+
tests: "testing",
|
|
1035
|
+
exports: "exporting",
|
|
1036
|
+
contains: "containing",
|
|
1037
|
+
defines: "defining",
|
|
1038
|
+
uses: "using",
|
|
1039
|
+
"inherits from": "inheriting from"
|
|
1040
|
+
});
|
|
1041
|
+
var CONDITIONAL_KIND_PLURAL = Object.freeze({
|
|
1042
|
+
module: "modules",
|
|
1043
|
+
class: "classes",
|
|
1044
|
+
function: "functions",
|
|
1045
|
+
method: "methods",
|
|
1046
|
+
attribute: "attributes",
|
|
1047
|
+
variable: "variables",
|
|
1048
|
+
commit: "commits",
|
|
1049
|
+
file: "files"
|
|
1050
|
+
});
|
|
1051
|
+
var CONDITIONAL_QUALIFIER_SRC = "public|private|protected|static|abstract|constant|re-?exported|exported|tested|covered|untested|uncovered";
|
|
1052
|
+
var CONDITIONAL_QUALIFIER_RE = new RegExp(
|
|
1053
|
+
"^if\\s+(?:a|an|the)?\\s*(" + Object.keys(CONDITIONAL_KIND_PLURAL).join("|") + ")\\s+(" + Object.keys(CONDITIONAL_VERB_GERUND).join("|") + ")\\s+(.+?),\\s*(?:is|are)\\s+(?:it|that|they|this)\\s+(" + CONDITIONAL_QUALIFIER_SRC + ")\\??$",
|
|
1054
|
+
"i"
|
|
1055
|
+
);
|
|
1056
|
+
var COUNTERFACTUAL_RE = /^if\s+(.+?)\s+(?:were|was)\s+(?:deleted|removed),?\s*what\s+(?:would|might|could)\s+(?:break|fail|be\s+affected)\??$/i;
|
|
1057
|
+
function applyConditionalFrames(text) {
|
|
1058
|
+
const q = String(text || "");
|
|
1059
|
+
const qual = q.match(CONDITIONAL_QUALIFIER_RE);
|
|
1060
|
+
if (qual) {
|
|
1061
|
+
const kind = CONDITIONAL_KIND_PLURAL[qual[1].toLowerCase()];
|
|
1062
|
+
const gerund = CONDITIONAL_VERB_GERUND[qual[2].toLowerCase()];
|
|
1063
|
+
return `${kind} ${gerund} ${qual[3].trim()} and ${qual[4].toLowerCase()}`;
|
|
1064
|
+
}
|
|
1065
|
+
const cf = q.match(COUNTERFACTUAL_RE);
|
|
1066
|
+
if (cf) return `which modules transitively import ${cf[1].trim()}`;
|
|
1067
|
+
return q;
|
|
1068
|
+
}
|
|
1004
1069
|
function normalizeQuery(text) {
|
|
1005
1070
|
let q = String(text || "");
|
|
1006
1071
|
q = q.replace(CONTRACTION_RE, (m) => CONTRACTIONS[m.toLowerCase()]);
|
|
@@ -1008,6 +1073,8 @@
|
|
|
1008
1073
|
q = q.replace(WRONG_WORD_RE, (m) => WRONG_WORDS[m.toLowerCase()]);
|
|
1009
1074
|
q = q.replace(G_DROP, "$1ing");
|
|
1010
1075
|
q = applyPreambleFrames(q);
|
|
1076
|
+
q = applySubordinationFrames(q);
|
|
1077
|
+
q = applyConditionalFrames(q);
|
|
1011
1078
|
if (FILLER_WORDS.length) {
|
|
1012
1079
|
const fillerRe = new RegExp(
|
|
1013
1080
|
"\\b(" + [...FILLER_WORDS].sort((a, b) => b.length - a.length).map(escapeRegex).join("|") + ")\\b",
|
|
@@ -1173,7 +1240,33 @@
|
|
|
1173
1240
|
// temporal filler in when-questions ("when was X last touched") — a symbol
|
|
1174
1241
|
// literally named "last" would be the accepted residual cost, same trade as
|
|
1175
1242
|
// every other stopword.
|
|
1176
|
-
"last"
|
|
1243
|
+
"last",
|
|
1244
|
+
// frequency-adverb filler ("what does X usually change together with", "what does
|
|
1245
|
+
// X typically call") — found live: "usually" glued onto the object term instead of
|
|
1246
|
+
// being stripped, corrupting resolution ("src/core/store.mjs usually" instead of
|
|
1247
|
+
// the module alone). Same trade as every other stopword: a symbol literally named
|
|
1248
|
+
// "usually" would be the accepted residual cost.
|
|
1249
|
+
"usually",
|
|
1250
|
+
"typically",
|
|
1251
|
+
"generally",
|
|
1252
|
+
"normally",
|
|
1253
|
+
"often",
|
|
1254
|
+
"commonly",
|
|
1255
|
+
"mostly",
|
|
1256
|
+
// modal auxiliaries ("what SHOULD i look at first") — found live: with no modal in
|
|
1257
|
+
// this set, "should" reached the cascade's bounded fuzzy-correction step and landed
|
|
1258
|
+
// within edit distance of the unrelated closed-vocab word "hold" ("defines" synonym,
|
|
1259
|
+
// ask-vocab.mjs), corrupting the whole query into "what hold i at". Same trade as
|
|
1260
|
+
// every other stopword: a symbol literally named "should" would be the accepted
|
|
1261
|
+
// residual cost.
|
|
1262
|
+
"should",
|
|
1263
|
+
"would",
|
|
1264
|
+
"could",
|
|
1265
|
+
"can",
|
|
1266
|
+
"will",
|
|
1267
|
+
"shall",
|
|
1268
|
+
"might",
|
|
1269
|
+
"must"
|
|
1177
1270
|
]);
|
|
1178
1271
|
var splitWords = (text) => String(text).replace(/\?+\s*$/, "").replace(/,/g, " ").split(/\s+/).filter(Boolean);
|
|
1179
1272
|
var wordsOf = (arr) => arr.flatMap((p) => String(p).toLowerCase().split(" "));
|
|
@@ -1412,6 +1505,15 @@
|
|
|
1412
1505
|
if (hit2) for (let i = hit2.start; i < hit2.end; i += 1) consumed.add(i);
|
|
1413
1506
|
};
|
|
1414
1507
|
mark(verbHit);
|
|
1508
|
+
for (const [phrase, kind2] of Object.entries(VERB_TO_KIND)) {
|
|
1509
|
+
if (kind2 !== verbHit.kind) continue;
|
|
1510
|
+
const pWords = phrase.split(" ");
|
|
1511
|
+
const start = verbHit.end;
|
|
1512
|
+
if (pWords.every((w, j) => canonWords[start + j] === w)) {
|
|
1513
|
+
mark({ start, end: start + pWords.length });
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1415
1517
|
const entityHit = findPhrase(canonWords, ENTITY_TO_TYPE, consumed);
|
|
1416
1518
|
mark(entityHit);
|
|
1417
1519
|
const modifierHit = findPhrase(canonWords, MODIFIER_TO_KIND, consumed);
|
|
@@ -2116,12 +2218,21 @@
|
|
|
2116
2218
|
}
|
|
2117
2219
|
|
|
2118
2220
|
// node_modules/@polycode-projects/the-mechanical-code-talker/src/ask.mjs
|
|
2221
|
+
var askEdgesOfKindCache = /* @__PURE__ */ new WeakMap();
|
|
2119
2222
|
function edgesOfKind(graph, kind) {
|
|
2223
|
+
let byKind = askEdgesOfKindCache.get(graph);
|
|
2224
|
+
if (!byKind) {
|
|
2225
|
+
byKind = /* @__PURE__ */ new Map();
|
|
2226
|
+
askEdgesOfKindCache.set(graph, byKind);
|
|
2227
|
+
}
|
|
2228
|
+
const cached3 = byKind.get(kind);
|
|
2229
|
+
if (cached3) return cached3;
|
|
2120
2230
|
const out = [];
|
|
2121
2231
|
for (const g of graph.relations) {
|
|
2122
2232
|
if (relationKind(g) !== kind) continue;
|
|
2123
2233
|
for (const e of g.edges) out.push(e);
|
|
2124
2234
|
}
|
|
2235
|
+
byKind.set(kind, out);
|
|
2125
2236
|
return out;
|
|
2126
2237
|
}
|
|
2127
2238
|
var SYMBOL_GRAIN_SIBLING = { calls: "callsSymbol", touches: "touchesSymbol" };
|
|
@@ -2146,7 +2257,7 @@
|
|
|
2146
2257
|
const [s, p] = PLURAL_FORMS[entityType] || ["result", "results"];
|
|
2147
2258
|
return n === 1 ? s : p;
|
|
2148
2259
|
}
|
|
2149
|
-
var REVERSE_MISS_VERB = { cochange: "cochanges" };
|
|
2260
|
+
var REVERSE_MISS_VERB = { cochange: "cochanges", reexports: "export" };
|
|
2150
2261
|
function verbFor(kind) {
|
|
2151
2262
|
return REVERSE_MISS_VERB[kind] || kind;
|
|
2152
2263
|
}
|
|
@@ -2180,7 +2291,7 @@
|
|
|
2180
2291
|
function parseComposite(text, nlp) {
|
|
2181
2292
|
const w = splitWords(text);
|
|
2182
2293
|
const lc = w.map((x) => x.toLowerCase());
|
|
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);
|
|
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);
|
|
2184
2295
|
}
|
|
2185
2296
|
function complementAst(entityType, diffAtom) {
|
|
2186
2297
|
return {
|
|
@@ -2509,7 +2620,84 @@
|
|
|
2509
2620
|
}
|
|
2510
2621
|
return { node: "superlative", entityType, metric, metricNoun, extreme: ext };
|
|
2511
2622
|
}
|
|
2623
|
+
var FIND_LINKERS = /* @__PURE__ */ new Set(["called", "named", "about", "like", "containing", "matching", "with"]);
|
|
2624
|
+
function parseFind(w, lc, nlp, depth) {
|
|
2625
|
+
if (lc[0] !== "find") return null;
|
|
2626
|
+
let i = 1;
|
|
2627
|
+
while (i < lc.length && LIST_SKIP.has(lc[i])) i += 1;
|
|
2628
|
+
if (i >= lc.length) return null;
|
|
2629
|
+
const leadNoun = entityNoun(lc[i]);
|
|
2630
|
+
if (leadNoun && !leadNoun.placeholder && leadNoun.entityType !== "Change" && i + 1 < lc.length && FIND_LINKERS.has(lc[i + 1])) {
|
|
2631
|
+
const term = w.slice(i + 2).join(" ").trim();
|
|
2632
|
+
if (term) return { node: "find", entityType: leadNoun.entityType, term };
|
|
2633
|
+
}
|
|
2634
|
+
const lastNoun = entityNoun(lc[lc.length - 1]);
|
|
2635
|
+
if (lastNoun && !lastNoun.placeholder && lastNoun.entityType !== "Change") {
|
|
2636
|
+
const term = w.slice(i, lc.length - 1).join(" ").trim();
|
|
2637
|
+
if (term) return { node: "find", entityType: lastNoun.entityType, term };
|
|
2638
|
+
}
|
|
2639
|
+
if (lc.some((t) => RELATIVE_PRONOUNS.includes(t))) return null;
|
|
2640
|
+
if (i === lc.length - 1 && /^[a-z]+$/.test(lc[i]) && !VERB_TO_KIND[lc[i]] && !PLACEHOLDER_NOUNS.includes(lc[i])) {
|
|
2641
|
+
return { node: "miss", reason: `"${lc[i]}" isn't a listable kind \u2014 try ${LISTABLE_KINDS}` };
|
|
2642
|
+
}
|
|
2643
|
+
return null;
|
|
2644
|
+
}
|
|
2645
|
+
function parseFindPredicateHead(w, lc) {
|
|
2646
|
+
if (lc[0] !== "find") return null;
|
|
2647
|
+
let i = 1;
|
|
2648
|
+
while (i < lc.length && LIST_SKIP.has(lc[i])) i += 1;
|
|
2649
|
+
let r = -1;
|
|
2650
|
+
for (let k = i + 1; k < lc.length; k += 1) {
|
|
2651
|
+
if (RELATIVE_PRONOUNS.includes(lc[k])) {
|
|
2652
|
+
r = k;
|
|
2653
|
+
break;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
if (r < 0) return null;
|
|
2657
|
+
const noun = entityNoun(lc[r - 1]);
|
|
2658
|
+
if (!noun || noun.placeholder || noun.entityType === "Change") return null;
|
|
2659
|
+
const term = w.slice(i, r - 1).join(" ").trim();
|
|
2660
|
+
if (!term) return null;
|
|
2661
|
+
return { entityType: noun.entityType, term, relIdx: r };
|
|
2662
|
+
}
|
|
2663
|
+
function buildPredicateAtoms(entityType, subjPrefix, predLc, predWords, nlp, depth) {
|
|
2664
|
+
const { branches, ops } = splitBoolean(predLc, predWords);
|
|
2665
|
+
let prevVerb = null;
|
|
2666
|
+
const atoms = [];
|
|
2667
|
+
for (let b = 0; b < branches.length; b += 1) {
|
|
2668
|
+
const bw = branches[b];
|
|
2669
|
+
const blc = bw.map((x) => x.toLowerCase());
|
|
2670
|
+
const op = b === 0 ? "intersection" : ops[b - 1];
|
|
2671
|
+
if (bw.length && blc.every((x) => QUALIFIERS[x])) {
|
|
2672
|
+
atoms.push({ op, kind: "qual", filters: blc });
|
|
2673
|
+
continue;
|
|
2674
|
+
}
|
|
2675
|
+
if (blc[0] === "of" || blc[0] === "in") {
|
|
2676
|
+
atoms.push({ op, kind: "set", ast: { node: "membership", entityType, term: bw.slice(1).join(" ") } });
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
let phrase = bw;
|
|
2680
|
+
const vh = findPhrase(blc, VERB_TO_KIND);
|
|
2681
|
+
if (vh) prevVerb = bw.slice(vh.start, vh.end);
|
|
2682
|
+
else if (prevVerb) phrase = [...prevVerb, ...bw];
|
|
2683
|
+
const ast = parseBranchAst(`${subjPrefix} ${phrase.join(" ")}`, nlp, depth);
|
|
2684
|
+
if (!ast || ast.node === "miss") return { miss: ast && ast.reason || "a clause in the combination didn't parse" };
|
|
2685
|
+
atoms.push({ op, kind: "set", ast });
|
|
2686
|
+
}
|
|
2687
|
+
return { atoms };
|
|
2688
|
+
}
|
|
2512
2689
|
function parseRelationalOrQualified(w, lc, nlp, depth) {
|
|
2690
|
+
const findHead = parseFindPredicateHead(w, lc);
|
|
2691
|
+
if (findHead) {
|
|
2692
|
+
const { entityType: entityType2, term, relIdx } = findHead;
|
|
2693
|
+
const predLc2 = lc.slice(relIdx + 1);
|
|
2694
|
+
const predWords2 = w.slice(relIdx + 1);
|
|
2695
|
+
if (!predLc2.length) return { node: "miss", reason: `a relative clause needs a predicate after "${lc[relIdx]}"` };
|
|
2696
|
+
const built = buildPredicateAtoms(entityType2, `which ${lc[relIdx - 1]}`, predLc2, predWords2, nlp, depth + 1);
|
|
2697
|
+
if (built.miss) return { node: "miss", reason: built.miss };
|
|
2698
|
+
const atoms2 = [{ op: "seed", kind: "set", ast: { node: "find", entityType: entityType2, term } }, ...built.atoms];
|
|
2699
|
+
return atoms2.length === 1 ? atoms2[0].ast : { node: "boolean", entityType: entityType2, atoms: atoms2 };
|
|
2700
|
+
}
|
|
2513
2701
|
let i = 0;
|
|
2514
2702
|
while (i < lc.length && FRAME_WORDS.has(lc[i])) i += 1;
|
|
2515
2703
|
const framed = i > 0;
|
|
@@ -2520,8 +2708,9 @@
|
|
|
2520
2708
|
}
|
|
2521
2709
|
const noun = i < lc.length ? entityNoun(lc[i]) : null;
|
|
2522
2710
|
if (!noun) {
|
|
2523
|
-
|
|
2524
|
-
|
|
2711
|
+
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])) {
|
|
2713
|
+
return { node: "find", entityType: nextNoun.entityType, term: w[i] };
|
|
2525
2714
|
}
|
|
2526
2715
|
return null;
|
|
2527
2716
|
}
|
|
@@ -2646,6 +2835,12 @@
|
|
|
2646
2835
|
}
|
|
2647
2836
|
return out;
|
|
2648
2837
|
}
|
|
2838
|
+
function directoryScopeModules(graph, term) {
|
|
2839
|
+
const norm = normPath(term);
|
|
2840
|
+
if (!norm) return [];
|
|
2841
|
+
const prefix = `${norm}/`;
|
|
2842
|
+
return graph.individuals.filter((i) => i.class === "Module" && normPath(i.label).startsWith(prefix));
|
|
2843
|
+
}
|
|
2649
2844
|
var qualCache = /* @__PURE__ */ new WeakMap();
|
|
2650
2845
|
function qualSets(graph) {
|
|
2651
2846
|
let c = qualCache.get(graph);
|
|
@@ -2689,12 +2884,127 @@
|
|
|
2689
2884
|
return false;
|
|
2690
2885
|
}
|
|
2691
2886
|
}
|
|
2887
|
+
function inheritsEdges(graph) {
|
|
2888
|
+
return edgesOfKind(graph, "inherits");
|
|
2889
|
+
}
|
|
2890
|
+
function directChildrenOf(graph, id) {
|
|
2891
|
+
return inheritsEdges(graph).filter((e) => e.object === id).map((e) => e.subject);
|
|
2892
|
+
}
|
|
2893
|
+
function directParentsOf(graph, id) {
|
|
2894
|
+
return inheritsEdges(graph).filter((e) => e.subject === id).map((e) => e.object);
|
|
2895
|
+
}
|
|
2896
|
+
function descendantsOf(graph, id) {
|
|
2897
|
+
const out = /* @__PURE__ */ new Set();
|
|
2898
|
+
const queue = [...directChildrenOf(graph, id)];
|
|
2899
|
+
while (queue.length) {
|
|
2900
|
+
const next = queue.shift();
|
|
2901
|
+
if (out.has(next)) continue;
|
|
2902
|
+
out.add(next);
|
|
2903
|
+
for (const c of directChildrenOf(graph, next)) if (!out.has(c)) queue.push(c);
|
|
2904
|
+
}
|
|
2905
|
+
return out;
|
|
2906
|
+
}
|
|
2907
|
+
function ancestorsOf(graph, id) {
|
|
2908
|
+
const out = /* @__PURE__ */ new Set();
|
|
2909
|
+
const queue = [...directParentsOf(graph, id)];
|
|
2910
|
+
while (queue.length) {
|
|
2911
|
+
const next = queue.shift();
|
|
2912
|
+
if (out.has(next)) continue;
|
|
2913
|
+
out.add(next);
|
|
2914
|
+
for (const p of directParentsOf(graph, next)) if (!out.has(p)) queue.push(p);
|
|
2915
|
+
}
|
|
2916
|
+
return out;
|
|
2917
|
+
}
|
|
2918
|
+
var inheritsApplicableCache = /* @__PURE__ */ new WeakMap();
|
|
2919
|
+
function inheritsApplicable(graph, entityType) {
|
|
2920
|
+
let byType = inheritsApplicableCache.get(graph);
|
|
2921
|
+
if (!byType) {
|
|
2922
|
+
byType = /* @__PURE__ */ new Map();
|
|
2923
|
+
inheritsApplicableCache.set(graph, byType);
|
|
2924
|
+
}
|
|
2925
|
+
if (byType.has(entityType)) return byType.get(entityType);
|
|
2926
|
+
const ok = inheritsEdges(graph).some((e) => {
|
|
2927
|
+
const s = graph.byId.get(e.subject);
|
|
2928
|
+
const o = graph.byId.get(e.object);
|
|
2929
|
+
return !!s && !!o && s.class === entityType && o.class === entityType;
|
|
2930
|
+
});
|
|
2931
|
+
byType.set(entityType, ok);
|
|
2932
|
+
return ok;
|
|
2933
|
+
}
|
|
2934
|
+
function ownSurfaceHit(ind, termTokens) {
|
|
2935
|
+
const labelLc = String(ind.label || "").toLowerCase();
|
|
2936
|
+
if (termTokens.every((tok) => labelLc.includes(tok))) return "label";
|
|
2937
|
+
const attrs = (ind.attributes || []).map((a) => String(a.value ?? "").toLowerCase());
|
|
2938
|
+
if (termTokens.every((tok) => attrs.some((v) => v.includes(tok)))) return "attr";
|
|
2939
|
+
return null;
|
|
2940
|
+
}
|
|
2941
|
+
var FIND_TIER = { label: 3, chain: 2, attr: 1 };
|
|
2942
|
+
function sortFindHits(hits) {
|
|
2943
|
+
return hits.slice().sort((a, b) => FIND_TIER[b.via] - FIND_TIER[a.via] || String(a.ind.label).length - String(b.ind.label).length).map((h) => h.ind);
|
|
2944
|
+
}
|
|
2945
|
+
function fuzzyFindHit(ind, term) {
|
|
2946
|
+
const tLc = String(term || "").trim().toLowerCase();
|
|
2947
|
+
if (tLc.length < 4) return false;
|
|
2948
|
+
const bound = fuzzyBound(tLc);
|
|
2949
|
+
if (editDistance(String(ind.label || "").toLowerCase(), tLc, bound) <= bound) return true;
|
|
2950
|
+
for (const comp of componentSet(ind.label)) {
|
|
2951
|
+
if (editDistance(comp, tLc, bound) <= bound) return true;
|
|
2952
|
+
}
|
|
2953
|
+
return false;
|
|
2954
|
+
}
|
|
2955
|
+
function computeFind(graph, entityType, term) {
|
|
2956
|
+
const pool = graph.individuals.filter((i) => i.class === entityType);
|
|
2957
|
+
const termTokens = [...componentSet(term)];
|
|
2958
|
+
if (!termTokens.length || !pool.length) return { narrow: [], broad: [] };
|
|
2959
|
+
const cascade = inheritsApplicable(graph, entityType);
|
|
2960
|
+
const narrowHits = [];
|
|
2961
|
+
for (const ind of pool) {
|
|
2962
|
+
const own = ownSurfaceHit(ind, termTokens);
|
|
2963
|
+
if (own) {
|
|
2964
|
+
narrowHits.push({ ind, via: own });
|
|
2965
|
+
continue;
|
|
2966
|
+
}
|
|
2967
|
+
if (!cascade) continue;
|
|
2968
|
+
const viaChain = [...descendantsOf(graph, ind.id)].some((did) => {
|
|
2969
|
+
const d = graph.byId.get(did);
|
|
2970
|
+
return !!d && !!ownSurfaceHit(d, termTokens);
|
|
2971
|
+
});
|
|
2972
|
+
if (viaChain) narrowHits.push({ ind, via: "chain" });
|
|
2973
|
+
}
|
|
2974
|
+
if (narrowHits.length || !cascade) return { narrow: sortFindHits(narrowHits), broad: [] };
|
|
2975
|
+
const broadHits = /* @__PURE__ */ new Map();
|
|
2976
|
+
for (const ind of pool) {
|
|
2977
|
+
for (const ancId of ancestorsOf(graph, ind.id)) {
|
|
2978
|
+
if (!broadHits.has(ancId)) {
|
|
2979
|
+
const anc = graph.byId.get(ancId);
|
|
2980
|
+
if (anc && anc.class === entityType && fuzzyFindHit(anc, term)) {
|
|
2981
|
+
broadHits.set(ancId, { ind: anc, via: "chain" });
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
for (const sibId of directChildrenOf(graph, ancId)) {
|
|
2985
|
+
if (sibId === ind.id || broadHits.has(sibId)) continue;
|
|
2986
|
+
const sib = graph.byId.get(sibId);
|
|
2987
|
+
if (sib && sib.class === entityType && fuzzyFindHit(sib, term)) broadHits.set(sibId, { ind: sib, via: "chain" });
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
return { narrow: [], broad: sortFindHits([...broadHits.values()]) };
|
|
2992
|
+
}
|
|
2692
2993
|
function evalSet(graph, ast, opts) {
|
|
2693
2994
|
switch (ast.node) {
|
|
2694
2995
|
case "clause":
|
|
2695
2996
|
return traverse(graph, ast.clause, opts).matches || [];
|
|
2696
2997
|
case "allOfClass":
|
|
2697
2998
|
return graph.individuals.filter((i) => i.class === ast.entityType);
|
|
2999
|
+
// predicate-find (Workstream 2), embedded as a set atom (§6 generalization —
|
|
3000
|
+
// a find-seed inside a boolean/qualifier fold): the narrow-then-broaden
|
|
3001
|
+
// cascade's result, transparently flattened (the "related, not exact" framing
|
|
3002
|
+
// is a top-level RENDER concern — evalComposite's dedicated "find" handling
|
|
3003
|
+
// below, not this generic embedding).
|
|
3004
|
+
case "find": {
|
|
3005
|
+
const { narrow, broad } = computeFind(graph, ast.entityType, ast.term);
|
|
3006
|
+
return narrow.length ? narrow : broad;
|
|
3007
|
+
}
|
|
2698
3008
|
// the SUBJECTS that have ANY edge of a kind (the existential "modules that import
|
|
2699
3009
|
// anything") — the positive set an existential negation ("do not import anything")
|
|
2700
3010
|
// differences off allOfClass to yield "modules that import nothing".
|
|
@@ -2722,6 +3032,15 @@
|
|
|
2722
3032
|
}
|
|
2723
3033
|
case "membership": {
|
|
2724
3034
|
const r = resolveObject(graph, ast.term);
|
|
3035
|
+
if (!(r.match && r.tier === 1)) {
|
|
3036
|
+
const dirMods = directoryScopeModules(graph, ast.term);
|
|
3037
|
+
if (dirMods.length) {
|
|
3038
|
+
if (!ast.entityType || ast.entityType === "Module") return dirMods;
|
|
3039
|
+
const ids2 = new Set(dirMods.map((m) => m.id));
|
|
3040
|
+
const objs2 = uniqueById(MEMBERSHIP_KINDS.flatMap((k) => forwardOverSet(graph, k, ids2)));
|
|
3041
|
+
return objs2.filter((o) => o.class === ast.entityType);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
2725
3044
|
if (!r.match) return [];
|
|
2726
3045
|
const ids = /* @__PURE__ */ new Set([r.match.id]);
|
|
2727
3046
|
const objs = uniqueById(MEMBERSHIP_KINDS.flatMap((k) => forwardOverSet(graph, k, ids)));
|
|
@@ -2827,11 +3146,27 @@
|
|
|
2827
3146
|
if (ast.node === "superlative") return evalSuperlative(graph, ast);
|
|
2828
3147
|
if (ast.node === "temporal") return evalTemporal(graph, ast, opts);
|
|
2829
3148
|
if (ast.node === "anaphora") return evalAnaphora(graph, ast, opts);
|
|
3149
|
+
if (ast.node === "find") {
|
|
3150
|
+
const { narrow, broad } = computeFind(graph, ast.entityType, ast.term);
|
|
3151
|
+
return {
|
|
3152
|
+
compositeKind: "find",
|
|
3153
|
+
entityType: ast.entityType,
|
|
3154
|
+
term: ast.term,
|
|
3155
|
+
matches: narrow.length ? narrow : broad,
|
|
3156
|
+
broad: !narrow.length && broad.length > 0
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
2830
3159
|
return { compositeKind: "set", matches: evalSet(graph, ast, opts), entityType: ast.entityType || null };
|
|
2831
3160
|
}
|
|
2832
3161
|
var compositeList = (matches) => listJoin(matches.slice(0, OVERFLOW_CAP).map((m) => ["Function", "Method"].includes(m.class) ? `${m.label}()` : m.label)) + (matches.length > OVERFLOW_CAP ? `, \u2026and ${matches.length - OVERFLOW_CAP} more` : "");
|
|
2833
3162
|
function compositionalHint() {
|
|
2834
|
-
return 'compositional queries also work: "which functions call X and call Y", "what calls something that imports X", "public methods of X", "list functions" / "show me the classes", "how many classes", "which module has the most imports", or (after a listing) "which of those are tested"';
|
|
3163
|
+
return 'compositional queries also work: "which functions call X and call Y", "what calls something that imports X", "public methods of X", "list functions" / "show me the classes", "how many classes", "which module has the most imports", "find me the payment class", or (after a listing) "which of those are tested"';
|
|
3164
|
+
}
|
|
3165
|
+
function describeFindHit(ind) {
|
|
3166
|
+
const label = ["Function", "Method"].includes(ind.class) ? `${ind.label}()` : ind.label;
|
|
3167
|
+
if (ind.class === "Module") return label;
|
|
3168
|
+
const mod = moduleLabelOf(ind);
|
|
3169
|
+
return mod && mod !== "(unknown module)" ? `${label} in ${mod}` : label;
|
|
2835
3170
|
}
|
|
2836
3171
|
function renderComposite(parsed, result) {
|
|
2837
3172
|
if (result.compositeMiss) {
|
|
@@ -2852,6 +3187,23 @@
|
|
|
2852
3187
|
const hint = !result.scoped && scopeable && result.matches.length > OVERFLOW_CAP ? ` \u2014 narrow with "${nounFor(result.entityType, 2)} in <module>"` : "";
|
|
2853
3188
|
return { content: `${compositeList(result.matches)}${hint}.`, miss: false, ambiguous: false, matches: result.matches };
|
|
2854
3189
|
}
|
|
3190
|
+
if (result.compositeKind === "find") {
|
|
3191
|
+
const typeNoun = nounFor(result.entityType, 1);
|
|
3192
|
+
if (!result.matches.length) {
|
|
3193
|
+
return { content: `no ${nounFor(result.entityType, 2)} found matching "${result.term}".`, miss: true, ambiguous: false, matches: [] };
|
|
3194
|
+
}
|
|
3195
|
+
const cited = result.matches.length === 1 ? describeFindHit(result.matches[0]) : compositeList(result.matches);
|
|
3196
|
+
if (result.broad) {
|
|
3197
|
+
return {
|
|
3198
|
+
content: `no exact ${typeNoun} named "${result.term}", but found a related ${result.matches.length === 1 ? typeNoun : nounFor(result.entityType, 2)}: ${cited}.`,
|
|
3199
|
+
miss: false,
|
|
3200
|
+
ambiguous: false,
|
|
3201
|
+
matches: result.matches,
|
|
3202
|
+
relatedNotExact: true
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
return { content: `${cited}.`, miss: false, ambiguous: false, matches: result.matches };
|
|
3206
|
+
}
|
|
2855
3207
|
if (result.compositeKind === "superlative") {
|
|
2856
3208
|
if (!result.matches.length) return { content: `no ${nounFor(result.entityType, 2)} to rank in this index.`, miss: true, ambiguous: false };
|
|
2857
3209
|
const lead = result.extreme === "most" ? "the most" : "the fewest";
|
|
@@ -2899,11 +3251,11 @@
|
|
|
2899
3251
|
function componentSet(s) {
|
|
2900
3252
|
return new Set(String(s).toLowerCase().split(/[^a-z0-9]+/).filter(Boolean));
|
|
2901
3253
|
}
|
|
2902
|
-
function resolveObject(graph, term) {
|
|
3254
|
+
function resolveObject(graph, term, { expectedClass = null } = {}) {
|
|
2903
3255
|
const t = String(term || "").trim();
|
|
2904
3256
|
if (!t) return { match: null, candidates: [], tier: null, ambiguous: false };
|
|
2905
3257
|
const tLc = t.toLowerCase();
|
|
2906
|
-
const pool = graph.individuals;
|
|
3258
|
+
const pool = expectedClass ? graph.individuals.filter((i) => i.class === expectedClass) : graph.individuals;
|
|
2907
3259
|
const shaTerm = tLc.match(/^(commit[:\s])?([0-9a-f]{7,40})$/);
|
|
2908
3260
|
if (shaTerm) {
|
|
2909
3261
|
const sha = shaTerm[2];
|
|
@@ -2924,7 +3276,7 @@
|
|
|
2924
3276
|
}
|
|
2925
3277
|
}
|
|
2926
3278
|
}
|
|
2927
|
-
if (extId) return { match: { id: extId, label: t, class: null }, candidates: [], tier: 2, ambiguous: false };
|
|
3279
|
+
if (extId && !expectedClass) return { match: { id: extId, label: t, class: null }, candidates: [], tier: 2, ambiguous: false };
|
|
2928
3280
|
const scored = [];
|
|
2929
3281
|
const dotted = !tLc.includes("/") && /^[\w$]+(\.[\w$]+)+$/.test(tLc);
|
|
2930
3282
|
if (dotted) {
|
|
@@ -2963,7 +3315,7 @@
|
|
|
2963
3315
|
};
|
|
2964
3316
|
}
|
|
2965
3317
|
let proseResult = null;
|
|
2966
|
-
const proseHits = !dotted && typeof lookupByProseTokens === "function" ? lookupByProseTokens(graph.proseIndex, t) : [];
|
|
3318
|
+
const proseHits = !dotted && typeof lookupByProseTokens === "function" ? lookupByProseTokens(graph.proseIndex, t).filter((h) => !expectedClass || graph.byId.get(h.id)?.class === expectedClass) : [];
|
|
2967
3319
|
if (proseHits.length) {
|
|
2968
3320
|
const [best, ...rest] = proseHits;
|
|
2969
3321
|
const bestInd = graph.byId.get(best.id);
|
|
@@ -3221,10 +3573,59 @@
|
|
|
3221
3573
|
}
|
|
3222
3574
|
return { matches: matches2, objMatch, candidates, traversal: `${symbolKind} edges where object = ${objMatch.label}${widenNote}`, ambiguous, matchedVia };
|
|
3223
3575
|
}
|
|
3224
|
-
let
|
|
3576
|
+
let gObjMatch = objMatch;
|
|
3577
|
+
let gCandidates = candidates;
|
|
3578
|
+
let gAmbiguous = ambiguous;
|
|
3579
|
+
let gMatchedVia = matchedVia;
|
|
3580
|
+
let grainRefinedNote = "";
|
|
3581
|
+
const wantClass = kindObjectClass(graph, kind);
|
|
3582
|
+
if (wantClass && gObjMatch.class && gObjMatch.class !== wantClass) {
|
|
3583
|
+
const retry = resolveObject(graph, parsed.object, { expectedClass: wantClass });
|
|
3584
|
+
if (retry.match && !retry.ambiguous) {
|
|
3585
|
+
gObjMatch = retry.match;
|
|
3586
|
+
gCandidates = retry.candidates;
|
|
3587
|
+
gAmbiguous = retry.ambiguous;
|
|
3588
|
+
gMatchedVia = retry.matchedVia;
|
|
3589
|
+
} else if ((kind === "tests" || kind === "cochange") && gObjMatch.class !== "Module") {
|
|
3590
|
+
const mid = moduleIdOf2(graph, gObjMatch);
|
|
3591
|
+
const mod = mid && graph.byId.get(mid);
|
|
3592
|
+
if (mod) {
|
|
3593
|
+
grainRefinedNote = `, refined from ${gObjMatch.label} to its containing module`;
|
|
3594
|
+
gObjMatch = mod;
|
|
3595
|
+
} else {
|
|
3596
|
+
return {
|
|
3597
|
+
matches: [],
|
|
3598
|
+
objMatch: gObjMatch,
|
|
3599
|
+
candidates: gCandidates,
|
|
3600
|
+
ambiguous: gAmbiguous,
|
|
3601
|
+
matchedVia: gMatchedVia,
|
|
3602
|
+
wrongGrainMiss: true,
|
|
3603
|
+
wantClass,
|
|
3604
|
+
traversal: `"${parsed.object}" resolved to ${gObjMatch.class} ${gObjMatch.label} (grain mismatch: this "${kind}" question needs a ${wantClass}, and no containing module could be found to refine to)`
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3607
|
+
} else {
|
|
3608
|
+
return {
|
|
3609
|
+
matches: [],
|
|
3610
|
+
objMatch: gObjMatch,
|
|
3611
|
+
candidates: gCandidates,
|
|
3612
|
+
ambiguous: gAmbiguous,
|
|
3613
|
+
matchedVia: gMatchedVia,
|
|
3614
|
+
wrongGrainMiss: true,
|
|
3615
|
+
wantClass,
|
|
3616
|
+
traversal: `"${parsed.object}" resolved to ${gObjMatch.class} ${gObjMatch.label} (grain mismatch: this "${kind}" question needs a ${wantClass})`
|
|
3617
|
+
};
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
let edges = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.object === gObjMatch.id);
|
|
3621
|
+
if (kind === "cochange") {
|
|
3622
|
+
edges = edges.concat(
|
|
3623
|
+
kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.subject === gObjMatch.id).map((e) => ({ ...e, subject: e.object, object: e.subject }))
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
3225
3626
|
let extNote = "";
|
|
3226
|
-
if (!edges.length &&
|
|
3227
|
-
const extId = `ext:${String(
|
|
3627
|
+
if (!edges.length && gObjMatch.class) {
|
|
3628
|
+
const extId = `ext:${String(gObjMatch.label).toLowerCase()}`;
|
|
3228
3629
|
edges = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => String(e.object).toLowerCase() === extId);
|
|
3229
3630
|
if (edges.length) extNote = ` (by name, via unresolved ${extId} references)`;
|
|
3230
3631
|
}
|
|
@@ -3252,7 +3653,14 @@
|
|
|
3252
3653
|
matches = [];
|
|
3253
3654
|
}
|
|
3254
3655
|
}
|
|
3255
|
-
return {
|
|
3656
|
+
return {
|
|
3657
|
+
matches,
|
|
3658
|
+
objMatch: gObjMatch,
|
|
3659
|
+
candidates: gCandidates,
|
|
3660
|
+
traversal: `${kindsFor(kind).join("+")} edges where object = ${gObjMatch.label}${extNote}${grainNote}${grainRefinedNote}`,
|
|
3661
|
+
ambiguous: gAmbiguous,
|
|
3662
|
+
matchedVia: gMatchedVia
|
|
3663
|
+
};
|
|
3256
3664
|
}
|
|
3257
3665
|
function moduleLabelOf(ind) {
|
|
3258
3666
|
if (ind.class === "Module") return ind.label;
|
|
@@ -3313,6 +3721,15 @@
|
|
|
3313
3721
|
ambiguous: false
|
|
3314
3722
|
};
|
|
3315
3723
|
}
|
|
3724
|
+
if (result.wrongGrainMiss) {
|
|
3725
|
+
const gotNoun = result.objMatch.class ? nounFor(result.objMatch.class, 1) : "term";
|
|
3726
|
+
const wantNoun = nounFor(result.wantClass, 1);
|
|
3727
|
+
return {
|
|
3728
|
+
content: `"${parsed.object}" resolved to the ${gotNoun} ${result.objMatch.label}, but this question needs a ${wantNoun} \u2014 no ${wantNoun} named "${parsed.object}" was found in the index.`,
|
|
3729
|
+
miss: true,
|
|
3730
|
+
ambiguous: false
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3316
3733
|
if (parsed.shape === "meta") {
|
|
3317
3734
|
if (!result.objMatch) {
|
|
3318
3735
|
return {
|
|
@@ -3365,8 +3782,10 @@
|
|
|
3365
3782
|
if (result.ambiguous) {
|
|
3366
3783
|
const pool = [result.objMatch, ...result.candidates || []].filter(Boolean);
|
|
3367
3784
|
const noun = pool.length && pool.every((i) => i.class === "Commit") ? "commit" : "module";
|
|
3785
|
+
const shown = pool.slice(0, OVERFLOW_CAP).map((i) => i.label);
|
|
3786
|
+
const extra2 = pool.length > OVERFLOW_CAP ? `, \u2026and ${pool.length - OVERFLOW_CAP} more` : "";
|
|
3368
3787
|
return {
|
|
3369
|
-
content: `"${parsed.object}" matches more than one ${noun} ambiguously \u2014
|
|
3788
|
+
content: `"${parsed.object}" matches more than one ${noun} ambiguously \u2014 did you mean ${listJoin(shown)}${extra2}? Try one of those.`,
|
|
3370
3789
|
miss: false,
|
|
3371
3790
|
ambiguous: true,
|
|
3372
3791
|
candidates: pool.map((i) => i.label)
|
|
@@ -3451,7 +3870,7 @@
|
|
|
3451
3870
|
if (!result.matches.length) {
|
|
3452
3871
|
if (parsed.shape === "forward") {
|
|
3453
3872
|
return {
|
|
3454
|
-
content: `${result.objMatch.label} has no ${parsed.kind} edges in the index.`,
|
|
3873
|
+
content: `${result.objMatch.label} has no ${verbFor(parsed.kind)} edges in the index.`,
|
|
3455
3874
|
miss: true,
|
|
3456
3875
|
ambiguous: false
|
|
3457
3876
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.9.11
|