@polycode-projects/seonix 0.7.3 → 0.9.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.
@@ -0,0 +1,3040 @@
1
+ (() => {
2
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/prose.mjs
3
+ var STOPWORDS = new Set(
4
+ "a an and or but the of to in on at for with from by as is are was were be been being it its this that these those i you he she they we me my your our do does did not no yes if then else than so such can will would should could may might about into over under out up down off again more most some any all what which who whom whose when where why how".split(/\s+/)
5
+ );
6
+ var MAX_TOKEN_LEN = 40;
7
+ var MAX_TOKENS_PER_DOC = 120;
8
+ function splitIdentifierWords(raw) {
9
+ if (!raw) return [];
10
+ let s = String(raw).replace(/\.[A-Za-z0-9]+$/, "");
11
+ s = s.replace(/[/\\]/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([A-Za-z])([0-9])/g, "$1 $2").replace(/([0-9])([A-Za-z])/g, "$1 $2").replace(/[_\-.]+/g, " ");
12
+ return s.split(/\s+/).map((w) => w.toLowerCase()).filter((w) => w.length > 1 && w.length <= MAX_TOKEN_LEN);
13
+ }
14
+ function tokenizeProse(text) {
15
+ if (!text) return [];
16
+ const out = [];
17
+ const seen = /* @__PURE__ */ new Set();
18
+ for (const raw of String(text).toLowerCase().split(/[^a-z0-9]+/)) {
19
+ if (raw.length < 2 || raw.length > MAX_TOKEN_LEN || STOPWORDS.has(raw)) continue;
20
+ if (seen.has(raw)) continue;
21
+ seen.add(raw);
22
+ out.push(raw);
23
+ if (out.length >= MAX_TOKENS_PER_DOC) break;
24
+ }
25
+ return out;
26
+ }
27
+ function lookupByProseTokens(proseIndex, query, { limit = 10 } = {}) {
28
+ const queryTokens = [.../* @__PURE__ */ new Set([...splitIdentifierWords(query), ...tokenizeProse(query)])];
29
+ if (!queryTokens.length) return [];
30
+ const scoreById = /* @__PURE__ */ new Map();
31
+ for (const word of queryTokens) {
32
+ for (const id of proseIndex?.[word] || []) {
33
+ scoreById.set(id, (scoreById.get(id) || 0) + 1);
34
+ }
35
+ }
36
+ return [...scoreById.entries()].sort((a, b) => b[1] - a[1] || String(a[0]).localeCompare(b[0])).slice(0, limit).map(([id, score]) => ({ id, score }));
37
+ }
38
+
39
+ // node-stub:node:fs
40
+ var unavailable = (name) => () => {
41
+ throw new Error(name + " unavailable in the browser ask bundle");
42
+ };
43
+ var createRequire = unavailable("createRequire");
44
+ var readFileSync = unavailable("readFileSync");
45
+ var randomBytes = unavailable("randomBytes");
46
+
47
+ // node-stub:node:path
48
+ var unavailable2 = (name) => () => {
49
+ throw new Error(name + " unavailable in the browser ask bundle");
50
+ };
51
+ var createRequire2 = unavailable2("createRequire");
52
+ var readFileSync2 = unavailable2("readFileSync");
53
+ var randomBytes2 = unavailable2("randomBytes");
54
+
55
+ // node-stub:node:url
56
+ var unavailable3 = (name) => () => {
57
+ throw new Error(name + " unavailable in the browser ask bundle");
58
+ };
59
+ var createRequire3 = unavailable3("createRequire");
60
+ var readFileSync3 = unavailable3("readFileSync");
61
+ var randomBytes3 = unavailable3("randomBytes");
62
+
63
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/codegraph.mjs
64
+ var PROP_KIND = {
65
+ // v2.0 faithful tokens (SEON-faithful realign)
66
+ "mgx:importsnamespace": "imports",
67
+ "mgx:callscoarse": "calls",
68
+ "seon:declaresmethod": "defines",
69
+ "mgx:testscoverage": "tests",
70
+ "mgx:touchedbycommit": "touches",
71
+ "seon:containscodeentity": "contains",
72
+ "seon:hassupertype": "inherits",
73
+ "mgx:changecoupledwith": "cochange",
74
+ "mgx:reexports": "reexports",
75
+ // fine-grained symbol-level edges (Commit→symbol history, fn→fn in-repo calls).
76
+ // These stay SEPARATE kinds from the module-coarse "touches"/"calls" so the impact
77
+ // closure (module-coarse) is unchanged.
78
+ "mgx:touchessymbol": "touchesSymbol",
79
+ "mgx:callssymbol": "callsSymbol",
80
+ // legacy tokens (pre-realign graphs) — kept so a stale artifact still classifies
81
+ "seon:usescomplextype": "imports",
82
+ "seon:invokesmethod": "calls",
83
+ "seon:history": "touches",
84
+ "mgx:subclassof": "inherits",
85
+ "mg:imports": "imports",
86
+ "mg:calls": "calls",
87
+ "mg:defines": "defines",
88
+ "mg:tests": "tests",
89
+ "mg:touches": "touches"
90
+ };
91
+ function relationKind(group) {
92
+ const prop = String(group?.prop || "").toLowerCase();
93
+ if (PROP_KIND[prop]) return PROP_KIND[prop];
94
+ const pred = String(group?.predicate || "").toLowerCase();
95
+ if (/symbol/.test(pred)) {
96
+ if (/\b(call|invoke)/.test(pred)) return "callsSymbol";
97
+ if (/(touch|chang|modif)/.test(pred)) return "touchesSymbol";
98
+ }
99
+ if (/\bimport/.test(pred)) return "imports";
100
+ if (/\b(call|invoke)/.test(pred)) return "calls";
101
+ if (/\b(define|export|declare)/.test(pred)) return "defines";
102
+ if (/\b(test|cover)/.test(pred)) return "tests";
103
+ if (/\b(touch|chang|modif)/.test(pred)) return "touches";
104
+ if (/\bcontain/.test(pred)) return "contains";
105
+ if (/\b(inherit|subclass|extend|specializ)/.test(pred)) return "inherits";
106
+ return null;
107
+ }
108
+ function siteOf(ind) {
109
+ const a = (ind?.attributes || []).find((x) => x.key === "site");
110
+ if (!a) return null;
111
+ const m = String(a.value).match(/^(.*):(\d+)(?:-(\d+))?$/);
112
+ if (!m) return null;
113
+ return { path: m[1], start: Number(m[2]), end: m[3] ? Number(m[3]) : Number(m[2]) };
114
+ }
115
+ function impactClosure(graph, ind, { maxDepth = 8 } = {}) {
116
+ const dependents = /* @__PURE__ */ new Map();
117
+ const coveredBy = /* @__PURE__ */ new Map();
118
+ const addDependent = (objectId, subjectId, subjectLabel, via) => {
119
+ if (!objectId || !subjectId || objectId === subjectId) return;
120
+ if (!dependents.has(objectId)) dependents.set(objectId, []);
121
+ dependents.get(objectId).push({ id: subjectId, label: subjectLabel, via });
122
+ };
123
+ for (const g of graph.relations) {
124
+ const kind = relationKind(g);
125
+ if (kind === "imports" || kind === "calls") {
126
+ for (const e of g.edges) addDependent(e.object, e.subject, e.subjectLabel || e.subject, g.predicate);
127
+ } else if (kind === "callsSymbol") {
128
+ for (const e of g.edges) {
129
+ const subjModId = moduleIdOfId(graph, e.subject);
130
+ const objModId = moduleIdOfId(graph, e.object);
131
+ if (!subjModId || !objModId) continue;
132
+ const subjLabel = graph.byId.get(subjModId)?.label || subjModId;
133
+ addDependent(objModId, subjModId, subjLabel, g.predicate);
134
+ }
135
+ } else if (kind === "tests") {
136
+ for (const e of g.edges) {
137
+ if (!coveredBy.has(e.object)) coveredBy.set(e.object, []);
138
+ coveredBy.get(e.object).push(e.subjectLabel || e.subject);
139
+ }
140
+ }
141
+ }
142
+ const levels = [];
143
+ const visited = /* @__PURE__ */ new Set([ind.id]);
144
+ let frontier = [ind.id];
145
+ for (let depth = 1; depth <= maxDepth && frontier.length; depth += 1) {
146
+ const next = [];
147
+ const level = [];
148
+ for (const id of frontier) {
149
+ for (const dep of dependents.get(id) || []) {
150
+ if (visited.has(dep.id)) continue;
151
+ visited.add(dep.id);
152
+ level.push({ ...dep, tests: coveredBy.get(dep.id) || [] });
153
+ next.push(dep.id);
154
+ }
155
+ }
156
+ if (level.length) {
157
+ level.sort((a, b) => String(a.label).localeCompare(String(b.label)));
158
+ levels.push(level);
159
+ }
160
+ frontier = next;
161
+ }
162
+ return levels;
163
+ }
164
+ function moduleIdOfId(graph, id) {
165
+ const ind = graph.byId?.get?.(id);
166
+ if (ind) return moduleIdOf(graph, ind);
167
+ const m = String(id || "").match(/^fn:(.+)#/);
168
+ return m ? `mod:${m[1]}` : null;
169
+ }
170
+ function moduleIdOf(graph, ind) {
171
+ if ((ind?.class || "") === "Module") return ind.id;
172
+ const site = siteOf(ind);
173
+ if (site) return `mod:${site.path}`;
174
+ const m = String(ind?.id || "").match(/^fn:(.+)#/);
175
+ return m ? `mod:${m[1]}` : null;
176
+ }
177
+
178
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/ask-vocab.mjs
179
+ var RELATIONS = {
180
+ imports: {
181
+ comment: "Module -> Module: subject's import graph references object (usesComplexType).",
182
+ verbs: [
183
+ // formal/neutral ("uses"/"use" moved to the `uses` union family, 2026-07-02 —
184
+ // "uses code from" stays here: its phrasing is specifically import-flavored)
185
+ "couples to",
186
+ "couple to",
187
+ "depends on",
188
+ "imports",
189
+ "import",
190
+ "relies on",
191
+ "rely on",
192
+ "requires",
193
+ "require",
194
+ "references",
195
+ "reference",
196
+ "pulls in",
197
+ "pull in",
198
+ "built on",
199
+ "builds on",
200
+ "build on",
201
+ "uses code from",
202
+ // casual/colloquial
203
+ "grabs",
204
+ "grab",
205
+ "pulls from",
206
+ "pull from",
207
+ "leans on",
208
+ "lean on",
209
+ "is wired to",
210
+ "are wired to",
211
+ "is hooked up to",
212
+ "are hooked up to",
213
+ // gerund (g-drop normalization turns dialectal "importin'" into this — §3.5)
214
+ "importing"
215
+ ]
216
+ },
217
+ // "uses" is a QUERY-side union family, not a stored predicate (2026-07-02 query
218
+ // families): "what uses X" honestly means BOTH the import graph and the call
219
+ // graph, so ask.mjs traverses it as imports + calls + callsSymbol together
220
+ // (KIND_UNIONS there). The verbs moved here FROM imports — "which modules use X"
221
+ // still answers with the importing modules (the asked Module grain filters the
222
+ // union down to module-grain subjects), and "what uses <function>" now also
223
+ // reaches the symbol-grain callers instead of silently ignoring them.
224
+ uses: {
225
+ comment: "query-side union: imports (Module->Module) + calls (Module->Module) + callsSymbol (fn->fn).",
226
+ verbs: [
227
+ "uses",
228
+ "use",
229
+ "used by",
230
+ "makes use of",
231
+ "make use of",
232
+ // gerund (g-drop normalization — §3.5)
233
+ "using"
234
+ ]
235
+ },
236
+ calls: {
237
+ comment: "Function/Method -> Function/Class (symbol-grain) or Module -> Module (coarse): subject invokes object.",
238
+ verbs: [
239
+ // formal
240
+ "invokes",
241
+ "invoke",
242
+ // neutral
243
+ "calls",
244
+ "call",
245
+ "runs",
246
+ "run",
247
+ "executes",
248
+ "execute",
249
+ // casual/colloquial
250
+ "hits",
251
+ "hit",
252
+ "triggers",
253
+ "trigger",
254
+ "fires",
255
+ "fire",
256
+ "kicks off",
257
+ "kick off",
258
+ // gerund (g-drop normalization turns dialectal "callin'" into this — §3.5)
259
+ "calling"
260
+ ]
261
+ },
262
+ defines: {
263
+ comment: "Module -> top-level Function/Class/Method/Attribute: subject declares object.",
264
+ verbs: [
265
+ "defines",
266
+ "define",
267
+ "declares",
268
+ "declare",
269
+ "has",
270
+ "have",
271
+ "holds",
272
+ "hold",
273
+ // gerund (g-drop normalization — §3.5)
274
+ "defining"
275
+ ]
276
+ },
277
+ contains: {
278
+ comment: "Class -> Method/Attribute: subject's membership includes object.",
279
+ verbs: [
280
+ "contains",
281
+ "contain",
282
+ "lives in",
283
+ "live in",
284
+ "is defined in",
285
+ "are defined in",
286
+ "is part of",
287
+ "are part of",
288
+ "sits in",
289
+ "sit in",
290
+ "sits inside",
291
+ "sit inside",
292
+ // gerund (g-drop normalization — §3.5)
293
+ "containing"
294
+ ]
295
+ },
296
+ tests: {
297
+ comment: "Module -> Module: subject is a test module importing/covering object.",
298
+ verbs: [
299
+ "tests",
300
+ "test",
301
+ "covers",
302
+ "cover",
303
+ "verifies",
304
+ "verify",
305
+ "exercises",
306
+ "exercise",
307
+ "checks",
308
+ "check",
309
+ "makes sure of",
310
+ "make sure of",
311
+ // gerund (g-drop normalization — §3.5)
312
+ "testing"
313
+ ]
314
+ },
315
+ inherits: {
316
+ comment: "Class -> Class: subject's declared base resolves to object (subclassOf).",
317
+ verbs: [
318
+ "inherits from",
319
+ "inherit from",
320
+ "extends",
321
+ "extend",
322
+ "subclasses",
323
+ "subclass",
324
+ "derives from",
325
+ "derive from",
326
+ "is a subclass of",
327
+ "are a subclass of",
328
+ "is a kind of",
329
+ "are a kind of",
330
+ "is built off",
331
+ "are built off",
332
+ "is built on top of",
333
+ "are built on top of",
334
+ // gerund (g-drop normalization — §3.5, and the compositional grammar's
335
+ // gerund-led boolean gate: "classes inheriting from Base but not tested").
336
+ // Both the two-word "inheriting from" (so "from" is consumed into the verb
337
+ // phrase, not the object term) and the bare "inheriting" (the single token
338
+ // the gerund-lead check reads) are listed; longest-match-first prefers the
339
+ // two-word form when "from" follows.
340
+ "extending",
341
+ "inheriting from",
342
+ "inheriting",
343
+ "subclassing",
344
+ "extends from"
345
+ ]
346
+ },
347
+ touches: {
348
+ comment: "Commit -> Module (coarse) or Commit -> symbol (fine, touchesSymbol): a commit's changed-line-range intersects object.",
349
+ verbs: [
350
+ "touched",
351
+ "touches",
352
+ "changed",
353
+ "change",
354
+ "modified",
355
+ "modifies",
356
+ "was changed in",
357
+ "were changed in",
358
+ "was edited in",
359
+ "were edited in",
360
+ "was modified by",
361
+ "were modified by",
362
+ "was tweaked in",
363
+ "were tweaked in",
364
+ "got changed in",
365
+ "got edited in",
366
+ // commit-question forms (2026-07-02, viewer commit-chat fix): the same touch
367
+ // relation asked from the commit's side — "which changes touch commit <sha>",
368
+ // "what did commit <sha> touch", "which functions changed in <sha>", "which
369
+ // changes landed in commit <sha>", "what was touched by commit <sha>". Bare
370
+ // "touch" completes the touched/touches pair for the "did … touch" auxiliary
371
+ // form; the "by"/"in" phrases are the passive/locative counterparts of forms
372
+ // already above (curated per register spread, not a thesaurus dump).
373
+ "touch",
374
+ "touched by",
375
+ "modified by",
376
+ "changed by",
377
+ "changed in",
378
+ "landed in",
379
+ "land in",
380
+ // contents-of-a-commit forms (2026-07-02, operator screenshot: "what was in
381
+ // commit <sha>" missed on the live site). "was in"/"went into" only read as
382
+ // touch questions when the object is a commit — the sha-shaped object keeps
383
+ // them from firing on structural questions ("is X in the graph" has no verb
384
+ // match anyway). Judgment call: "is in" omitted — too generic without the
385
+ // past-tense anchor and risks matching containment phrasings.
386
+ "was in",
387
+ "were in",
388
+ "went into",
389
+ "included in",
390
+ // when-question forms (2026-07-02 query families): "when was X last
391
+ // updated/edited" — bare past participles that only read naturally in the
392
+ // temporal shape; the when template routes them, but they are ordinary
393
+ // touches verbs so "which modules were updated ..." keeps working too.
394
+ "updated",
395
+ "edited",
396
+ // gerund (g-drop normalization — §3.5)
397
+ "touching"
398
+ ]
399
+ },
400
+ cochange: {
401
+ comment: "Module -> Module: subject and object are frequently committed together (changeCoupledWith).",
402
+ verbs: [
403
+ "changed with",
404
+ "co-changes with",
405
+ "co-change with",
406
+ "changes alongside",
407
+ "change alongside",
408
+ "shares commits with",
409
+ "share commits with",
410
+ "tends to change together with",
411
+ "tend to change together with",
412
+ "moves together with",
413
+ "move together with"
414
+ ]
415
+ },
416
+ reexports: {
417
+ comment: "Module -> exported symbol: subject's public API surface (__all__/export list).",
418
+ verbs: [
419
+ "exports",
420
+ "export",
421
+ "re-exports",
422
+ "re-export",
423
+ "passes through",
424
+ "pass through",
425
+ // API-surface phrasing (2026-07-02 query families): "what does <module>
426
+ // expose". Bare "exposed" is NOT listed — the lemma tier maps it here when
427
+ // an adapter is present, and "how does the API get exposed" has no
428
+ // traversal either way (pinned as an honest miss in the tests).
429
+ "exposes",
430
+ "expose",
431
+ // gerund (g-drop normalization — §3.5)
432
+ "exporting"
433
+ ]
434
+ }
435
+ };
436
+ var WHERE_MARKERS = Object.freeze(["defined", "declared", "located", "implemented"]);
437
+ var MENTION_MARKERS = Object.freeze(["mentioned", "referenced"]);
438
+ var VERB_TO_KIND = Object.freeze(
439
+ Object.fromEntries(
440
+ Object.entries(RELATIONS).flatMap(([kind, { verbs }]) => verbs.map((v) => [v, kind]))
441
+ )
442
+ );
443
+ var ENTITY_TO_TYPE = Object.freeze({
444
+ function: "Function",
445
+ functions: "Function",
446
+ method: "Method",
447
+ methods: "Method",
448
+ class: "Class",
449
+ classes: "Class",
450
+ module: "Module",
451
+ modules: "Module",
452
+ file: "Module",
453
+ files: "Module",
454
+ attribute: "Attribute",
455
+ attributes: "Attribute",
456
+ field: "Attribute",
457
+ fields: "Attribute",
458
+ variable: "GlobalVariable",
459
+ variables: "GlobalVariable",
460
+ global: "GlobalVariable",
461
+ globals: "GlobalVariable",
462
+ // "changes" in a touch question ("which changes touch commit <sha>") means the
463
+ // code entities on the other end of the touch edges, at WHATEVER grain the graph
464
+ // recorded — module (touches) and symbol (touchesSymbol) together when the commit
465
+ // is the given side, and the touching commits themselves when a module/symbol is
466
+ // the given side. Mapped to the pseudo-type "Change" (not a node class): ask.mjs's
467
+ // traverse() reads it as a wildcard over the touch traversal's results rather than
468
+ // aliasing it to ONE real class and silently dropping the other grain of the
469
+ // answer. Listed BEFORE commit/commits: findPhrase (ask.mjs) takes the first
470
+ // same-length phrase in table order, so in "which changes touch commit <sha>" the
471
+ // entity slot must consume "changes" and leave "commit <sha>" intact as the
472
+ // object term.
473
+ change: "Change",
474
+ changes: "Change",
475
+ commit: "Commit",
476
+ commits: "Commit"
477
+ });
478
+ var MODIFIER_TO_KIND = Object.freeze({
479
+ explicitly: "direct",
480
+ directly: "direct",
481
+ transitively: "transitive",
482
+ indirectly: "transitive"
483
+ });
484
+ var PASSIVE_PARTICIPLE_TO_KIND = Object.freeze({
485
+ imported: "imports",
486
+ called: "calls",
487
+ used: "uses",
488
+ tested: "tests",
489
+ covered: "tests",
490
+ verified: "tests",
491
+ exercised: "tests",
492
+ checked: "tests",
493
+ defined: "defines",
494
+ declared: "defines",
495
+ inherited: "inherits",
496
+ extended: "inherits",
497
+ subclassed: "inherits",
498
+ contained: "contains",
499
+ exported: "reexports",
500
+ "re-exported": "reexports",
501
+ exposed: "reexports",
502
+ touched: "touches",
503
+ changed: "touches",
504
+ modified: "touches",
505
+ edited: "touches",
506
+ updated: "touches"
507
+ });
508
+ var CONTRACTIONS = Object.freeze({
509
+ "ain't": "is not",
510
+ "aint": "is not",
511
+ "isn't": "is not",
512
+ "isnt": "is not",
513
+ "aren't": "are not",
514
+ "arent": "are not",
515
+ "doesn't": "does not",
516
+ "doesnt": "does not",
517
+ "don't": "do not",
518
+ "dont": "do not",
519
+ "didn't": "did not",
520
+ "didnt": "did not",
521
+ "there's": "there is",
522
+ "theres": "there is",
523
+ "what's": "what is",
524
+ "whats": "what is",
525
+ "who's": "who is",
526
+ "whos": "who is",
527
+ "gonna": "going to",
528
+ "wanna": "want to",
529
+ "gotta": "got to",
530
+ "yer": "your",
531
+ "ur": "your",
532
+ "gimme": "give me"
533
+ });
534
+ var MISSPELLINGS = Object.freeze({
535
+ // entity nouns
536
+ "funtion": "function",
537
+ "funtions": "functions",
538
+ "fucntion": "function",
539
+ "fucntions": "functions",
540
+ "functoin": "function",
541
+ "functoins": "functions",
542
+ "calss": "class",
543
+ "calsses": "classes",
544
+ "classs": "class",
545
+ "clases": "classes",
546
+ "modul": "module",
547
+ "moduls": "modules",
548
+ "moduel": "module",
549
+ "moduels": "modules",
550
+ "moudle": "module",
551
+ "moudles": "modules",
552
+ "methdo": "method",
553
+ "methdos": "methods",
554
+ "mehtod": "method",
555
+ "mehtods": "methods",
556
+ "comit": "commit",
557
+ "comits": "commits",
558
+ "commmit": "commit",
559
+ "commmits": "commits",
560
+ "varaible": "variable",
561
+ "varaibles": "variables",
562
+ "varibale": "variable",
563
+ "varibales": "variables",
564
+ "atribute": "attribute",
565
+ "atributes": "attributes",
566
+ // relation verbs
567
+ "improt": "import",
568
+ "improts": "imports",
569
+ "imoprt": "import",
570
+ "imoprts": "imports",
571
+ "inherts": "inherits",
572
+ "inheirts": "inherits",
573
+ "extands": "extends",
574
+ "extneds": "extends",
575
+ "depnds": "depends",
576
+ "touchs": "touches",
577
+ "tuoches": "touches",
578
+ "touhced": "touched",
579
+ "chagned": "changed",
580
+ "chnaged": "changed",
581
+ "chagnes": "changes",
582
+ "chnages": "changes",
583
+ "calles": "calls",
584
+ "exprot": "export",
585
+ "exprots": "exports",
586
+ "tets": "tests",
587
+ // grammar anchor words
588
+ "whcih": "which",
589
+ "wich": "which",
590
+ "whihc": "which",
591
+ // "wat" (chatbench cycle 2, tf-wat-calls): the internet-casual spelling of
592
+ // "what" — neither curated noise nor a restorable trigger typo, so "wat calls
593
+ // fnAlpha" used to die as "couldn't resolve one of the terms". Restored here
594
+ // so BOTH parse strategies and the relaxation cascade see the canonical
595
+ // anchor; the correction regex's dotted-extension guard keeps a module
596
+ // literally named "wat.mjs" untouched, same residual trade as every entry.
597
+ "waht": "what",
598
+ "wat": "what",
599
+ "dose": "does",
600
+ "doess": "does",
601
+ "teh": "the",
602
+ // aggregate/list TRIGGER words (2026-07-02, trigger-typo work) — a typo of a count
603
+ // or list trigger used to be DROPPED as unmatched by the relaxation cascade, losing
604
+ // the aggregate/list INTENT entirely ("how manyn classes" → the count was lost);
605
+ // curated here so the intended trigger is restored BEFORE parsing (the general
606
+ // bounded fuzzy path in ask.mjs's cascade is the backstop for uncurated typos).
607
+ "manyn": "many",
608
+ "mnay": "many",
609
+ "amny": "many",
610
+ "mnany": "many",
611
+ "coutn": "count",
612
+ "conut": "count",
613
+ "cuont": "count",
614
+ "ocunt": "count",
615
+ "numer": "number",
616
+ "nubmer": "number",
617
+ "numbr": "number",
618
+ "nmuber": "number",
619
+ "lst": "list",
620
+ "lsit": "list",
621
+ "ilst": "list",
622
+ "shwo": "show",
623
+ "hsow": "show",
624
+ "dispaly": "display",
625
+ "dsiplay": "display",
626
+ "funtcions": "functions",
627
+ "funciton": "function",
628
+ "funcitons": "functions"
629
+ });
630
+ var WRONG_WORDS = Object.freeze({
631
+ // neither a folder nor a directory grain exists in this graph — in "which
632
+ // folders import X" the only honest referent is the module/file grain.
633
+ "folder": "module",
634
+ "folders": "modules",
635
+ "directory": "module",
636
+ "directories": "modules",
637
+ // unambiguous abbreviation of an entity noun this grammar owns
638
+ "func": "function",
639
+ "funcs": "functions",
640
+ // VCS terms whose canonical schema class here is Commit
641
+ "changeset": "commit",
642
+ "changesets": "commits",
643
+ "revision": "commit",
644
+ "revisions": "commits",
645
+ // code-graph "property" is the Attribute grain in this schema
646
+ "property": "attribute",
647
+ "properties": "attributes"
648
+ });
649
+ var G_DROP = /\b([a-z]{3,})in'/gi;
650
+ var FILLER_WORDS = Object.freeze([
651
+ "um",
652
+ "uh",
653
+ "erm",
654
+ "so",
655
+ "like",
656
+ "yo",
657
+ "hey",
658
+ "bru",
659
+ "bro",
660
+ "fam",
661
+ "mate",
662
+ "please",
663
+ "could you",
664
+ "can you",
665
+ "would you",
666
+ "tell me",
667
+ "i wonder",
668
+ "just wondering",
669
+ "quickly",
670
+ "real quick",
671
+ "kinda",
672
+ "sorta"
673
+ ]);
674
+ var CONTEXT_PRONOUNS = Object.freeze(["this", "it", "that", "here", "this one", "that one"]);
675
+ var NEGATION_FRAMES = Object.freeze([
676
+ // "there ain't nothin' calling it" / "there isn't nothing that calls it"
677
+ // -> "what calls it"
678
+ { re: /\bthere\s+is\s+not\s+(?:anything|nothing)\s+(?:that\s+)?(\S.*)$/i, to: (m) => `what ${m[1]}` },
679
+ // "there's no module that imports auth" -> "what imports auth"
680
+ { re: /\bthere\s+is\s+no\s+\S+\s+(?:that\s+)?(\S.*)$/i, to: (m) => `what ${m[1]}` },
681
+ // "isn't there anything calling it" -> "what calls it" (question-inverted form)
682
+ { re: /\bis\s+not\s+there\s+(?:anything|nothing)\s+(?:that\s+)?(\S.*)$/i, to: (m) => `what ${m[1]}` },
683
+ // "nobody/nothing calls it, does it" (tag-question double-negative) -> "what calls it"
684
+ { re: /\b(?:nobody|nothing|no one)\s+(\S.*?)(?:,?\s+does\s+it\??|,?\s+do\s+they\??)?$/i, to: (m) => `what ${m[1]}` }
685
+ ]);
686
+ var COMMIT_CONTENT_FRAMES = Object.freeze([
687
+ // "what was in commit ef74e44e25c8" / "what is in ef74e44e" / "what's in commit <sha>"
688
+ { re: /^what\s+(?:is|was|were|are)\s+in\s+((?:commit\s+)?[0-9a-f]{7,40})\??$/i, to: (m) => `what did ${m[1]} touch` },
689
+ // "what went into commit ef74e44e25c8" / casual "what got into <sha>"
690
+ { re: /^what\s+(?:went|got)\s+into\s+((?:commit\s+)?[0-9a-f]{7,40})\??$/i, to: (m) => `what did ${m[1]} touch` },
691
+ // "what made it into commit ef74e44e25c8"
692
+ { re: /^what\s+made\s+it\s+into\s+((?:commit\s+)?[0-9a-f]{7,40})\??$/i, to: (m) => `what did ${m[1]} touch` }
693
+ ]);
694
+ var META_MEANING_VERBS = Object.freeze([
695
+ "mean",
696
+ "means",
697
+ "stand for",
698
+ "stands for",
699
+ "signify",
700
+ "signifies",
701
+ "represent",
702
+ "represents",
703
+ "refer to",
704
+ "refers to"
705
+ ]);
706
+ var RELATIVE_PRONOUNS = Object.freeze(["that", "which", "who"]);
707
+ var PLACEHOLDER_NOUNS = Object.freeze([
708
+ "something",
709
+ "anything",
710
+ "everything",
711
+ "somethings",
712
+ "things",
713
+ "thing",
714
+ "entities",
715
+ "entity",
716
+ "nodes",
717
+ "node",
718
+ "stuff",
719
+ "code",
720
+ // "symbol(s)" is a grain-agnostic stand-in for any code entity — "exported
721
+ // symbols of X" means whatever X defines, at any grain, filtered by the qualifier.
722
+ "symbol",
723
+ "symbols"
724
+ ]);
725
+ var BOOLEAN_CONNECTIVES = Object.freeze({
726
+ "but not": "difference",
727
+ "and not": "difference",
728
+ "except": "difference",
729
+ "without": "difference",
730
+ "and": "intersection",
731
+ "plus": "intersection",
732
+ "or": "union"
733
+ });
734
+ var QUALIFIERS = Object.freeze({
735
+ public: { via: "visibility", value: "public" },
736
+ private: { via: "visibility", value: "private" },
737
+ protected: { via: "visibility", value: "protected" },
738
+ static: { via: "attr", attr: "isStatic" },
739
+ abstract: { via: "attr", attr: "isAbstract" },
740
+ constant: { via: "attr", attr: "isConstant" },
741
+ exported: { via: "exported" },
742
+ "re-exported": { via: "exported" },
743
+ tested: { via: "tested", value: true },
744
+ covered: { via: "tested", value: true },
745
+ untested: { via: "tested", value: false },
746
+ uncovered: { via: "tested", value: false }
747
+ });
748
+ var AGGREGATE_TRIGGERS = Object.freeze([
749
+ // formal ("the number of classes" reaches "number of" once the cascade strips the
750
+ // leading article — keeping the trigger list clear of "the" so it never enters
751
+ // CONTENT_VOCAB and blocks the article's own noise-strip)
752
+ "how many",
753
+ "how much",
754
+ "how many of",
755
+ "number of",
756
+ "total number of",
757
+ "quantity of",
758
+ // "<measure> of <kind>" cardinality forms (widened net, cycle W2P): count = sum = total
759
+ // = tally = number of. The bare single words (sum/total/tally) stay CASCADE_SYNONYMS-
760
+ // mapped to "count" (identifier-fragment risk without the "of" anchor — see that table's
761
+ // note); the multi-word "of" forms are safe to promote to direct triggers because the
762
+ // trailing "of <kind>" pins them to a cardinality question, not a stray identifier.
763
+ "tally of",
764
+ "sum of",
765
+ "total of",
766
+ "amount of",
767
+ // neutral / imperative (bare "tally"/"sum"/"total" stay CASCADE_SYNONYMS-mapped so the
768
+ // "tally the classes" relaxation path — pinned by a cascade test — is preserved).
769
+ "count",
770
+ "count up",
771
+ "count of",
772
+ "tot up"
773
+ ]);
774
+ var LIST_TRIGGERS = Object.freeze([
775
+ // imperative — "<verb> [me/us] [the] <kind>"
776
+ "list",
777
+ "show",
778
+ "show me",
779
+ "show us",
780
+ "display",
781
+ "print",
782
+ "print out",
783
+ "dump",
784
+ "enumerate",
785
+ "name",
786
+ "give me",
787
+ "get me",
788
+ "spit out",
789
+ "rattle off",
790
+ "run down",
791
+ "run through",
792
+ "ls",
793
+ // interrogative — "what/which are [the] <kind>"
794
+ "what are",
795
+ "which are"
796
+ ]);
797
+ var SUPERLATIVE_EXTREMES = Object.freeze({
798
+ most: "most",
799
+ greatest: "most",
800
+ highest: "most",
801
+ biggest: "most",
802
+ largest: "most",
803
+ "most-connected": "most",
804
+ "most connected": "most",
805
+ fewest: "fewest",
806
+ least: "fewest",
807
+ smallest: "fewest",
808
+ lowest: "fewest"
809
+ });
810
+ var EDGE_NOUN_TO_METRIC = Object.freeze({
811
+ imports: { kind: "imports", dir: "out" },
812
+ dependencies: { kind: "imports", dir: "out" },
813
+ importers: { kind: "imports", dir: "in" },
814
+ dependents: { kind: "imports", dir: "in" },
815
+ callers: { kind: "calls", dir: "in", sibling: "callsSymbol" },
816
+ callees: { kind: "calls", dir: "out", sibling: "callsSymbol" },
817
+ calls: { kind: "calls", dir: "out", sibling: "callsSymbol" },
818
+ methods: { kind: "contains", dir: "out", filter: "Method" },
819
+ members: { kind: "contains", dir: "out" },
820
+ tests: { kind: "tests", dir: "in" },
821
+ subclasses: { kind: "inherits", dir: "in" },
822
+ connections: { kind: "*", dir: "both" },
823
+ edges: { kind: "*", dir: "both" },
824
+ connected: { kind: "*", dir: "both" },
825
+ // participle degree-nouns (widened net, cycle W2P): "the most imported / most
826
+ // depended-on / most used <module>" ranks by IN-degree — how many things import/depend
827
+ // on/use it — the ARGMAX-by-degree intent a developer expresses with a passive
828
+ // participle rather than the noun ("importers"). "depended" catches "depended-on" /
829
+ // "depended on" (both tokenize to a bare "depended"); "used" folds the symbol-grain
830
+ // callsSymbol callers in alongside importers so "most used" reads as most-relied-upon.
831
+ imported: { kind: "imports", dir: "in" },
832
+ "depended-on": { kind: "imports", dir: "in" },
833
+ depended: { kind: "imports", dir: "in" },
834
+ used: { kind: "imports", dir: "in", sibling: "callsSymbol" },
835
+ called: { kind: "calls", dir: "in", sibling: "callsSymbol" }
836
+ });
837
+ var ANAPHORA_TRIGGERS = Object.freeze(["those", "them", "these"]);
838
+ var MEMBERSHIP_KINDS = Object.freeze(["contains", "defines"]);
839
+ var CASCADE_NOISE = Object.freeze([
840
+ // articles / vague determiners (kept OUT of content vocab so they're strippable;
841
+ // the aggregate/where parsers already tolerate a stray "the"/"a", so stripping is
842
+ // belt-and-braces, not load-bearing)
843
+ "the",
844
+ "a",
845
+ "an",
846
+ "some",
847
+ // topic lead-in filler — "what about the modules", "how about classes": "about"
848
+ // carries no graph meaning here, so stripping it lets the bare kind noun surface for
849
+ // the cascade's bare-kind-noun terminal rule (ask.mjs). ("what"/"how" are structural
850
+ // question words the drop-pass keeps; only the "about" between them and the kind is
851
+ // noise.) A module literally named "about" is safe-listed by relaxParse's resolvesExact
852
+ // guard, same as every other noise token.
853
+ "about",
854
+ // politeness / hedges (single-token; multi-word "could you"/"please" etc. are
855
+ // FILLER_WORDS, stripped earlier during normalization)
856
+ "please",
857
+ "pls",
858
+ "plz",
859
+ "kindly",
860
+ "just",
861
+ "simply",
862
+ "maybe",
863
+ "perhaps",
864
+ "thanks",
865
+ "thank",
866
+ "ta",
867
+ "cheers",
868
+ // greetings a question sometimes opens with (chat.mjs owns standalone greetings;
869
+ // here they're only stripped when embedded in an otherwise-real question)
870
+ "hi",
871
+ "hello",
872
+ "hey",
873
+ "yo",
874
+ "hiya",
875
+ "howdy",
876
+ "ok",
877
+ "okay",
878
+ // vocatives / terms of address (the "matey" of the worked example, and its kin)
879
+ "matey",
880
+ "mate",
881
+ "buddy",
882
+ "pal",
883
+ "dude",
884
+ "man",
885
+ "bro",
886
+ "bru",
887
+ "fam",
888
+ "friend",
889
+ "sir",
890
+ "maam",
891
+ "folks",
892
+ "guys",
893
+ "everyone",
894
+ "dear",
895
+ // the product's OWN name used as an address (chatbench cycle 2, ns-hey-tmct:
896
+ // "hey tmct, what calls fnAlpha thanks") — a vocative like "matey", stripped
897
+ // by the same rules: relaxParse's resolvesExact guard still protects a module
898
+ // literally named "tmct", and noise-strip's template/keyword-spot acceptance
899
+ // bounds the cost of a mid-question strip to an honest object-miss.
900
+ "tmct",
901
+ // presentation frames — the keyword-spotting strategy's blind spot: the
902
+ // compositional grammar skips these as FRAME_WORDS, but "show me what imports X"
903
+ // otherwise decomposes (via keyword-spot) to ask{subject:"show me"}. Stripping
904
+ // them on a miss recovers the underlying reverse/forward question. ("count" is NOT
905
+ // here — it is an aggregate trigger; "find"/"search" are here as presentation
906
+ // verbs, not the tmct_search tool, which ask.mjs never dispatches.)
907
+ "show",
908
+ "tell",
909
+ "give",
910
+ "list",
911
+ "find",
912
+ "me",
913
+ "us",
914
+ "lemme"
915
+ ]);
916
+ var CASCADE_SYNONYMS = Object.freeze({
917
+ tally: "count",
918
+ tallies: "count",
919
+ sum: "count",
920
+ total: "count",
921
+ totals: "count"
922
+ });
923
+ var HELP_TRIGGERS = Object.freeze([
924
+ "help",
925
+ "help me",
926
+ "how do i ask",
927
+ "how do i use this",
928
+ "what can i ask",
929
+ "what can you ask",
930
+ "usage",
931
+ "commands",
932
+ "examples",
933
+ "syntax",
934
+ "options"
935
+ ]);
936
+
937
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/normalize.mjs
938
+ function escapeRegex(s) {
939
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
940
+ }
941
+ var tableRe = (table) => new RegExp(
942
+ "\\b(" + Object.keys(table).sort((a, b) => b.length - a.length).map(escapeRegex).join("|") + ")\\b",
943
+ "gi"
944
+ );
945
+ var CONTRACTION_RE = tableRe(CONTRACTIONS);
946
+ var correctionRe = (table) => new RegExp(
947
+ "\\b(" + Object.keys(table).sort((a, b) => b.length - a.length).map(escapeRegex).join("|") + ")\\b(?!\\.[a-z0-9])",
948
+ "gi"
949
+ );
950
+ var MISSPELLING_RE = correctionRe(MISSPELLINGS);
951
+ var WRONG_WORD_RE = correctionRe(WRONG_WORDS);
952
+ function normalizeQuery(text) {
953
+ let q = String(text || "");
954
+ q = q.replace(CONTRACTION_RE, (m) => CONTRACTIONS[m.toLowerCase()]);
955
+ q = q.replace(MISSPELLING_RE, (m) => MISSPELLINGS[m.toLowerCase()]);
956
+ q = q.replace(WRONG_WORD_RE, (m) => WRONG_WORDS[m.toLowerCase()]);
957
+ q = q.replace(G_DROP, "$1ing");
958
+ if (FILLER_WORDS.length) {
959
+ const fillerRe = new RegExp(
960
+ "\\b(" + [...FILLER_WORDS].sort((a, b) => b.length - a.length).map(escapeRegex).join("|") + ")\\b",
961
+ "gi"
962
+ );
963
+ q = q.replace(fillerRe, " ");
964
+ }
965
+ q = q.replace(/\?{2,}\s*$/, "?");
966
+ return q.replace(/\s+/g, " ").trim();
967
+ }
968
+ function applyNegationFrames(text) {
969
+ for (const frame of [...COMMIT_CONTENT_FRAMES, ...NEGATION_FRAMES]) {
970
+ const m = text.match(frame.re);
971
+ if (m) return frame.to(m).replace(/\s+/g, " ").trim();
972
+ }
973
+ return text;
974
+ }
975
+ var NEGATION_SET_RE = new RegExp(
976
+ "^(?: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
+ // the negation marker + (2) the predicate
978
+ "i"
979
+ );
980
+ function matchNegationSet(text) {
981
+ const m = String(text || "").match(NEGATION_SET_RE);
982
+ if (!m) return null;
983
+ const entWord = m[1].toLowerCase();
984
+ const predicate = m[2].trim();
985
+ if (!predicate) return null;
986
+ return { entWord, predicate };
987
+ }
988
+ var STOPWORDS2 = /* @__PURE__ */ new Set([
989
+ "what",
990
+ "who",
991
+ "which",
992
+ "where",
993
+ "when",
994
+ "why",
995
+ "how",
996
+ "does",
997
+ "do",
998
+ "did",
999
+ "is",
1000
+ "are",
1001
+ "was",
1002
+ "were",
1003
+ "the",
1004
+ "a",
1005
+ "an",
1006
+ "of",
1007
+ "to",
1008
+ "from",
1009
+ "at",
1010
+ "in",
1011
+ "on",
1012
+ "there",
1013
+ "something",
1014
+ "anything",
1015
+ "nothing",
1016
+ "one",
1017
+ "any",
1018
+ // temporal filler in when-questions ("when was X last touched") — a symbol
1019
+ // literally named "last" would be the accepted residual cost, same trade as
1020
+ // every other stopword.
1021
+ "last"
1022
+ ]);
1023
+ var splitWords = (text) => String(text).replace(/\?+\s*$/, "").replace(/,/g, " ").split(/\s+/).filter(Boolean);
1024
+ var wordsOf = (arr) => arr.flatMap((p) => String(p).toLowerCase().split(" "));
1025
+
1026
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/fuzzy.mjs
1027
+ function editDistance(a, b, max) {
1028
+ if (a === b) return 0;
1029
+ if (Math.abs(a.length - b.length) > max) return max + 1;
1030
+ let prev2 = null;
1031
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
1032
+ for (let i = 1; i <= a.length; i += 1) {
1033
+ const cur = [i];
1034
+ let rowMin = i;
1035
+ for (let j = 1; j <= b.length; j += 1) {
1036
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
1037
+ let v = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
1038
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) v = Math.min(v, prev2[j - 2] + cost);
1039
+ cur[j] = v;
1040
+ if (v < rowMin) rowMin = v;
1041
+ }
1042
+ if (rowMin > max) return max + 1;
1043
+ prev2 = prev;
1044
+ prev = cur;
1045
+ }
1046
+ return prev[b.length];
1047
+ }
1048
+ var fuzzyBound = (s) => s.length <= 5 ? 1 : 2;
1049
+ var VOCAB_WORDS = new Set(
1050
+ [...Object.keys(VERB_TO_KIND), ...Object.keys(ENTITY_TO_TYPE), ...Object.keys(MODIFIER_TO_KIND)].flatMap((p) => p.split(" "))
1051
+ );
1052
+ var FUZZY_TARGET_WORDS = [...new Set(
1053
+ [...Object.keys(VERB_TO_KIND), ...Object.keys(MODIFIER_TO_KIND)].flatMap((p) => p.split(" ")).filter((w) => w.length >= 4)
1054
+ )];
1055
+ function eligibleForCanon(w) {
1056
+ return /^[a-z]+$/.test(w) && !STOPWORDS2.has(w) && !VOCAB_WORDS.has(w);
1057
+ }
1058
+ function fuzzyVocabWord(w) {
1059
+ const bound = fuzzyBound(w);
1060
+ let best = bound + 1;
1061
+ let hit = null;
1062
+ let tied = false;
1063
+ for (const target of FUZZY_TARGET_WORDS) {
1064
+ const d = editDistance(w, target, Math.min(best, bound));
1065
+ if (d < best) {
1066
+ best = d;
1067
+ hit = target;
1068
+ tied = false;
1069
+ } else if (d === best && d <= bound && target !== hit) tied = true;
1070
+ }
1071
+ return best <= bound && !tied ? hit : null;
1072
+ }
1073
+
1074
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/strategies/grammar.mjs
1075
+ var VERB_ALT = Object.keys(VERB_TO_KIND).sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
1076
+ var ENTITY_ALT = Object.keys(ENTITY_TO_TYPE).sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
1077
+ var MODIFIER_ALT = Object.keys(MODIFIER_TO_KIND).sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
1078
+ var META_ALT = META_MEANING_VERBS.slice().sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
1079
+ var TEMPLATES = [
1080
+ // T1 ASK: "does X import Y" / "is X a subclass of Y" -> Yes/No. Tried FIRST: it starts with
1081
+ // does/is/do/did, which the reverse/forward templates below never match (those start with
1082
+ // which/what), so precedence between T1 and the rest is structural, not a tie-break guess.
1083
+ // "did" joins does/do for the past-tense commit forms ("did commit <sha> touch X").
1084
+ {
1085
+ name: "ask",
1086
+ re: new RegExp(`^(?:does|do|did)\\s+(.+?)\\s+(${VERB_ALT})\\s+(.+?)\\??$`, "i"),
1087
+ build: (m) => ({
1088
+ shape: "ask",
1089
+ entityType: null,
1090
+ modifier: "direct",
1091
+ kind: VERB_TO_KIND[m[2].toLowerCase()],
1092
+ subject: m[1].trim(),
1093
+ object: m[3].trim()
1094
+ })
1095
+ },
1096
+ // T2 reverse: "which <entity> [<modifier>] <verb> <object>" — the operator's own example shape.
1097
+ {
1098
+ name: "reverse",
1099
+ re: new RegExp(`^which\\s+(${ENTITY_ALT})\\s+(?:(${MODIFIER_ALT})\\s+)?(${VERB_ALT})\\s+(.+?)\\??$`, "i"),
1100
+ build: (m) => ({
1101
+ shape: "reverse",
1102
+ entityType: ENTITY_TO_TYPE[m[1].toLowerCase()],
1103
+ modifier: m[2] ? MODIFIER_TO_KIND[m[2].toLowerCase()] : "direct",
1104
+ kind: VERB_TO_KIND[m[3].toLowerCase()],
1105
+ object: m[4].trim()
1106
+ })
1107
+ },
1108
+ // T3 forward: "what does <object> <verb>" — X is given, list its R-related things.
1109
+ // "did" joins does/do for the past-tense commit forms ("what did commit <sha> touch").
1110
+ {
1111
+ name: "forward",
1112
+ re: new RegExp(`^what\\s+(?:does|do|did)\\s+(.+?)\\s+(${VERB_ALT})\\??$`, "i"),
1113
+ build: (m) => ({
1114
+ shape: "forward",
1115
+ entityType: null,
1116
+ modifier: "direct",
1117
+ kind: VERB_TO_KIND[m[2].toLowerCase()],
1118
+ object: m[1].trim()
1119
+ })
1120
+ },
1121
+ // T4 meta: "what does <term> mean" — a question about the GRAPH'S OWN VOCABULARY
1122
+ // (a SchemaClass/SchemaPredicate label, e.g. "cochange", or a raw prop token, e.g.
1123
+ // "mgx:callsSymbol"), not a graph traversal over code edges. Tried after T3: T3 also
1124
+ // starts "what does/do", but T3 only fires when the tail is a relation VERB_ALT
1125
+ // phrase ("import"/"calls"/…), which "mean"/"means"/etc never are (disjoint tables —
1126
+ // ask-vocab.mjs's file comment explains why they're kept separate), so the two never
1127
+ // actually compete for the same input.
1128
+ {
1129
+ name: "meta-mean",
1130
+ re: new RegExp(`^what\\s+(?:does|do|is|are)\\s+(.+?)\\s+(?:${META_ALT})\\??$`, "i"),
1131
+ build: (m) => ({ shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: m[1].trim() })
1132
+ },
1133
+ // T5 meta: "what is a/an <term>" — the OTHER worked phrasing ("what is a Commit").
1134
+ // The indefinite article is REQUIRED (not optional): a bare "what is <anything>"
1135
+ // would also swallow "what is the meaning of this codebase" (an existing, deliberately
1136
+ // honest grammar-miss regression case — ask.test.mjs/ask-dual-strategy.test.mjs both
1137
+ // assert it stays null), which never mentions "a"/"an" before its tail. Requiring the
1138
+ // article keeps this template's reach to the one worked shape without reopening that.
1139
+ {
1140
+ name: "meta-whatis",
1141
+ re: new RegExp(`^what\\s+(?:is|are)\\s+(?:an?)\\s+(.+?)\\??$`, "i"),
1142
+ build: (m) => ({ shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: m[1].trim() })
1143
+ },
1144
+ // T6 mention: "where is <term> mentioned/referenced" — the prose/mentions surface
1145
+ // (2026-07-02 query families). Tried BEFORE T7: T7's trailing marker is optional,
1146
+ // so without this ordering it would swallow the mention question and lose the
1147
+ // marker that distinguishes "locate the definition" from "list the prose mentions".
1148
+ {
1149
+ name: "mention",
1150
+ re: new RegExp(`^where\\s+(?:is|are|was|were)\\s+(.+?)\\s+(?:${MENTION_MARKERS.map(escapeRegex).join("|")})\\??$`, "i"),
1151
+ build: (m) => ({ shape: "mentions", entityType: null, modifier: "direct", kind: "mentions", object: m[1].trim() })
1152
+ },
1153
+ // T7 where: "where is <term> [defined|declared|located|implemented]" — definition
1154
+ // location off the site attribute / defining module. "where" starts no other
1155
+ // template, so precedence against T1-T5 is structural.
1156
+ {
1157
+ name: "where",
1158
+ re: new RegExp(`^where\\s+(?:is|are|was|were)\\s+(.+?)(?:\\s+(?:${WHERE_MARKERS.map(escapeRegex).join("|")}))?\\??$`, "i"),
1159
+ build: (m) => ({ shape: "where", entityType: null, modifier: "direct", kind: "where", object: m[1].trim() })
1160
+ },
1161
+ // T8 when: "when did <term> [last] change/touched/updated…" — temporal shape over
1162
+ // the touches edges + commit date attributes. The verb slot reuses VERB_ALT, but
1163
+ // only the touches family carries dates to answer with, so build() rejects any
1164
+ // other kind (returning null falls through — parseAnchored tolerates it) rather
1165
+ // than pretending "when did X import Y" has a temporal answer.
1166
+ {
1167
+ name: "when",
1168
+ re: new RegExp(`^when\\s+(?:did|does|do|was|were|is)\\s+(.+?)\\s+(?:last\\s+)?(${VERB_ALT})\\??$`, "i"),
1169
+ build: (m) => VERB_TO_KIND[m[2].toLowerCase()] === "touches" ? { shape: "when", entityType: null, modifier: "direct", kind: "touches", object: m[1].trim() } : null
1170
+ }
1171
+ ];
1172
+ function parseAnchored(text) {
1173
+ for (const t of TEMPLATES) {
1174
+ const m = text.match(t.re);
1175
+ if (m) {
1176
+ const parsed = t.build(m);
1177
+ if (parsed) return parsed;
1178
+ }
1179
+ }
1180
+ return null;
1181
+ }
1182
+ var grammarStrategy = {
1183
+ id: "grammar",
1184
+ class: "graph-query",
1185
+ run(text) {
1186
+ const parsed = parseAnchored(text);
1187
+ return parsed ? { strategyId: "grammar", class: "graph-query", candidates: [{ parsed, confidence: 0.9 }] } : null;
1188
+ }
1189
+ };
1190
+
1191
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/strategies/keywords.mjs
1192
+ var PASSIVE_AUX = /* @__PURE__ */ new Set(["is", "are", "was", "were", "be", "been", "being", "get", "gets", "got"]);
1193
+ var WH_WORDS = /* @__PURE__ */ new Set(["which", "what", "who", "whom", "whose"]);
1194
+ var PLACEHOLDER_SET = new Set(PLACEHOLDER_NOUNS.map((w) => w.toLowerCase()));
1195
+ function findPhrase(lcWords, table, consumed = null) {
1196
+ const phrases = Object.keys(table).sort((a, b) => b.split(" ").length - a.split(" ").length);
1197
+ for (const p of phrases) {
1198
+ const pWords = p.split(" ");
1199
+ for (let i = 0; i <= lcWords.length - pWords.length; i += 1) {
1200
+ if (consumed && pWords.some((_, j) => consumed.has(i + j))) continue;
1201
+ if (pWords.every((w, j) => lcWords[i + j] === w)) return { kind: table[p], start: i, end: i + pWords.length };
1202
+ }
1203
+ }
1204
+ return null;
1205
+ }
1206
+ function parseKeywordSpot(text, nlp = null) {
1207
+ const words = text.replace(/\?+\s*$/, "").replace(/,/g, " ").split(/\s+/).filter(Boolean);
1208
+ const lcWords = words.map((w) => w.toLowerCase());
1209
+ if (lcWords.includes("where") && !findPhrase(lcWords, VERB_TO_KIND)) {
1210
+ const mention = lcWords.some((w) => MENTION_MARKERS.includes(w));
1211
+ const markers = /* @__PURE__ */ new Set([...WHERE_MARKERS, ...MENTION_MARKERS]);
1212
+ const objText = words.filter((w, i) => !STOPWORDS2.has(lcWords[i]) && !markers.has(lcWords[i])).join(" ").trim();
1213
+ if (objText) {
1214
+ const kind2 = mention ? "mentions" : "where";
1215
+ return { shape: kind2, entityType: null, modifier: "direct", kind: kind2, object: objText };
1216
+ }
1217
+ }
1218
+ let canonWords = lcWords;
1219
+ let verbHit = findPhrase(lcWords, VERB_TO_KIND);
1220
+ if (!verbHit && nlp) {
1221
+ const lemmaWords = lcWords.map((w) => {
1222
+ if (!eligibleForCanon(w)) return w;
1223
+ const l = nlp.lemma(w);
1224
+ return VOCAB_WORDS.has(l) ? l : w;
1225
+ });
1226
+ verbHit = findPhrase(lemmaWords, VERB_TO_KIND);
1227
+ if (verbHit) canonWords = lemmaWords;
1228
+ }
1229
+ if (!verbHit) {
1230
+ const fuzzyWords = lcWords.map((w) => w.length >= 4 && eligibleForCanon(w) ? fuzzyVocabWord(w) || w : w);
1231
+ verbHit = findPhrase(fuzzyWords, VERB_TO_KIND);
1232
+ if (verbHit) canonWords = fuzzyWords;
1233
+ }
1234
+ if (!verbHit && lcWords.includes("by")) {
1235
+ for (let i = 0; i < lcWords.length; i += 1) {
1236
+ const k = PASSIVE_PARTICIPLE_TO_KIND[lcWords[i]];
1237
+ if (k && lcWords.slice(0, i).some((w) => PASSIVE_AUX.has(w))) {
1238
+ verbHit = { kind: k, start: i, end: i + 1 };
1239
+ break;
1240
+ }
1241
+ }
1242
+ }
1243
+ if (!verbHit) return null;
1244
+ if (nlp && verbHit.end - verbHit.start === 1) {
1245
+ const i = verbHit.start;
1246
+ const det = lcWords[i - 1];
1247
+ if ((det === "the" || det === "these" || det === "those") && lcWords[i + 1] === "of") {
1248
+ const tags = nlp.posTags(words);
1249
+ if (tags[i] === "NOUN") {
1250
+ const objText = words.slice(i + 2).filter((w, j) => !STOPWORDS2.has(lcWords[i + 2 + j])).join(" ").trim();
1251
+ if (objText) return { shape: "forward", entityType: null, modifier: "direct", kind: verbHit.kind, object: objText };
1252
+ }
1253
+ }
1254
+ }
1255
+ const consumed = /* @__PURE__ */ new Set();
1256
+ const mark = (hit) => {
1257
+ if (hit) for (let i = hit.start; i < hit.end; i += 1) consumed.add(i);
1258
+ };
1259
+ mark(verbHit);
1260
+ const entityHit = findPhrase(canonWords, ENTITY_TO_TYPE, consumed);
1261
+ mark(entityHit);
1262
+ const modifierHit = findPhrase(canonWords, MODIFIER_TO_KIND, consumed);
1263
+ mark(modifierHit);
1264
+ const sideText = (from, to) => words.slice(from, to).filter((_, j) => !consumed.has(from + j) && !STOPWORDS2.has(lcWords[from + j])).join(" ").trim();
1265
+ const beforeText = sideText(0, verbHit.start);
1266
+ const afterText = sideText(verbHit.end, words.length);
1267
+ const kind = verbHit.kind;
1268
+ const entityType = entityHit ? ENTITY_TO_TYPE[canonWords.slice(entityHit.start, entityHit.end).join(" ")] : null;
1269
+ const modifier = modifierHit ? MODIFIER_TO_KIND[canonWords.slice(modifierHit.start, modifierHit.end).join(" ")] : "direct";
1270
+ if (kind === "touches" && lcWords.includes("when")) {
1271
+ const objText = beforeText || afterText;
1272
+ if (objText) return { shape: "when", entityType: null, modifier: "direct", kind: "touches", object: objText };
1273
+ }
1274
+ const byIdx = lcWords.indexOf("by");
1275
+ const hasPassiveAux = lcWords.slice(0, verbHit.start).some((w) => PASSIVE_AUX.has(w));
1276
+ if (byIdx >= 0 && !consumed.has(byIdx) && hasPassiveAux) {
1277
+ const roleWords = [];
1278
+ for (let i = 0; i < words.length; i += 1) {
1279
+ const w = lcWords[i];
1280
+ if (consumed.has(i) || STOPWORDS2.has(w) || w === "by" || PASSIVE_AUX.has(w) || WH_WORDS.has(w) || PLACEHOLDER_SET.has(w)) continue;
1281
+ roleWords.push(words[i]);
1282
+ }
1283
+ const object = roleWords.join(" ").trim();
1284
+ if (object) {
1285
+ let nextAfterBy = null;
1286
+ for (let i = byIdx + 1; i < lcWords.length; i += 1) {
1287
+ if (lcWords[i] === "the" || lcWords[i] === "a" || lcWords[i] === "an") continue;
1288
+ nextAfterBy = lcWords[i];
1289
+ break;
1290
+ }
1291
+ const agentNamed = nextAfterBy != null && !WH_WORDS.has(nextAfterBy) && !ENTITY_TO_TYPE[nextAfterBy];
1292
+ return { shape: agentNamed ? "forward" : "reverse", entityType, modifier, kind, object };
1293
+ }
1294
+ }
1295
+ if (beforeText && afterText) return { shape: "ask", entityType: null, modifier: "direct", kind, subject: beforeText, object: afterText };
1296
+ if (afterText) return { shape: "reverse", entityType, modifier, kind, object: afterText };
1297
+ if (beforeText) return { shape: "forward", entityType, modifier: "direct", kind, object: beforeText };
1298
+ return null;
1299
+ }
1300
+ var keywordSpotStrategy = {
1301
+ id: "keyword-spot",
1302
+ class: "graph-query",
1303
+ run(text, ctx = {}) {
1304
+ const parsed = parseKeywordSpot(text, ctx.nlp || null);
1305
+ return parsed ? { strategyId: "keyword-spot", class: "graph-query", candidates: [{ parsed, confidence: 0.7 }] } : null;
1306
+ }
1307
+ };
1308
+
1309
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/strategies/noise-strip.mjs
1310
+ var KEEP = /* @__PURE__ */ new Set([
1311
+ ...STOPWORDS2,
1312
+ ...wordsOf(CONTEXT_PRONOUNS),
1313
+ ...wordsOf(Object.keys(VERB_TO_KIND)),
1314
+ ...wordsOf(Object.keys(ENTITY_TO_TYPE)),
1315
+ ...wordsOf(Object.keys(MODIFIER_TO_KIND)),
1316
+ ...wordsOf(Object.keys(QUALIFIERS)),
1317
+ ...wordsOf(AGGREGATE_TRIGGERS),
1318
+ ...wordsOf(LIST_TRIGGERS),
1319
+ ...wordsOf(Object.keys(SUPERLATIVE_EXTREMES)),
1320
+ ...wordsOf(Object.keys(EDGE_NOUN_TO_METRIC)),
1321
+ ...wordsOf(Object.keys(BOOLEAN_CONNECTIVES)),
1322
+ ...wordsOf(PLACEHOLDER_NOUNS),
1323
+ ...wordsOf(ANAPHORA_TRIGGERS),
1324
+ ...wordsOf(META_MEANING_VERBS),
1325
+ ...wordsOf(WHERE_MARKERS),
1326
+ ...wordsOf(MENTION_MARKERS),
1327
+ ...wordsOf(RELATIVE_PRONOUNS),
1328
+ ...wordsOf(Object.keys(CASCADE_SYNONYMS))
1329
+ ]);
1330
+ var CURATED_NOISE = /* @__PURE__ */ new Set([...wordsOf(FILLER_WORDS), ...wordsOf(CASCADE_NOISE)]);
1331
+ function stripNoise(text, nlp = null) {
1332
+ const kept = [];
1333
+ const dropped = [];
1334
+ for (const w of splitWords(text)) {
1335
+ const lc = w.toLowerCase();
1336
+ const strippable = /^[a-z]+$/.test(w) && !KEEP.has(lc) && (CURATED_NOISE.has(lc) || nlp && typeof nlp.isStopWord === "function" && nlp.isStopWord(lc));
1337
+ if (strippable) dropped.push(w);
1338
+ else kept.push(w);
1339
+ }
1340
+ return { text: kept.join(" "), dropped };
1341
+ }
1342
+ var noiseStripStrategy = {
1343
+ id: "noise-strip",
1344
+ class: "noise-stripped",
1345
+ run(text, ctx = {}) {
1346
+ if (parseAnchored(text)) return null;
1347
+ const { text: stripped, dropped } = stripNoise(text, ctx.nlp || null);
1348
+ if (!dropped.length || !stripped) return null;
1349
+ const parsed = parseAnchored(stripped) || parseKeywordSpot(stripped, ctx.nlp || null);
1350
+ if (!parsed) return null;
1351
+ return {
1352
+ strategyId: "noise-strip",
1353
+ class: "noise-stripped",
1354
+ candidates: [{ parsed, confidence: 0.75, note: `noise-stripped to "${stripped}"` }]
1355
+ };
1356
+ }
1357
+ };
1358
+
1359
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/merge.mjs
1360
+ var DEFAULT_CONFIDENCE = 0.5;
1361
+ var cmpTerm = (s) => String(s || "").trim().toLowerCase().replace(/\s+/g, " ").replace(/^commit\s+(?=[0-9a-f]{7,40}$)/, "");
1362
+ function sameParse(p, q) {
1363
+ if (p.shape !== q.shape || p.kind !== q.kind) return false;
1364
+ if (p.shape === "ask") return cmpTerm(p.subject) === cmpTerm(q.subject) && cmpTerm(p.object) === cmpTerm(q.object);
1365
+ return cmpTerm(p.object) === cmpTerm(q.object);
1366
+ }
1367
+ var APPROXIMATE_VIAS = /* @__PURE__ */ new Set(["fuzzy", "lemma", "spell"]);
1368
+ var isApproximate = (c) => APPROXIMATE_VIAS.has(c.via);
1369
+ function mergeStrategyResults(results) {
1370
+ const valid = (results || []).filter((r) => r && Array.isArray(r.candidates) && r.candidates.length);
1371
+ if (!valid.length) return null;
1372
+ let flat = [];
1373
+ for (const r of valid) {
1374
+ for (const c of r.candidates) {
1375
+ if (!c || !c.parsed) continue;
1376
+ flat.push({
1377
+ parsed: c.parsed,
1378
+ confidence: typeof c.confidence === "number" ? c.confidence : DEFAULT_CONFIDENCE,
1379
+ note: c.note || null,
1380
+ via: c.via || null,
1381
+ strategyId: r.strategyId,
1382
+ class: r.class
1383
+ });
1384
+ }
1385
+ }
1386
+ if (flat.some((c) => !isApproximate(c))) flat = flat.filter((c) => !isApproximate(c));
1387
+ if (!flat.length) return null;
1388
+ const groups = /* @__PURE__ */ new Map();
1389
+ for (const c of flat) {
1390
+ if (!groups.has(c.class)) groups.set(c.class, []);
1391
+ groups.get(c.class).push(c);
1392
+ }
1393
+ const merged = [];
1394
+ for (const [cls, cands] of groups) {
1395
+ const distinct = [];
1396
+ for (const c of cands) {
1397
+ const dup = distinct.find((d) => sameParse(d.parsed, c.parsed) && d.via === c.via);
1398
+ if (dup) {
1399
+ dup.agreed += 1;
1400
+ dup.confidence = Math.max(dup.confidence, c.confidence);
1401
+ continue;
1402
+ }
1403
+ distinct.push({ ...c, agreed: 1 });
1404
+ }
1405
+ if (distinct.length) merged.push({ class: cls, candidates: distinct });
1406
+ }
1407
+ if (!merged.length) return null;
1408
+ const top = (g) => Math.max(...g.candidates.map((c) => c.confidence));
1409
+ let winner = merged[0];
1410
+ for (const g of merged) if (top(g) > top(winner)) winner = g;
1411
+ const parsed = winner.candidates.length === 1 ? winner.candidates[0].parsed : { ambiguousParse: true, candidates: winner.candidates.map((c) => c.parsed) };
1412
+ const alternates = merged.filter((g) => g !== winner).map((g) => g.candidates[0]).filter((a) => !winner.candidates.some((w) => sameParse(w.parsed, a.parsed)));
1413
+ return { class: winner.class, parsed, winner: winner.candidates[0], alternates };
1414
+ }
1415
+
1416
+ // node-stub:node:module
1417
+ var unavailable4 = (name) => () => {
1418
+ throw new Error(name + " unavailable in the browser ask bundle");
1419
+ };
1420
+ var createRequire4 = unavailable4("createRequire");
1421
+ var readFileSync4 = unavailable4("readFileSync");
1422
+ var randomBytes4 = unavailable4("randomBytes");
1423
+
1424
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/wink-model.mjs
1425
+ var import_meta = {};
1426
+ var injected;
1427
+ var cached;
1428
+ function loadWinkModel() {
1429
+ if (cached !== void 0) return cached;
1430
+ try {
1431
+ const pair = injected ? injected() : nodeRequireWink();
1432
+ cached = pair && pair.winkNLP && pair.model ? pair : null;
1433
+ } catch {
1434
+ cached = null;
1435
+ }
1436
+ return cached;
1437
+ }
1438
+ function nodeRequireWink() {
1439
+ const require2 = createRequire4(import_meta.url);
1440
+ return {
1441
+ winkNLP: require2("wink-nlp"),
1442
+ model: require2("wink-eng-lite-web-model")
1443
+ };
1444
+ }
1445
+ function winkInstance() {
1446
+ const loaded = loadWinkModel();
1447
+ if (!loaded) return null;
1448
+ try {
1449
+ return loaded.winkNLP(loaded.model);
1450
+ } catch {
1451
+ return null;
1452
+ }
1453
+ }
1454
+
1455
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/ask-nlp.mjs
1456
+ var cached2;
1457
+ function nlpAdapter() {
1458
+ if (cached2 !== void 0) return cached2;
1459
+ try {
1460
+ const nlp = winkInstance();
1461
+ if (!nlp) {
1462
+ cached2 = null;
1463
+ return cached2;
1464
+ }
1465
+ const its = nlp.its;
1466
+ cached2 = {
1467
+ /** Lowercase lemma of a single token ("imported" -> "import"); the word
1468
+ * itself when wink has nothing better (unknown words come back as-is). */
1469
+ lemma(word) {
1470
+ const w = String(word || "");
1471
+ try {
1472
+ const out = nlp.readDoc(w).tokens().out(its.lemma);
1473
+ return String(out[0] || w).toLowerCase();
1474
+ } catch {
1475
+ return w.toLowerCase();
1476
+ }
1477
+ },
1478
+ /** True when wink's lexicon flags the word as an English stop word
1479
+ * ("anyway", "well", "also", …). Consulted by the noise-strip strategy
1480
+ * (interpret/strategies/noise-strip.mjs) as its wink tier — the strategy's
1481
+ * own KEEP set screens out the grammar's load-bearing words (which/what/
1482
+ * does/…) BEFORE this is asked, so wink flagging a question word is
1483
+ * harmless by construction. False on any surprise, never a throw. */
1484
+ isStopWord(word) {
1485
+ try {
1486
+ const out = nlp.readDoc(String(word || "")).tokens().out(its.stopWordFlag);
1487
+ return out[0] === true;
1488
+ } catch {
1489
+ return false;
1490
+ }
1491
+ },
1492
+ /** UPOS tags aligned to the CALLER's word array. wink re-tokenizes (it
1493
+ * splits "walk.mjs" into three tokens), so each input word is greedily
1494
+ * matched to the run of wink tokens that spell it and takes its FIRST
1495
+ * sub-token's tag; null per word on any surprise, never a throw. */
1496
+ posTags(words) {
1497
+ try {
1498
+ const toks = nlp.readDoc(words.join(" ")).tokens();
1499
+ const texts = toks.out();
1500
+ const tags = toks.out(its.pos);
1501
+ const out = [];
1502
+ let k = 0;
1503
+ for (const w of words) {
1504
+ if (k >= texts.length) {
1505
+ out.push(null);
1506
+ continue;
1507
+ }
1508
+ out.push(tags[k]);
1509
+ let acc = texts[k];
1510
+ k += 1;
1511
+ while (acc.length < w.length && k < texts.length) {
1512
+ acc += texts[k];
1513
+ k += 1;
1514
+ }
1515
+ }
1516
+ return out;
1517
+ } catch {
1518
+ return words.map(() => null);
1519
+ }
1520
+ }
1521
+ };
1522
+ } catch {
1523
+ cached2 = null;
1524
+ }
1525
+ return cached2;
1526
+ }
1527
+
1528
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/interpret/pipeline.mjs
1529
+ var STRATEGIES = [grammarStrategy, keywordSpotStrategy, noiseStripStrategy];
1530
+ function runStrategiesSync(text, ctx = {}, strategies = STRATEGIES) {
1531
+ const results = [];
1532
+ for (const s of strategies) {
1533
+ try {
1534
+ const r = s.run(text, ctx);
1535
+ if (r && typeof r.then !== "function") results.push(r);
1536
+ } catch {
1537
+ }
1538
+ }
1539
+ return results;
1540
+ }
1541
+
1542
+ // node_modules/@polycode-projects/the-mechanical-code-talker/src/ask.mjs
1543
+ function edgesOfKind(graph, kind) {
1544
+ const out = [];
1545
+ for (const g of graph.relations) if (relationKind(g) === kind) out.push(...g.edges);
1546
+ return out;
1547
+ }
1548
+ var SYMBOL_GRAIN_SIBLING = { calls: "callsSymbol", touches: "touchesSymbol" };
1549
+ var FINE_ENTITY_TYPES = /* @__PURE__ */ new Set(["Function", "Method", "Class", "Attribute", "GlobalVariable"]);
1550
+ var KIND_UNIONS = { uses: ["imports", "calls", "callsSymbol"] };
1551
+ var kindsFor = (kind) => KIND_UNIONS[kind] || [kind];
1552
+ var OVERFLOW_CAP = 12;
1553
+ var PLURAL_FORMS = {
1554
+ Function: ["function", "functions"],
1555
+ Method: ["method", "methods"],
1556
+ Class: ["class", "classes"],
1557
+ Module: ["module", "modules"],
1558
+ Attribute: ["attribute", "attributes"],
1559
+ GlobalVariable: ["variable", "variables"],
1560
+ Commit: ["commit", "commits"],
1561
+ // "Change" is ask-vocab.mjs's pseudo-type (a wildcard over the touch traversal's
1562
+ // results, never a node class) — it still needs noun forms for zero-hit templates.
1563
+ Change: ["change", "changes"]
1564
+ };
1565
+ function nounFor(entityType, n) {
1566
+ const [s, p] = PLURAL_FORMS[entityType] || ["result", "results"];
1567
+ return n === 1 ? s : p;
1568
+ }
1569
+ var REVERSE_MISS_VERB = { cochange: "cochanges" };
1570
+ function verbFor(kind) {
1571
+ return REVERSE_MISS_VERB[kind] || kind;
1572
+ }
1573
+ function defaultNlp() {
1574
+ return typeof nlpAdapter === "function" ? nlpAdapter() : null;
1575
+ }
1576
+ function parseQuery(query, { nlp = void 0 } = {}) {
1577
+ const adapter = nlp === void 0 ? defaultNlp() : nlp;
1578
+ const raw = String(query || "").trim().replace(/\s+/g, " ");
1579
+ if (!raw) return null;
1580
+ const text = applyNegationFrames(normalizeQuery(raw));
1581
+ if (!text) return null;
1582
+ const composite = parseComposite(text, adapter);
1583
+ if (composite) return composite;
1584
+ const merged = mergeStrategyResults(runStrategiesSync(text, { nlp: adapter, raw }));
1585
+ return merged ? merged.parsed : null;
1586
+ }
1587
+ var MAX_COMPOSE_DEPTH = 4;
1588
+ var NEST_SENTINEL = "zzinnerset";
1589
+ var PRED_LEAD_SKIP = /* @__PURE__ */ new Set(["that", "which", "who", "are", "is", "was", "were", "do", "does", "also", "still", "both", "and"]);
1590
+ var FRAME_WORDS = /* @__PURE__ */ new Set(["which", "what", "who", "list", "show", "find", "give", "me", "us", "all"]);
1591
+ var entityNoun = (w) => ENTITY_TO_TYPE[w] ? { entityType: ENTITY_TO_TYPE[w], placeholder: false } : PLACEHOLDER_NOUNS.includes(w) ? { entityType: null, placeholder: true } : null;
1592
+ var isGerundVerb = (w) => !!VERB_TO_KIND[w] && w.endsWith("ing");
1593
+ function parseSimpleClause(text, nlp) {
1594
+ return parseAnchored(text) || parseKeywordSpot(text, nlp);
1595
+ }
1596
+ function parseComposite(text, nlp) {
1597
+ const w = splitWords(text);
1598
+ 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);
1600
+ }
1601
+ function complementAst(entityType, diffAtom) {
1602
+ return {
1603
+ node: "boolean",
1604
+ entityType,
1605
+ atoms: [
1606
+ { op: "seed", kind: "set", ast: { node: "allOfClass", entityType } },
1607
+ diffAtom
1608
+ ]
1609
+ };
1610
+ }
1611
+ function parseNegation(text, nlp, depth = 0) {
1612
+ const neg = matchNegationSet(text);
1613
+ if (!neg) return null;
1614
+ const noun = entityNoun(neg.entWord);
1615
+ if (!noun || noun.placeholder || !noun.entityType) return null;
1616
+ const entityType = noun.entityType;
1617
+ if (entityType === "Change") {
1618
+ return { node: "miss", reason: `"${neg.entWord}" isn't an enumerable kind \u2014 a set complement needs a concrete kind (functions, classes, modules, \u2026)` };
1619
+ }
1620
+ const predWords = splitWords(neg.predicate);
1621
+ const predLc = predWords.map((x) => x.toLowerCase());
1622
+ if (predLc.length && predLc.every((x) => QUALIFIERS[x])) {
1623
+ return complementAst(entityType, { op: "difference", kind: "qual", filters: predLc });
1624
+ }
1625
+ const vh = findPhrase(predLc, VERB_TO_KIND);
1626
+ if (!vh) return { node: "miss", reason: "a negated set query needs a known relation verb (import, call, inherit from, test, \u2026)" };
1627
+ const objWords = predWords.filter((_, i) => (i < vh.start || i >= vh.end) && !STOPWORDS2.has(predLc[i]) && predLc[i] !== "from");
1628
+ if (!objWords.length) {
1629
+ return complementAst(entityType, { op: "difference", kind: "set", ast: { node: "existsEdge", entityType, kind: vh.kind } });
1630
+ }
1631
+ const positive = parseSetPhrase(`which ${neg.entWord} ${neg.predicate}`, nlp, depth + 1);
1632
+ if (!positive || positive.node === "miss") {
1633
+ return { node: "miss", reason: positive && positive.reason || "the negated clause didn't parse" };
1634
+ }
1635
+ return complementAst(entityType, { op: "difference", kind: "set", ast: positive });
1636
+ }
1637
+ var FWD_NEG_FRAME = /* @__PURE__ */ new Set(["what", "which", "thing", "things", "one", "ones", "stuff"]);
1638
+ function parseForwardNegation(w, lc, nlp) {
1639
+ let i = 0;
1640
+ while (i < lc.length && FWD_NEG_FRAME.has(lc[i])) i += 1;
1641
+ if (!["do", "does", "did"].includes(lc[i])) return null;
1642
+ i += 1;
1643
+ const rest = w.slice(i);
1644
+ const restLc = lc.slice(i);
1645
+ const notIdx = restLc.indexOf("not");
1646
+ if (notIdx < 0) return null;
1647
+ const vh = findPhrase(restLc, VERB_TO_KIND);
1648
+ if (!vh) return null;
1649
+ const subjTokens = rest.filter((_, j) => j !== notIdx && (j < vh.start || j >= vh.end) && restLc[j] !== "from" && !STOPWORDS2.has(restLc[j]));
1650
+ const subjectTerm = subjTokens.join(" ").trim();
1651
+ if (!subjectTerm) return null;
1652
+ return { node: "forwardComplement", kind: vh.kind, subjectTerm };
1653
+ }
1654
+ function kindObjectClass(graph, kind) {
1655
+ const classes = /* @__PURE__ */ new Set();
1656
+ for (const k of kindsFor(kind)) {
1657
+ for (const e of edgesOfKind(graph, k)) {
1658
+ const o = graph.byId.get(e.object);
1659
+ if (o && o.class) classes.add(o.class);
1660
+ }
1661
+ }
1662
+ return classes.size === 1 ? [...classes][0] : null;
1663
+ }
1664
+ function parseSetPhrase(text, nlp, depth) {
1665
+ if (depth > MAX_COMPOSE_DEPTH) return { node: "miss", reason: "too deep to resolve" };
1666
+ const negated = parseNegation(text, nlp, depth);
1667
+ if (negated) return negated;
1668
+ const w = splitWords(text);
1669
+ const lc = w.map((x) => x.toLowerCase());
1670
+ const nested = parseNested(w, lc, nlp, depth);
1671
+ if (nested) return nested;
1672
+ const rel = parseRelationalOrQualified(w, lc, nlp, depth);
1673
+ if (rel) return rel;
1674
+ const clause = parseSimpleClause(text, nlp);
1675
+ if (clause) return { node: "clause", clause };
1676
+ return null;
1677
+ }
1678
+ function parseNested(w, lc, nlp, depth) {
1679
+ for (let r = 1; r < lc.length; r += 1) {
1680
+ if (!RELATIVE_PRONOUNS.includes(lc[r])) continue;
1681
+ if (r + 1 >= lc.length) continue;
1682
+ const noun = entityNoun(lc[r - 1]);
1683
+ if (!noun) continue;
1684
+ const head = w.slice(0, r - 1);
1685
+ if (!head.length) continue;
1686
+ const outer = parseSimpleClause([...head, NEST_SENTINEL].join(" "), nlp);
1687
+ if (!outer || outer.shape !== "reverse" && outer.shape !== "forward") continue;
1688
+ if (outer.modifier && outer.modifier !== "direct") continue;
1689
+ const innerText = `which ${lc[r - 1]} ${w.slice(r + 1).join(" ")}`;
1690
+ const inner = parseSetPhrase(innerText, nlp, depth + 1);
1691
+ if (!inner || inner.node === "miss") return inner ? { node: "miss", reason: inner.reason || "inner clause didn't parse" } : { node: "miss", reason: "inner clause didn't parse" };
1692
+ return { node: outer.shape === "reverse" ? "reverseSet" : "forwardSet", kind: outer.kind, entityType: outer.entityType, inner };
1693
+ }
1694
+ return null;
1695
+ }
1696
+ function parseAnaphora(w, lc, nlp) {
1697
+ let p = -1;
1698
+ let viaOf = false;
1699
+ for (let i = 1; i < lc.length; i += 1) {
1700
+ if (!ANAPHORA_TRIGGERS.includes(lc[i])) continue;
1701
+ if (lc[i - 1] === "of") {
1702
+ p = i;
1703
+ viaOf = true;
1704
+ break;
1705
+ }
1706
+ const headSoFar = lc.slice(0, i).join(" ");
1707
+ if (i === lc.length - 1 && (AGGREGATE_TRIGGERS.includes(headSoFar) || LIST_TRIGGERS.includes(headSoFar))) {
1708
+ p = i;
1709
+ break;
1710
+ }
1711
+ }
1712
+ if (p < 0) return null;
1713
+ const head = (viaOf ? lc.slice(0, p - 1) : lc.slice(0, p)).join(" ");
1714
+ const mode = AGGREGATE_TRIGGERS.includes(head) || /^(how many|how much|count|number|quantity|total)\b/.test(head) ? "count" : "list";
1715
+ const filter = parsePredicateFilter(w.slice(p + 1), nlp);
1716
+ if (filter === void 0) return { node: "miss", reason: "the follow-up filter didn't parse" };
1717
+ return { node: "anaphora", mode, filter };
1718
+ }
1719
+ function parsePredicateFilter(words, nlp) {
1720
+ let i = 0;
1721
+ const lc = words.map((x) => x.toLowerCase());
1722
+ while (i < lc.length && PRED_LEAD_SKIP.has(lc[i])) i += 1;
1723
+ const rest = words.slice(i);
1724
+ const restLc = lc.slice(i);
1725
+ if (!rest.length) return { type: "all" };
1726
+ if (restLc.every((x) => QUALIFIERS[x])) return { type: "qual", filters: restLc };
1727
+ const clause = parseSimpleClause(`what ${rest.join(" ")}`, nlp);
1728
+ if (clause && (clause.shape === "reverse" || clause.shape === "forward") && clause.object) {
1729
+ return { type: "clause", clause };
1730
+ }
1731
+ return void 0;
1732
+ }
1733
+ var AGG_TAIL_FILLER = /* @__PURE__ */ new Set([
1734
+ "total",
1735
+ "altogether",
1736
+ "overall",
1737
+ "exist",
1738
+ "exists",
1739
+ "existing",
1740
+ "present",
1741
+ "here",
1742
+ "now",
1743
+ "currently",
1744
+ "graph",
1745
+ "index",
1746
+ "codebase",
1747
+ "repo",
1748
+ "repository"
1749
+ ]);
1750
+ function parseAggregate(w, lc, nlp) {
1751
+ const trig = AGGREGATE_TRIGGERS.find((t) => lc.slice(0, t.split(" ").length).join(" ") === t);
1752
+ if (!trig) return null;
1753
+ let i = trig.split(" ").length;
1754
+ while (i < lc.length && (lc[i] === "the" || lc[i] === "a" || lc[i] === "all")) i += 1;
1755
+ const quals = [];
1756
+ while (i < lc.length && QUALIFIERS[lc[i]]) {
1757
+ quals.push(lc[i]);
1758
+ i += 1;
1759
+ }
1760
+ const noun = i < lc.length ? entityNoun(lc[i]) : null;
1761
+ if (!noun) return { node: "miss", reason: "count needs a known entity kind (functions, classes, modules, \u2026)" };
1762
+ const entWord = lc[i];
1763
+ i += 1;
1764
+ const tail = w.slice(i);
1765
+ const tailMeaningful = lc.slice(i).some((t) => !STOPWORDS2.has(t) && !AGG_TAIL_FILLER.has(t));
1766
+ let base;
1767
+ if (tailMeaningful) {
1768
+ const setAst = parseSetPhrase(`which ${entWord} ${tail.join(" ")}`, nlp, 1);
1769
+ if (!setAst || setAst.node === "miss") return { node: "miss", reason: "the count restrictor didn't parse" };
1770
+ base = setAst;
1771
+ } else {
1772
+ base = { node: "allOfClass", entityType: noun.entityType };
1773
+ }
1774
+ if (quals.length) base = { node: "qualifier", filters: quals, inner: base };
1775
+ return { node: "count", entityType: noun.entityType, base };
1776
+ }
1777
+ var LIST_SKIP = /* @__PURE__ */ new Set(["the", "a", "an", "all", "me", "us"]);
1778
+ var LIST_TRIGGERS_SORTED = [...LIST_TRIGGERS].sort((a, b) => b.split(" ").length - a.split(" ").length);
1779
+ var LISTABLE_KINDS = "functions, classes, methods, modules, attributes, variables, or commits";
1780
+ function parseList(w, lc, nlp, depth) {
1781
+ let i = 0;
1782
+ let interrogative = false;
1783
+ let matched = null;
1784
+ for (const t of LIST_TRIGGERS_SORTED) {
1785
+ const tw = t.split(" ");
1786
+ if (lc.slice(0, tw.length).join(" ") === t) {
1787
+ matched = t;
1788
+ i = tw.length;
1789
+ break;
1790
+ }
1791
+ }
1792
+ if (!matched) {
1793
+ if (lc[0] === "what" || lc[0] === "which") {
1794
+ interrogative = true;
1795
+ i = 1;
1796
+ } else return null;
1797
+ }
1798
+ while (i < lc.length && LIST_SKIP.has(lc[i])) i += 1;
1799
+ const quals = [];
1800
+ while (i < lc.length && QUALIFIERS[lc[i]]) {
1801
+ quals.push(lc[i]);
1802
+ i += 1;
1803
+ }
1804
+ const noun = i < lc.length ? entityNoun(lc[i]) : null;
1805
+ if (!noun || noun.placeholder || noun.entityType === "Change") {
1806
+ if (!interrogative && i < lc.length && i === lc.length - 1 && /^[a-z]+$/.test(lc[i]) && !VERB_TO_KIND[lc[i]] && !PLACEHOLDER_NOUNS.includes(lc[i])) {
1807
+ return { node: "miss", reason: `"${lc[i]}" isn't a listable kind \u2014 try ${LISTABLE_KINDS}` };
1808
+ }
1809
+ return null;
1810
+ }
1811
+ const entityType = noun.entityType;
1812
+ const entWord = lc[i];
1813
+ i += 1;
1814
+ const tail = w.slice(i);
1815
+ const tailMeaningful = lc.slice(i).some((t) => !STOPWORDS2.has(t) && !AGG_TAIL_FILLER.has(t));
1816
+ if (interrogative && (tailMeaningful || tail.length === 0)) return null;
1817
+ let base;
1818
+ let scoped = false;
1819
+ if (tailMeaningful) {
1820
+ const setAst = parseSetPhrase(`which ${[...quals, entWord, ...tail].join(" ")}`, nlp, (depth || 0) + 1);
1821
+ if (!setAst || setAst.node === "miss") return { node: "miss", reason: setAst && setAst.reason || "the list filter didn't parse" };
1822
+ base = setAst;
1823
+ scoped = true;
1824
+ } else {
1825
+ base = { node: "allOfClass", entityType };
1826
+ if (quals.length) base = { node: "qualifier", filters: quals, inner: base };
1827
+ }
1828
+ return { node: "list", entityType, base, scoped };
1829
+ }
1830
+ function parseSuperlative(w, lc, nlp) {
1831
+ let ext = null;
1832
+ let extIdx = -1;
1833
+ for (let i = 0; i < lc.length; i += 1) {
1834
+ const two = lc.slice(i, i + 2).join(" ");
1835
+ if (SUPERLATIVE_EXTREMES[two]) {
1836
+ ext = SUPERLATIVE_EXTREMES[two];
1837
+ extIdx = i;
1838
+ break;
1839
+ }
1840
+ if (SUPERLATIVE_EXTREMES[lc[i]]) {
1841
+ ext = SUPERLATIVE_EXTREMES[lc[i]];
1842
+ extIdx = i;
1843
+ break;
1844
+ }
1845
+ }
1846
+ if (!ext) return null;
1847
+ let entityType;
1848
+ let entWord = null;
1849
+ for (const x of lc) {
1850
+ const n = entityNoun(x);
1851
+ if (n && !n.placeholder) {
1852
+ entityType = n.entityType;
1853
+ entWord = x;
1854
+ break;
1855
+ }
1856
+ }
1857
+ if (!entWord) return { node: "miss", reason: "a superlative needs an entity kind (module, class, function, \u2026)" };
1858
+ let metric = null;
1859
+ let metricNoun = null;
1860
+ for (let i = extIdx; i < lc.length; i += 1) {
1861
+ if (EDGE_NOUN_TO_METRIC[lc[i]]) {
1862
+ metric = EDGE_NOUN_TO_METRIC[lc[i]];
1863
+ metricNoun = lc[i];
1864
+ break;
1865
+ }
1866
+ }
1867
+ const connectivity = lc.includes("connected") || lc.slice(extIdx, extIdx + 2).join(" ") === "most connected" || ["largest", "biggest", "smallest"].includes(lc[extIdx]);
1868
+ if (!metric) {
1869
+ if (connectivity) {
1870
+ metric = EDGE_NOUN_TO_METRIC.connections;
1871
+ metricNoun = "connections";
1872
+ } else return { node: "miss", reason: "name what to rank by (imports, callers, methods, tests, or connections)" };
1873
+ }
1874
+ return { node: "superlative", entityType, metric, metricNoun, extreme: ext };
1875
+ }
1876
+ function parseRelationalOrQualified(w, lc, nlp, depth) {
1877
+ let i = 0;
1878
+ while (i < lc.length && FRAME_WORDS.has(lc[i])) i += 1;
1879
+ const framed = i > 0;
1880
+ const quals = [];
1881
+ while (i < lc.length && QUALIFIERS[lc[i]]) {
1882
+ quals.push(lc[i]);
1883
+ i += 1;
1884
+ }
1885
+ const noun = i < lc.length ? entityNoun(lc[i]) : null;
1886
+ if (!noun) {
1887
+ if ((framed || quals.length) && i + 1 < lc.length && /^[a-z]+$/.test(lc[i]) && !VERB_TO_KIND[lc[i]] && !STOPWORDS2.has(lc[i]) && entityNoun(lc[i + 1])) {
1888
+ return { node: "miss", reason: `unknown qualifier "${lc[i]}" \u2014 supported: ${Object.keys(QUALIFIERS).join(", ")}` };
1889
+ }
1890
+ return null;
1891
+ }
1892
+ const entityType = noun.entityType;
1893
+ const entWord = lc[i];
1894
+ i += 1;
1895
+ let predLc = lc.slice(i);
1896
+ let predWords = w.slice(i);
1897
+ let relFlag = false;
1898
+ if (predLc.length && RELATIVE_PRONOUNS.includes(predLc[0])) {
1899
+ relFlag = true;
1900
+ predLc = predLc.slice(1);
1901
+ predWords = predWords.slice(1);
1902
+ }
1903
+ const membershipLed = predLc[0] === "of" || predLc[0] === "in";
1904
+ const gerundLed = predLc.length > 0 && isGerundVerb(predLc[0]);
1905
+ if (!(quals.length || relFlag || membershipLed || gerundLed)) return null;
1906
+ if (!predWords.length) {
1907
+ let base = { node: "allOfClass", entityType };
1908
+ if (!quals.length) return { node: "miss", reason: "nothing to filter or traverse" };
1909
+ return { node: "qualifier", filters: quals, inner: base };
1910
+ }
1911
+ const subjPrefix = noun.placeholder ? "what" : `which ${entWord}`;
1912
+ const { branches, ops } = splitBoolean(predLc, predWords);
1913
+ let prevVerb = null;
1914
+ const atoms = [];
1915
+ for (let b = 0; b < branches.length; b += 1) {
1916
+ const bw = branches[b];
1917
+ const blc = bw.map((x) => x.toLowerCase());
1918
+ const op = b === 0 ? "seed" : ops[b - 1];
1919
+ if (bw.length && blc.every((x) => QUALIFIERS[x])) {
1920
+ atoms.push({ op, kind: "qual", filters: blc });
1921
+ continue;
1922
+ }
1923
+ if (blc[0] === "of" || blc[0] === "in") {
1924
+ atoms.push({ op, kind: "set", ast: { node: "membership", entityType, term: bw.slice(1).join(" ") } });
1925
+ continue;
1926
+ }
1927
+ let phrase = bw;
1928
+ const vh = findPhrase(blc, VERB_TO_KIND);
1929
+ if (vh) prevVerb = bw.slice(vh.start, vh.end);
1930
+ else if (prevVerb) phrase = [...prevVerb, ...bw];
1931
+ const ast = parseBranchAst(`${subjPrefix} ${phrase.join(" ")}`, nlp, depth + 1);
1932
+ if (!ast || ast.node === "miss") return { node: "miss", reason: ast && ast.reason || "a clause in the combination didn't parse" };
1933
+ atoms.push({ op, kind: "set", ast });
1934
+ }
1935
+ if (atoms[0].kind !== "set") return { node: "miss", reason: "start with a clause, then combine with and/or/but-not" };
1936
+ let result;
1937
+ if (atoms.length === 1) {
1938
+ result = atoms[0].ast;
1939
+ } else {
1940
+ result = { node: "boolean", entityType, atoms };
1941
+ }
1942
+ if (quals.length) result = { node: "qualifier", filters: quals, inner: result };
1943
+ return result;
1944
+ }
1945
+ function parseBranchAst(text, nlp, depth) {
1946
+ if (depth > MAX_COMPOSE_DEPTH) return { node: "miss", reason: "too deep to resolve" };
1947
+ const w = splitWords(text);
1948
+ const lc = w.map((x) => x.toLowerCase());
1949
+ const nested = parseNested(w, lc, nlp, depth);
1950
+ if (nested) return nested;
1951
+ const clause = parseSimpleClause(text, nlp);
1952
+ return clause ? { node: "clause", clause } : null;
1953
+ }
1954
+ function splitBoolean(predLc, predWords) {
1955
+ const conns = Object.keys(BOOLEAN_CONNECTIVES).sort((a, z) => z.split(" ").length - a.split(" ").length);
1956
+ const branches = [];
1957
+ const ops = [];
1958
+ let start = 0;
1959
+ let i = 0;
1960
+ while (i < predLc.length) {
1961
+ let hit = null;
1962
+ for (const c of conns) {
1963
+ const cw = c.split(" ");
1964
+ if (predLc.slice(i, i + cw.length).join(" ") === c) {
1965
+ hit = { c, len: cw.length };
1966
+ break;
1967
+ }
1968
+ }
1969
+ if (hit && i > start) {
1970
+ branches.push(predWords.slice(start, i));
1971
+ ops.push(BOOLEAN_CONNECTIVES[hit.c]);
1972
+ i += hit.len;
1973
+ start = i;
1974
+ } else if (hit) {
1975
+ i += hit.len;
1976
+ start = i;
1977
+ } else i += 1;
1978
+ }
1979
+ branches.push(predWords.slice(start));
1980
+ return { branches, ops };
1981
+ }
1982
+ function reverseOverSet(graph, kind, entityType, objectIds) {
1983
+ const symbolKind = SYMBOL_GRAIN_SIBLING[kind];
1984
+ if (symbolKind && FINE_ENTITY_TYPES.has(entityType)) {
1985
+ const edges2 = edgesOfKind(graph, symbolKind).filter((e) => objectIds.has(e.object));
1986
+ return uniqueById(edges2.map((e) => graph.byId.get(e.subject)).filter((s) => s && s.class === entityType));
1987
+ }
1988
+ const edges = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => objectIds.has(e.object));
1989
+ const subjects = uniqueById(edges.map((e) => graph.byId.get(e.subject)).filter(Boolean));
1990
+ if (!entityType || entityType === "Change") return subjects;
1991
+ const direct = subjects.filter((s) => s.class === entityType);
1992
+ if (direct.length) return direct;
1993
+ if (entityType !== "Module" && subjects.some((s) => s.class === "Module")) {
1994
+ return refineToEntities(graph, new Set(subjects.filter((s) => s.class === "Module").map((s) => s.id)), entityType);
1995
+ }
1996
+ return [];
1997
+ }
1998
+ function forwardOverSet(graph, kind, subjectIds) {
1999
+ const edges = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => subjectIds.has(e.subject));
2000
+ return uniqueById(edges.map((e) => graph.byId.get(e.object)).filter(Boolean));
2001
+ }
2002
+ function uniqueById(inds) {
2003
+ const seen = /* @__PURE__ */ new Set();
2004
+ const out = [];
2005
+ for (const x of inds) if (x && !seen.has(x.id)) {
2006
+ seen.add(x.id);
2007
+ out.push(x);
2008
+ }
2009
+ return out;
2010
+ }
2011
+ var qualCache = /* @__PURE__ */ new WeakMap();
2012
+ function qualSets(graph) {
2013
+ let c = qualCache.get(graph);
2014
+ if (c) return c;
2015
+ const exported = /* @__PURE__ */ new Set();
2016
+ for (const e of edgesOfKind(graph, "reexports")) {
2017
+ exported.add(String(e.object).toLowerCase());
2018
+ const ind = graph.byId.get(e.object);
2019
+ if (ind) exported.add(String(ind.label).toLowerCase());
2020
+ }
2021
+ const testedModules = new Set(edgesOfKind(graph, "tests").map((e) => e.object));
2022
+ const moduleOfSymbol = /* @__PURE__ */ new Map();
2023
+ for (const e of edgesOfKind(graph, "defines")) moduleOfSymbol.set(e.object, e.subject);
2024
+ c = { exported, testedModules, moduleOfSymbol };
2025
+ qualCache.set(graph, c);
2026
+ return c;
2027
+ }
2028
+ function moduleIdOf2(graph, ind) {
2029
+ if (!ind) return null;
2030
+ if (ind.class === "Module") return ind.id;
2031
+ return qualSets(graph).moduleOfSymbol.get(ind.id) || null;
2032
+ }
2033
+ function qualHolds(graph, ind, spec) {
2034
+ if (!spec) return false;
2035
+ switch (spec.via) {
2036
+ case "visibility": {
2037
+ const v = String((ind.attributes || []).find((a) => a.key === "visibility")?.value || "public").toLowerCase();
2038
+ return v === spec.value;
2039
+ }
2040
+ case "attr":
2041
+ return !!(ind.attributes || []).find((a) => a.key === spec.attr)?.value;
2042
+ case "exported": {
2043
+ const ex = qualSets(graph).exported;
2044
+ return ex.has(String(ind.label).toLowerCase()) || ex.has(String(ind.id).toLowerCase());
2045
+ }
2046
+ case "tested": {
2047
+ const mid = moduleIdOf2(graph, ind);
2048
+ return (!!mid && qualSets(graph).testedModules.has(mid)) === spec.value;
2049
+ }
2050
+ default:
2051
+ return false;
2052
+ }
2053
+ }
2054
+ function evalSet(graph, ast, opts) {
2055
+ switch (ast.node) {
2056
+ case "clause":
2057
+ return traverse(graph, ast.clause, opts).matches || [];
2058
+ case "allOfClass":
2059
+ return graph.individuals.filter((i) => i.class === ast.entityType);
2060
+ // the SUBJECTS that have ANY edge of a kind (the existential "modules that import
2061
+ // anything") — the positive set an existential negation ("do not import anything")
2062
+ // differences off allOfClass to yield "modules that import nothing".
2063
+ case "existsEdge": {
2064
+ const subs = new Set(kindsFor(ast.kind).flatMap((k) => edgesOfKind(graph, k)).map((e) => e.subject));
2065
+ return graph.individuals.filter((i) => subs.has(i.id) && (!ast.entityType || i.class === ast.entityType));
2066
+ }
2067
+ // forward complement: the verb's object-grain universe MINUS what the (late-resolved,
2068
+ // focus-bindable) subject reaches via that verb — "what doesn't it import".
2069
+ case "forwardComplement": {
2070
+ const r = resolveTermOrContext(graph, ast.subjectTerm, opts && opts.contextId);
2071
+ if (!r.match) return [];
2072
+ const universeType = kindObjectClass(graph, ast.kind);
2073
+ if (!universeType) return [];
2074
+ const positive = new Set(forwardOverSet(graph, ast.kind, /* @__PURE__ */ new Set([r.match.id])).map((x) => x.id));
2075
+ return graph.individuals.filter((i) => i.class === universeType && !positive.has(i.id));
2076
+ }
2077
+ case "reverseSet": {
2078
+ const ids = new Set(evalSet(graph, ast.inner, opts).map((i) => i.id));
2079
+ return reverseOverSet(graph, ast.kind, ast.entityType, ids);
2080
+ }
2081
+ case "forwardSet": {
2082
+ const ids = new Set(evalSet(graph, ast.inner, opts).map((i) => i.id));
2083
+ return forwardOverSet(graph, ast.kind, ids);
2084
+ }
2085
+ case "membership": {
2086
+ const r = resolveObject(graph, ast.term);
2087
+ if (!r.match) return [];
2088
+ const ids = /* @__PURE__ */ new Set([r.match.id]);
2089
+ const objs = uniqueById(MEMBERSHIP_KINDS.flatMap((k) => forwardOverSet(graph, k, ids)));
2090
+ return ast.entityType ? objs.filter((o) => o.class === ast.entityType) : objs;
2091
+ }
2092
+ case "qualifier": {
2093
+ const base = evalSet(graph, ast.inner, opts);
2094
+ return base.filter((ind) => ast.filters.every((f) => qualHolds(graph, ind, QUALIFIERS[f])));
2095
+ }
2096
+ case "boolean":
2097
+ return evalBoolean(graph, ast, opts);
2098
+ case "anaphora":
2099
+ return evalAnaphora(graph, ast, opts).matches;
2100
+ default:
2101
+ return [];
2102
+ }
2103
+ }
2104
+ function evalBoolean(graph, ast, opts) {
2105
+ let acc = [];
2106
+ for (const atom of ast.atoms) {
2107
+ if (atom.op === "seed") {
2108
+ acc = evalSet(graph, atom.ast, opts);
2109
+ continue;
2110
+ }
2111
+ if (atom.kind === "qual") {
2112
+ const holds = (ind) => atom.filters.every((f) => qualHolds(graph, ind, QUALIFIERS[f]));
2113
+ acc = atom.op === "difference" ? acc.filter((i) => !holds(i)) : acc.filter((i) => holds(i));
2114
+ continue;
2115
+ }
2116
+ const oids = new Set(evalSet(graph, atom.ast, opts).map((i) => i.id));
2117
+ if (atom.op === "intersection") acc = acc.filter((i) => oids.has(i.id));
2118
+ else if (atom.op === "difference") acc = acc.filter((i) => !oids.has(i.id));
2119
+ else if (atom.op === "union") {
2120
+ const seen = new Set(acc.map((i) => i.id));
2121
+ for (const other of evalSet(graph, atom.ast, opts)) if (!seen.has(other.id)) {
2122
+ seen.add(other.id);
2123
+ acc.push(other);
2124
+ }
2125
+ }
2126
+ }
2127
+ return acc;
2128
+ }
2129
+ function evalAnaphora(graph, ast, opts) {
2130
+ const prev = opts && opts.prev;
2131
+ if (!Array.isArray(prev) || !prev.length) return { compositeMiss: true, reason: "no-prev", matches: [] };
2132
+ let items = prev.map((id) => graph.byId.get(id)).filter(Boolean);
2133
+ const f = ast.filter;
2134
+ if (f && f.type === "qual") {
2135
+ items = items.filter((ind) => f.filters.every((q) => qualHolds(graph, ind, QUALIFIERS[q])));
2136
+ } else if (f && f.type === "clause") {
2137
+ const r = resolveObject(graph, f.clause.object);
2138
+ if (!r.match) items = [];
2139
+ else {
2140
+ const sib = SYMBOL_GRAIN_SIBLING[f.clause.kind];
2141
+ const kinds = [...kindsFor(f.clause.kind), ...sib ? [sib] : []];
2142
+ const ok = new Set(kinds.flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.object === r.match.id).map((e) => e.subject));
2143
+ items = items.filter((ind) => ok.has(ind.id));
2144
+ }
2145
+ }
2146
+ const common = items.length && items.every((x) => x.class === items[0].class) ? items[0].class : null;
2147
+ if (ast.mode === "count") return { compositeKind: "count", count: items.length, entityType: common, matches: [] };
2148
+ return { compositeKind: "set", matches: items, entityType: common };
2149
+ }
2150
+ var DEGREE_KINDS = ["imports", "calls", "callsSymbol", "inherits", "contains", "tests"];
2151
+ function degreeMetric(graph, ind, metric) {
2152
+ const kinds = metric.kind === "*" ? DEGREE_KINDS : [metric.kind, ...metric.sibling ? [metric.sibling] : []];
2153
+ let n = 0;
2154
+ for (const k of kinds) for (const e of edgesOfKind(graph, k)) {
2155
+ const out = e.subject === ind.id;
2156
+ const inc = e.object === ind.id;
2157
+ if (metric.dir === "out" && out) {
2158
+ if (metric.filter) {
2159
+ const o = graph.byId.get(e.object);
2160
+ if (!o || o.class !== metric.filter) continue;
2161
+ }
2162
+ n += 1;
2163
+ } else if (metric.dir === "in" && inc) n += 1;
2164
+ else if (metric.dir === "both" && (out || inc)) n += 1;
2165
+ }
2166
+ return n;
2167
+ }
2168
+ function evalSuperlative(graph, ast) {
2169
+ const pool = graph.individuals.filter((i) => i.class === ast.entityType);
2170
+ 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);
2171
+ if (!scored.length) return { compositeKind: "superlative", entityType: ast.entityType, matches: [] };
2172
+ const best = scored[0].score;
2173
+ const winners = scored.filter((s) => s.score === best).map((s) => s.ind);
2174
+ return { compositeKind: "superlative", entityType: ast.entityType, metricNoun: ast.metricNoun, extreme: ast.extreme, score: best, matches: winners };
2175
+ }
2176
+ function evalComposite(graph, ast, opts = {}) {
2177
+ if (ast.node === "miss") return { compositeMiss: true, reason: ast.reason || null, matches: [] };
2178
+ if (ast.node === "count") return { compositeKind: "count", count: evalSet(graph, ast.base, opts).length, entityType: ast.entityType, matches: [] };
2179
+ if (ast.node === "list") return { compositeKind: "list", matches: evalSet(graph, ast.base, opts), entityType: ast.entityType, scoped: ast.scoped };
2180
+ if (ast.node === "superlative") return evalSuperlative(graph, ast);
2181
+ if (ast.node === "anaphora") return evalAnaphora(graph, ast, opts);
2182
+ return { compositeKind: "set", matches: evalSet(graph, ast, opts), entityType: ast.entityType || null };
2183
+ }
2184
+ 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` : "");
2185
+ function compositionalHint() {
2186
+ 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"';
2187
+ }
2188
+ function renderComposite(parsed, result) {
2189
+ if (result.compositeMiss) {
2190
+ if (result.reason === "no-prev") {
2191
+ return { content: `"those"/"them" needs a previous answer to refer to \u2014 ask a listing question first, then follow up.`, miss: true, ambiguous: false };
2192
+ }
2193
+ return { content: `couldn't compile this compositional question${result.reason ? ` (${result.reason})` : ""}. ${compositionalHint()}.`, miss: true, ambiguous: false };
2194
+ }
2195
+ if (result.compositeKind === "count") {
2196
+ const noun = result.entityType ? nounFor(result.entityType, result.count) : result.count === 1 ? "result" : "results";
2197
+ return { content: `${result.count} ${noun}.`, miss: false, ambiguous: false, matches: [] };
2198
+ }
2199
+ if (result.compositeKind === "list") {
2200
+ if (!result.matches.length) {
2201
+ return { content: `no ${nounFor(result.entityType, 2)} in this index.`, miss: true, ambiguous: false, matches: [] };
2202
+ }
2203
+ const scopeable = !["Module", "Commit"].includes(result.entityType);
2204
+ const hint = !result.scoped && scopeable && result.matches.length > OVERFLOW_CAP ? ` \u2014 narrow with "${nounFor(result.entityType, 2)} in <module>"` : "";
2205
+ return { content: `${compositeList(result.matches)}${hint}.`, miss: false, ambiguous: false, matches: result.matches };
2206
+ }
2207
+ if (result.compositeKind === "superlative") {
2208
+ if (!result.matches.length) return { content: `no ${nounFor(result.entityType, 2)} to rank in this index.`, miss: true, ambiguous: false };
2209
+ const lead = result.extreme === "most" ? "the most" : "the fewest";
2210
+ const tie = result.matches.length > 1 ? ` (${result.matches.length}-way tie)` : "";
2211
+ return {
2212
+ content: `${compositeList(result.matches)} \u2014 ${lead} ${result.metricNoun} (${result.score})${tie}.`,
2213
+ miss: false,
2214
+ ambiguous: false,
2215
+ matches: result.matches
2216
+ };
2217
+ }
2218
+ if (!result.matches.length) {
2219
+ return { content: `nothing in the index matches that${result.entityType ? ` (${nounFor(result.entityType, 2)})` : ""}.`, miss: true, ambiguous: false, matches: [] };
2220
+ }
2221
+ return { content: `${compositeList(result.matches)}.`, miss: false, ambiguous: false, matches: result.matches };
2222
+ }
2223
+ function rephraseHint() {
2224
+ return `"which <functions|classes|modules> <imports|calls|uses|inherits from|tests|touched> <name>" or "what does <name> <import|call|export>" or "what uses <name>" or "where is <name> defined" / "where is <name> mentioned" or "when did <name> change" or "which changes touch commit <sha>"/"what did commit <sha> touch" (a commit's own changes) or plainly "what calls this" (about a selected node) or "what does <term> mean"/"what is a <ClassName>" (about the graph's own vocabulary). ` + compositionalHint();
2225
+ }
2226
+ function componentSet(s) {
2227
+ return new Set(String(s).toLowerCase().split(/[^a-z0-9]+/).filter(Boolean));
2228
+ }
2229
+ function resolveObject(graph, term) {
2230
+ const t = String(term || "").trim();
2231
+ if (!t) return { match: null, candidates: [], tier: null, ambiguous: false };
2232
+ const tLc = t.toLowerCase();
2233
+ const pool = graph.individuals;
2234
+ const shaTerm = tLc.match(/^(commit[:\s])?([0-9a-f]{7,40})$/);
2235
+ if (shaTerm) {
2236
+ const sha = shaTerm[2];
2237
+ const hits = pool.filter((i) => i.class === "Commit" && (String(i.id).toLowerCase().startsWith(`commit:${sha}`) || String(i.label).toLowerCase().startsWith(sha)));
2238
+ if (hits.length === 1) return { match: hits[0], candidates: [], tier: 1, ambiguous: false };
2239
+ if (hits.length > 1) return { match: hits[0], candidates: hits.slice(1, 5), tier: 1, ambiguous: true };
2240
+ if (shaTerm[1]) return { match: null, candidates: [], tier: null, ambiguous: false };
2241
+ }
2242
+ const exact = pool.find((i) => String(i.label).toLowerCase() === tLc || String(i.id).toLowerCase() === tLc);
2243
+ if (exact) return { match: exact, candidates: [], tier: 1, ambiguous: false };
2244
+ const extLc = `ext:${tLc}`;
2245
+ let extId = null;
2246
+ outer: for (const g of graph.relations) {
2247
+ for (const e of g.edges) {
2248
+ if (String(e.object).toLowerCase() === extLc) {
2249
+ extId = e.object;
2250
+ break outer;
2251
+ }
2252
+ }
2253
+ }
2254
+ if (extId) return { match: { id: extId, label: t, class: null }, candidates: [], tier: 2, ambiguous: false };
2255
+ const scored = [];
2256
+ const dotted = !tLc.includes("/") && /^[\w$]+(\.[\w$]+)+$/.test(tLc);
2257
+ if (dotted) {
2258
+ const lastSeg = tLc.split(".").pop();
2259
+ for (const m of pool) {
2260
+ const label = String(m.label || "").toLowerCase();
2261
+ if (m.class === "Module") {
2262
+ if (label.split("/").pop() === tLc) scored.push({ ind: m, score: 1e3 - Math.abs(label.length - tLc.length) });
2263
+ } else if (label.includes(tLc)) {
2264
+ scored.push({ ind: m, score: 2e3 - Math.abs(label.length - tLc.length) });
2265
+ } else if (label.endsWith(`.${lastSeg}`)) {
2266
+ scored.push({ ind: m, score: 1500 - Math.abs(label.length - tLc.length) });
2267
+ }
2268
+ }
2269
+ } else {
2270
+ const termComps = componentSet(t);
2271
+ for (const m of pool) {
2272
+ const label = String(m.label || "").toLowerCase();
2273
+ if (label.includes(tLc)) {
2274
+ scored.push({ ind: m, score: 1e3 - Math.abs(label.length - tLc.length) });
2275
+ continue;
2276
+ }
2277
+ const overlap = [...termComps].filter((c) => componentSet(m.label).has(c)).length;
2278
+ if (overlap > 0) scored.push({ ind: m, score: overlap * 10 });
2279
+ }
2280
+ }
2281
+ scored.sort((a, b) => b.score - a.score);
2282
+ if (scored.length) {
2283
+ const [best, ...rest] = scored;
2284
+ const tied = rest.filter((x) => x.score === best.score);
2285
+ return {
2286
+ match: best.ind,
2287
+ candidates: rest.slice(0, 4).map((x) => x.ind),
2288
+ tier: 3,
2289
+ ambiguous: tied.length > 0
2290
+ };
2291
+ }
2292
+ let proseResult = null;
2293
+ const proseHits = !dotted && typeof lookupByProseTokens === "function" ? lookupByProseTokens(graph.proseIndex, t) : [];
2294
+ if (proseHits.length) {
2295
+ const [best, ...rest] = proseHits;
2296
+ const bestInd = graph.byId.get(best.id);
2297
+ if (bestInd) {
2298
+ const tied = rest.filter((h) => h.score === best.score);
2299
+ proseResult = {
2300
+ match: bestInd,
2301
+ candidates: rest.slice(0, 4).map((h) => graph.byId.get(h.id)).filter(Boolean),
2302
+ tier: 4,
2303
+ ambiguous: tied.length > 0,
2304
+ matchedVia: "prose"
2305
+ };
2306
+ if (!proseResult.ambiguous && best.via !== "spell") return proseResult;
2307
+ }
2308
+ }
2309
+ if (!shaTerm && tLc.length >= 4) {
2310
+ const bound = fuzzyBound(tLc);
2311
+ let best = bound + 1;
2312
+ let hits = [];
2313
+ for (const m of pool) {
2314
+ let d = editDistance(String(m.label || "").toLowerCase(), tLc, bound);
2315
+ if (d > 0) {
2316
+ for (const comp of componentSet(m.label)) {
2317
+ if (d <= 0) break;
2318
+ d = Math.min(d, editDistance(comp, tLc, bound));
2319
+ }
2320
+ }
2321
+ if (d < best) {
2322
+ best = d;
2323
+ hits = [m];
2324
+ } else if (d === best && d <= bound) hits.push(m);
2325
+ }
2326
+ if (best <= bound && hits.length === 1) {
2327
+ return { match: hits[0], candidates: [], tier: 5, ambiguous: false, matchedVia: "fuzzy" };
2328
+ }
2329
+ if (best <= bound && hits.length > 1 && !proseResult) {
2330
+ const [bestInd, ...rest] = hits;
2331
+ return { match: bestInd, candidates: rest.slice(0, 4), tier: 5, ambiguous: true, matchedVia: "fuzzy" };
2332
+ }
2333
+ }
2334
+ return proseResult || { match: null, candidates: [], tier: null, ambiguous: false };
2335
+ }
2336
+ function resolveTermOrContext(graph, term, contextId) {
2337
+ if (CONTEXT_PRONOUNS.includes(String(term || "").trim().toLowerCase())) {
2338
+ if (!contextId) return { match: null, candidates: [], tier: null, ambiguous: false, unresolvedPronoun: true };
2339
+ const ind = graph.byId.get(contextId);
2340
+ return ind ? { match: ind, candidates: [], tier: 1, ambiguous: false } : { match: null, candidates: [], tier: null, ambiguous: false, unresolvedPronoun: true };
2341
+ }
2342
+ return resolveObject(graph, term);
2343
+ }
2344
+ function refineToEntities(graph, moduleIds, entityType) {
2345
+ const out = [];
2346
+ for (const e of edgesOfKind(graph, "defines")) {
2347
+ if (!moduleIds.has(e.subject)) continue;
2348
+ const ind = graph.byId.get(e.object);
2349
+ if (ind && ind.class === entityType) out.push(ind);
2350
+ }
2351
+ return out;
2352
+ }
2353
+ function commitTouches(graph, commit, entityType, extra = {}) {
2354
+ const wildcard = !entityType || entityType === "Change" || entityType === "Commit";
2355
+ const wantCoarse = wildcard || entityType === "Module";
2356
+ const wantFine = wildcard || FINE_ENTITY_TYPES.has(entityType);
2357
+ const kinds = [...wantCoarse ? ["touches"] : [], ...wantFine ? ["touchesSymbol"] : []];
2358
+ let matches = kinds.flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.subject === commit.id).map((e) => graph.byId.get(e.object)).filter(Boolean);
2359
+ if (entityType && FINE_ENTITY_TYPES.has(entityType)) matches = matches.filter((m) => m.class === entityType);
2360
+ return {
2361
+ matches,
2362
+ objMatch: commit,
2363
+ commitSubject: true,
2364
+ ambiguous: false,
2365
+ candidates: [],
2366
+ traversal: `${kinds.join("+")} edges where subject = commit ${commit.label}`,
2367
+ ...extra
2368
+ };
2369
+ }
2370
+ function modifierIsWired(shape, kind, entityType) {
2371
+ return shape === "reverse" && (kind === "imports" || kind === "calls") && (!entityType || entityType === "Module");
2372
+ }
2373
+ var TRANSITIVE_MAX_DEPTH = 8;
2374
+ function traverse(graph, parsed, { contextId = null, prev = null } = {}) {
2375
+ if (!parsed) return { matches: [], objMatch: null, candidates: [], traversal: null, ambiguous: false };
2376
+ if (parsed.node) return evalComposite(graph, parsed, { contextId, prev });
2377
+ if (parsed.ambiguousParse) return { matches: [], objMatch: null, candidates: [], traversal: null, ambiguous: false };
2378
+ const { shape, kind, entityType } = parsed;
2379
+ if (shape === "meta") {
2380
+ const term = String(parsed.object || "").trim();
2381
+ const termLc = term.toLowerCase();
2382
+ const match = (graph.individuals || []).find((i) => {
2383
+ if (i.class !== "SchemaClass" && i.class !== "SchemaPredicate") return false;
2384
+ if (String(i.label).toLowerCase() === termLc) return true;
2385
+ const token = (i.attributes || []).find((a) => a.key === "token")?.value;
2386
+ return token && String(token).toLowerCase() === termLc;
2387
+ });
2388
+ if (!match) return { matches: [], objMatch: null, candidates: [], traversal: `schema lookup for "${term}"`, ambiguous: false };
2389
+ return {
2390
+ matches: [match],
2391
+ objMatch: match,
2392
+ candidates: [],
2393
+ traversal: `schema lookup for "${term}"`,
2394
+ ambiguous: false
2395
+ };
2396
+ }
2397
+ if (shape === "mentions") {
2398
+ const term = String(parsed.object || "").trim();
2399
+ const hits = typeof lookupByProseTokens === "function" ? lookupByProseTokens(graph.proseIndex, term) : [];
2400
+ const matches2 = hits.map((h) => graph.byId.get(h.id)).filter(Boolean);
2401
+ return {
2402
+ matches: matches2,
2403
+ objMatch: null,
2404
+ candidates: [],
2405
+ ambiguous: false,
2406
+ mentionsShape: true,
2407
+ traversal: `proseIndex word lookup for "${term}"`
2408
+ };
2409
+ }
2410
+ if (parsed.modifier && parsed.modifier !== "direct" && !modifierIsWired(shape, kind, entityType)) {
2411
+ return {
2412
+ matches: [],
2413
+ objMatch: null,
2414
+ candidates: [],
2415
+ ambiguous: false,
2416
+ unsupportedModifier: true,
2417
+ traversal: `modifier "${parsed.modifier}" requested for a "${kind}" query \u2014 no closure traversal exists for this combination yet`
2418
+ };
2419
+ }
2420
+ if (shape === "ask") {
2421
+ const subj = resolveTermOrContext(graph, parsed.subject, contextId);
2422
+ const obj = resolveTermOrContext(graph, parsed.object, contextId);
2423
+ if (!subj.match || !obj.match) {
2424
+ return {
2425
+ matches: [],
2426
+ objMatch: obj.match,
2427
+ candidates: obj.candidates,
2428
+ traversal: null,
2429
+ ambiguous: false,
2430
+ answer: null,
2431
+ unresolvedPronoun: !!(subj.unresolvedPronoun || obj.unresolvedPronoun)
2432
+ };
2433
+ }
2434
+ let [from, to] = [subj.match, obj.match];
2435
+ let kinds = kindsFor(kind);
2436
+ if (kind === "touches") {
2437
+ if (to.class === "Commit" && from.class !== "Commit") [from, to] = [to, from];
2438
+ if (from.class === "Commit") kinds = ["touches", "touchesSymbol"];
2439
+ }
2440
+ const edges2 = kinds.flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.subject === from.id && e.object === to.id);
2441
+ return {
2442
+ matches: edges2,
2443
+ answer: edges2.length > 0,
2444
+ objMatch: obj.match,
2445
+ subjMatch: subj.match,
2446
+ candidates: [],
2447
+ traversal: `${kinds.join("+")} edge from ${from.label} to ${to.label}`,
2448
+ ambiguous: false
2449
+ };
2450
+ }
2451
+ const { match: objMatch, candidates, ambiguous, unresolvedPronoun, matchedVia } = resolveTermOrContext(graph, parsed.object, contextId);
2452
+ if (!objMatch) return { matches: [], objMatch: null, candidates, traversal: null, ambiguous: false, unresolvedPronoun };
2453
+ if (shape === "where") {
2454
+ const site = (objMatch.attributes || []).find((a) => a.key === "site")?.value || null;
2455
+ return {
2456
+ matches: [objMatch],
2457
+ objMatch,
2458
+ candidates,
2459
+ ambiguous,
2460
+ matchedVia,
2461
+ whereShape: true,
2462
+ site,
2463
+ traversal: site ? `site attribute of ${objMatch.label}` : `class + defining module of ${objMatch.label}`
2464
+ };
2465
+ }
2466
+ if (shape === "when") {
2467
+ const dateOf = (c) => String((c.attributes || []).find((a) => a.key === "date")?.value || "");
2468
+ let commits;
2469
+ if (objMatch.class === "Commit") {
2470
+ commits = [objMatch];
2471
+ } else {
2472
+ const edges2 = ["touches", "touchesSymbol"].flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.object === objMatch.id);
2473
+ const seen = /* @__PURE__ */ new Set();
2474
+ commits = [];
2475
+ for (const e of edges2) {
2476
+ if (seen.has(e.subject)) continue;
2477
+ seen.add(e.subject);
2478
+ const c = graph.byId.get(e.subject);
2479
+ if (c && c.class === "Commit") commits.push(c);
2480
+ }
2481
+ commits.sort((a, b) => dateOf(b).localeCompare(dateOf(a)));
2482
+ }
2483
+ return {
2484
+ matches: commits,
2485
+ objMatch,
2486
+ candidates,
2487
+ ambiguous,
2488
+ matchedVia,
2489
+ whenShape: true,
2490
+ traversal: `touches+touchesSymbol edges where object = ${objMatch.label}, newest commit date first`
2491
+ };
2492
+ }
2493
+ if (kind === "touches" && objMatch.class === "Commit") {
2494
+ return commitTouches(graph, objMatch, entityType, { candidates, ambiguous, matchedVia });
2495
+ }
2496
+ if (shape === "forward") {
2497
+ const edges2 = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.subject === objMatch.id);
2498
+ const matches2 = edges2.map((e) => graph.byId.get(e.object)).filter(Boolean);
2499
+ return { matches: matches2, objMatch, candidates, traversal: `${kindsFor(kind).join("+")} edges where subject = ${objMatch.label}`, ambiguous, matchedVia };
2500
+ }
2501
+ if (parsed.modifier === "transitive") {
2502
+ const levels = impactClosure(graph, objMatch, { maxDepth: TRANSITIVE_MAX_DEPTH });
2503
+ const matches2 = levels.flat().map((d) => graph.byId.get(d.id)).filter(Boolean);
2504
+ return {
2505
+ matches: matches2,
2506
+ objMatch,
2507
+ candidates,
2508
+ ambiguous,
2509
+ matchedVia,
2510
+ traversal: `reverse dependency closure over imports+calls edges from ${objMatch.label} (impactClosure, maxDepth=${TRANSITIVE_MAX_DEPTH})`
2511
+ };
2512
+ }
2513
+ const symbolKind = SYMBOL_GRAIN_SIBLING[kind];
2514
+ const objIsFineSymbol = !!(objMatch.class && FINE_ENTITY_TYPES.has(objMatch.class));
2515
+ if (symbolKind && (FINE_ENTITY_TYPES.has(entityType) || objIsFineSymbol)) {
2516
+ const edges2 = edgesOfKind(graph, symbolKind).filter((e) => e.object === objMatch.id);
2517
+ const subjects2 = uniqueById(edges2.map((e) => graph.byId.get(e.subject)).filter(Boolean));
2518
+ const matches2 = !entityType || entityType === "Change" ? subjects2 : subjects2.filter((i) => i.class === entityType);
2519
+ return { matches: matches2, objMatch, candidates, traversal: `${symbolKind} edges where object = ${objMatch.label}`, ambiguous, matchedVia };
2520
+ }
2521
+ let edges = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => e.object === objMatch.id);
2522
+ let extNote = "";
2523
+ if (!edges.length && objMatch.class) {
2524
+ const extId = `ext:${String(objMatch.label).toLowerCase()}`;
2525
+ edges = kindsFor(kind).flatMap((k) => edgesOfKind(graph, k)).filter((e) => String(e.object).toLowerCase() === extId);
2526
+ if (edges.length) extNote = ` (by name, via unresolved ${extId} references)`;
2527
+ }
2528
+ const subjects = [];
2529
+ const seenSubjects = /* @__PURE__ */ new Set();
2530
+ for (const e of edges) {
2531
+ const s = graph.byId.get(e.subject);
2532
+ if (s && !seenSubjects.has(s.id)) {
2533
+ seenSubjects.add(s.id);
2534
+ subjects.push(s);
2535
+ }
2536
+ }
2537
+ let matches, grainNote = "";
2538
+ if (!entityType || entityType === "Change") {
2539
+ matches = subjects;
2540
+ } else {
2541
+ const direct = subjects.filter((s) => s.class === entityType);
2542
+ if (direct.length) {
2543
+ matches = direct;
2544
+ } else if (entityType !== "Module" && subjects.some((s) => s.class === "Module")) {
2545
+ const moduleIds = new Set(subjects.filter((s) => s.class === "Module").map((s) => s.id));
2546
+ matches = refineToEntities(graph, moduleIds, entityType);
2547
+ grainNote = `, then ${entityType} defined in the matched module(s)`;
2548
+ } else {
2549
+ matches = [];
2550
+ }
2551
+ }
2552
+ return { matches, objMatch, candidates, traversal: `${kindsFor(kind).join("+")} edges where object = ${objMatch.label}${extNote}${grainNote}`, ambiguous, matchedVia };
2553
+ }
2554
+ function moduleLabelOf(ind) {
2555
+ if (ind.class === "Module") return ind.label;
2556
+ const site = (ind.attributes || []).find((a) => a.key === "site")?.value;
2557
+ if (site) return String(site).split(":")[0];
2558
+ const m = String(ind.id || "").match(/^fn:(.+)#/);
2559
+ return m ? m[1] : "(unknown module)";
2560
+ }
2561
+ function symbolLabelOf(ind) {
2562
+ const label = String(ind.label || ind.id || "");
2563
+ return ["Function", "Method"].includes(ind.class) ? `function ${label}()` : label;
2564
+ }
2565
+ function listJoin(syms) {
2566
+ return syms.length > 1 ? `${syms.slice(0, -1).join(", ")} and ${syms[syms.length - 1]}` : syms[0];
2567
+ }
2568
+ function describeParse(p) {
2569
+ const obj = p.object ?? p.subject ?? "?";
2570
+ const ent = p.entityType ? nounFor(p.entityType, 2) + " that " : "";
2571
+ return `${ent}${p.kind} "${obj}"`;
2572
+ }
2573
+ function render(parsed, result) {
2574
+ const r = renderCore(parsed, result);
2575
+ if (result && result.matchedVia === "fuzzy" && result.objMatch && !r.ambiguous) {
2576
+ r.content = `assuming you meant ${result.objMatch.label}: ${r.content}`;
2577
+ }
2578
+ return r;
2579
+ }
2580
+ function renderCore(parsed, result) {
2581
+ if (!parsed) {
2582
+ return { content: `couldn't parse this as a graph question. Try: ${rephraseHint()}`, miss: true, ambiguous: false };
2583
+ }
2584
+ if (parsed.node) return renderComposite(parsed, result);
2585
+ if (parsed.ambiguousParse) {
2586
+ const options = parsed.candidates.map((p, i) => `${i + 1}) ${describeParse(p)}`).join(" or ");
2587
+ return {
2588
+ content: `this could mean more than one thing: ${options} \u2014 try rephrasing more specifically.`,
2589
+ miss: false,
2590
+ ambiguous: true,
2591
+ candidates: parsed.candidates.map(describeParse)
2592
+ };
2593
+ }
2594
+ if (result.unresolvedPronoun) {
2595
+ return {
2596
+ content: `"${parsed.object ?? parsed.subject}" needs a selected node to refer to \u2014 click a node first, or name it directly.`,
2597
+ miss: true,
2598
+ ambiguous: false
2599
+ };
2600
+ }
2601
+ if (result.unsupportedModifier) {
2602
+ return {
2603
+ content: `the "${parsed.modifier}" modifier isn't supported for "${parsed.kind}" queries yet \u2014 only imports/calls (module-level) have a transitive closure today.`,
2604
+ miss: true,
2605
+ ambiguous: false
2606
+ };
2607
+ }
2608
+ if (parsed.shape === "meta") {
2609
+ if (!result.objMatch) {
2610
+ return {
2611
+ content: `"${parsed.object}" isn't a term in this graph's own vocabulary (no matching class or predicate).`,
2612
+ miss: true,
2613
+ ambiguous: false
2614
+ };
2615
+ }
2616
+ const doc = (result.objMatch.attributes || []).find((a) => a.key === "doc")?.value || "";
2617
+ const kindWord = result.objMatch.class === "SchemaClass" ? "a class in the graph's schema" : "a predicate (relation) in the graph's schema";
2618
+ return { content: `${result.objMatch.label} is ${kindWord}: ${doc}`, miss: false, ambiguous: false, matches: result.matches };
2619
+ }
2620
+ if (result.mentionsShape) {
2621
+ if (!result.matches.length) {
2622
+ return {
2623
+ content: `"${parsed.object}" is not mentioned in any indexed identifier or doc-comment prose. (traversal: ${result.traversal})`,
2624
+ miss: true,
2625
+ ambiguous: false
2626
+ };
2627
+ }
2628
+ const shown = result.matches.slice(0, OVERFLOW_CAP).map((m) => `${m.label} (${nounFor(m.class, 1)})`);
2629
+ const extra2 = result.matches.length > OVERFLOW_CAP ? `, \u2026and ${result.matches.length - OVERFLOW_CAP} more` : "";
2630
+ return {
2631
+ content: `"${parsed.object}" is mentioned in the prose tokens of ${listJoin(shown)}${extra2}.`,
2632
+ miss: false,
2633
+ ambiguous: false,
2634
+ matches: result.matches
2635
+ };
2636
+ }
2637
+ if (!result.objMatch && (!result.candidates || result.candidates.length === 0) && parsed.shape !== "ask") {
2638
+ const objText = String(parsed.object || "").trim();
2639
+ const what = /^(?:commit[:\s])?[0-9a-f]{7,40}$/i.test(objText) ? "commit" : !objText.includes("/") && /^[\w$]+(\.[\w$]+)+$/.test(objText) ? "symbol" : "module";
2640
+ return {
2641
+ content: `no ${what} matching "${parsed.object}" found in the index.`,
2642
+ miss: true,
2643
+ ambiguous: false,
2644
+ candidates: []
2645
+ };
2646
+ }
2647
+ if (result.ambiguous) {
2648
+ const pool = [result.objMatch, ...result.candidates || []].filter(Boolean);
2649
+ const noun = pool.length && pool.every((i) => i.class === "Commit") ? "commit" : "module";
2650
+ return {
2651
+ content: `"${parsed.object}" matches more than one ${noun} ambiguously \u2014 please narrow the term.`,
2652
+ miss: false,
2653
+ ambiguous: true,
2654
+ candidates: pool.map((i) => i.label)
2655
+ };
2656
+ }
2657
+ if (result.whereShape) {
2658
+ const ind = result.objMatch;
2659
+ if (ind.class === "Module") {
2660
+ return { content: `${ind.label} is a module \u2014 the label is its repo path.`, miss: false, ambiguous: false, matches: result.matches };
2661
+ }
2662
+ if (ind.class === "Commit") {
2663
+ return { content: `${ind.label} is a commit, not a code location \u2014 try "what did commit ${ind.label} touch".`, miss: true, ambiguous: false };
2664
+ }
2665
+ const m = String(result.site || "").match(/^(.*):(\d+)(?:-(\d+))?$/);
2666
+ if (m) {
2667
+ const lines = m[3] && m[3] !== m[2] ? `lines ${m[2]}-${m[3]}` : `line ${m[2]}`;
2668
+ return { content: `${symbolLabelOf(ind)} is defined in ${m[1]} at ${lines}.`, miss: false, ambiguous: false, matches: result.matches };
2669
+ }
2670
+ return {
2671
+ content: `${symbolLabelOf(ind)} is defined in ${moduleLabelOf(ind)} (no line span recorded in this index).`,
2672
+ miss: false,
2673
+ ambiguous: false,
2674
+ matches: result.matches
2675
+ };
2676
+ }
2677
+ if (result.whenShape) {
2678
+ const subject = result.objMatch.label;
2679
+ if (!result.matches.length) {
2680
+ return { content: `no recorded commit touches ${subject} in this index. (traversal: ${result.traversal})`, miss: true, ambiguous: false };
2681
+ }
2682
+ const newest = result.matches[0];
2683
+ const date = (newest.attributes || []).find((a) => a.key === "date")?.value || "";
2684
+ if (!date) {
2685
+ return {
2686
+ content: `commit ${newest.label} touched ${subject}, but this index records no commit dates \u2014 regenerate the graph to attach mgx:commitDate.`,
2687
+ miss: true,
2688
+ ambiguous: false
2689
+ };
2690
+ }
2691
+ const msg = (newest.attributes || []).find((a) => a.key === "message")?.value || "";
2692
+ const day = String(date).slice(0, 10);
2693
+ if (newest.id === result.objMatch.id) {
2694
+ return { content: `commit ${newest.label} is dated ${day}${msg ? ` ("${msg}")` : ""}.`, miss: false, ambiguous: false, matches: result.matches };
2695
+ }
2696
+ const more = result.matches.length - 1;
2697
+ return {
2698
+ content: `${subject} was last touched by commit ${newest.label} on ${day}${msg ? ` ("${msg}")` : ""}${more ? `; ${more} earlier commit${more === 1 ? "" : "s"} recorded` : ""}.`,
2699
+ miss: false,
2700
+ ambiguous: false,
2701
+ matches: result.matches
2702
+ };
2703
+ }
2704
+ if (result.commitSubject) {
2705
+ const cite = `commit ${result.objMatch.label}`;
2706
+ if (!result.matches.length) {
2707
+ return {
2708
+ content: `${cite} touched nothing recorded in the index. (traversal: ${result.traversal})`,
2709
+ miss: true,
2710
+ ambiguous: false
2711
+ };
2712
+ }
2713
+ const byClass = /* @__PURE__ */ new Map();
2714
+ for (const m of result.matches.slice(0, OVERFLOW_CAP)) {
2715
+ const cls = m.class || "Module";
2716
+ if (!byClass.has(cls)) byClass.set(cls, []);
2717
+ byClass.get(cls).push(["Function", "Method"].includes(cls) ? `${m.label}()` : m.label);
2718
+ }
2719
+ const clauses2 = [...byClass.entries()].map(([cls, labels]) => `${nounFor(cls, labels.length)} ${listJoin(labels)}`);
2720
+ const extra2 = result.matches.length > OVERFLOW_CAP ? `; \u2026and ${result.matches.length - OVERFLOW_CAP} more` : "";
2721
+ return { content: `${cite} touched ${clauses2.join("; ")}${extra2}.`, miss: false, ambiguous: false, matches: result.matches };
2722
+ }
2723
+ if (parsed.shape === "ask") {
2724
+ if (!result.objMatch || !result.subjMatch) {
2725
+ return { content: `couldn't resolve one of the terms in this question.`, miss: true, ambiguous: false };
2726
+ }
2727
+ return {
2728
+ content: result.answer ? `Yes. (${result.traversal})` : `No \u2014 no ${parsed.kind} edge found from ${result.subjMatch.label} to ${result.objMatch.label}.`,
2729
+ miss: !result.answer,
2730
+ ambiguous: false
2731
+ };
2732
+ }
2733
+ if (!result.matches.length) {
2734
+ if (parsed.shape === "forward") {
2735
+ return {
2736
+ content: `${result.objMatch.label} has no ${parsed.kind} edges in the index. (traversal: ${result.traversal || "no traversal resolved"})`,
2737
+ miss: true,
2738
+ ambiguous: false
2739
+ };
2740
+ }
2741
+ const entityWord = nounFor(parsed.entityType || "Module", 2);
2742
+ return {
2743
+ content: `No ${entityWord} found whose module directly ${verbFor(parsed.kind)} ${parsed.object}. (traversal: ${result.traversal || "no traversal resolved"})`,
2744
+ miss: true,
2745
+ ambiguous: false
2746
+ };
2747
+ }
2748
+ 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);
2750
+ const extra2 = result.matches.length > OVERFLOW_CAP ? `, \u2026and ${result.matches.length - OVERFLOW_CAP} more` : "";
2751
+ return { content: shown.join(" and ") + extra2 + ".", miss: false, ambiguous: false, matches: result.matches };
2752
+ }
2753
+ const byModule = /* @__PURE__ */ new Map();
2754
+ for (const m of result.matches.slice(0, OVERFLOW_CAP)) {
2755
+ const mod = moduleLabelOf(m);
2756
+ if (!byModule.has(mod)) byModule.set(mod, []);
2757
+ byModule.get(mod).push(symbolLabelOf(m));
2758
+ }
2759
+ const clauses = [...byModule.entries()].map(([mod, syms], i) => {
2760
+ const list = listJoin(syms);
2761
+ return i === 0 ? `in ${mod} there is ${list}` : `there is ${list} in ${mod}`;
2762
+ });
2763
+ const extra = result.matches.length > OVERFLOW_CAP ? ` \u2026and ${result.matches.length - OVERFLOW_CAP} more` : "";
2764
+ return { content: clauses.join(" and ") + extra + ".", miss: false, ambiguous: false, matches: result.matches };
2765
+ }
2766
+ var CONTENT_VOCAB = /* @__PURE__ */ new Set([
2767
+ ...wordsOf(Object.keys(VERB_TO_KIND)),
2768
+ ...wordsOf(Object.keys(ENTITY_TO_TYPE)),
2769
+ ...wordsOf(Object.keys(MODIFIER_TO_KIND)),
2770
+ ...wordsOf(Object.keys(QUALIFIERS)),
2771
+ ...wordsOf(AGGREGATE_TRIGGERS),
2772
+ ...wordsOf(Object.keys(SUPERLATIVE_EXTREMES)),
2773
+ ...wordsOf(Object.keys(EDGE_NOUN_TO_METRIC)),
2774
+ ...wordsOf(Object.keys(BOOLEAN_CONNECTIVES)),
2775
+ ...wordsOf(PLACEHOLDER_NOUNS),
2776
+ ...wordsOf(ANAPHORA_TRIGGERS),
2777
+ ...wordsOf(META_MEANING_VERBS),
2778
+ ...wordsOf(WHERE_MARKERS),
2779
+ ...wordsOf(MENTION_MARKERS),
2780
+ ...wordsOf(RELATIVE_PRONOUNS),
2781
+ ...wordsOf(Object.keys(CASCADE_SYNONYMS))
2782
+ ]);
2783
+ var STRUCTURAL_WORDS = /* @__PURE__ */ new Set([...STOPWORDS2, ...FRAME_WORDS, ...CONTEXT_PRONOUNS]);
2784
+ var CASCADE_NOISE_SET = new Set(wordsOf(CASCADE_NOISE));
2785
+ var NOISE_OR_SCAFFOLD = /* @__PURE__ */ new Set([...CASCADE_NOISE_SET, ...STRUCTURAL_WORDS]);
2786
+ var TRIGGER_FUZZY_WORDS = [
2787
+ "many",
2788
+ "count",
2789
+ "number",
2790
+ "quantity",
2791
+ "total",
2792
+ "tally",
2793
+ "list",
2794
+ "show",
2795
+ "display",
2796
+ "print",
2797
+ "dump",
2798
+ "enumerate",
2799
+ "name"
2800
+ ];
2801
+ var CASCADE_FUZZY_TARGETS = [.../* @__PURE__ */ new Set([
2802
+ ...wordsOf(Object.keys(VERB_TO_KIND)),
2803
+ ...Object.keys(ENTITY_TO_TYPE),
2804
+ ...TRIGGER_FUZZY_WORDS
2805
+ ])].filter((wd) => /^[a-z]+$/.test(wd) && wd.length >= 4 && !STOPWORDS2.has(wd));
2806
+ function fuzzyCascadeWord(w) {
2807
+ const bound = fuzzyBound(w);
2808
+ let best = bound + 1;
2809
+ let hit = null;
2810
+ let tied = false;
2811
+ for (const target of CASCADE_FUZZY_TARGETS) {
2812
+ const d = editDistance(w, target, Math.min(best, bound));
2813
+ if (d < best) {
2814
+ best = d;
2815
+ hit = target;
2816
+ tied = false;
2817
+ } else if (d === best && d <= bound && target !== hit) tied = true;
2818
+ }
2819
+ return best <= bound && !tied ? hit : null;
2820
+ }
2821
+ function schemaTypoTrap(resolution, term) {
2822
+ if (!resolution?.match || resolution.matchedVia !== "fuzzy" || resolution.ambiguous) return false;
2823
+ const cls = resolution.match.class;
2824
+ if (cls !== "SchemaClass" && cls !== "SchemaPredicate") return false;
2825
+ const lc = String(term || "").trim().toLowerCase();
2826
+ const kindNoun = fuzzyCascadeWord(lc);
2827
+ return !!kindNoun && kindNoun !== lc && !!ENTITY_TO_TYPE[kindNoun];
2828
+ }
2829
+ function answerable(graph, parsed, contextId) {
2830
+ if (!parsed) return false;
2831
+ if (parsed.ambiguousParse) return "ambiguous";
2832
+ if (parsed.node) return parsed.node !== "miss";
2833
+ if (parsed.shape === "meta" || parsed.shape === "mentions") return true;
2834
+ const o = resolveTermOrContext(graph, parsed.object, contextId);
2835
+ if (o.unresolvedPronoun) return "pronoun";
2836
+ if (!o.match || schemaTypoTrap(o, parsed.object)) return false;
2837
+ if (parsed.shape === "ask") {
2838
+ const s = resolveTermOrContext(graph, parsed.subject, contextId);
2839
+ if (s.unresolvedPronoun) return "pronoun";
2840
+ return s.match && !schemaTypoTrap(s, parsed.subject) ? true : false;
2841
+ }
2842
+ return true;
2843
+ }
2844
+ function isHelpRequest(query) {
2845
+ const q = String(query || "").trim().toLowerCase().replace(/[?.!\s]+$/, "");
2846
+ return HELP_TRIGGERS.includes(q);
2847
+ }
2848
+ function relaxParse(graph, query, { nlp = void 0, contextId = null, prev = null } = {}) {
2849
+ const from = applyNegationFrames(normalizeQuery(String(query || "")));
2850
+ let tokens = splitWords(from);
2851
+ if (!tokens.length) return null;
2852
+ const dropped = [];
2853
+ const steps = [];
2854
+ const resolvesExact = (t) => {
2855
+ const r = resolveObject(graph, t);
2856
+ return !!r.match && r.tier != null && r.tier <= 2;
2857
+ };
2858
+ const resolvesLiteral = (t) => {
2859
+ const r = resolveObject(graph, t);
2860
+ return !!r.match && r.tier != null && r.tier <= 3;
2861
+ };
2862
+ const hasRealTerm = (s) => splitWords(String(s || "")).some((w) => {
2863
+ const lc = w.toLowerCase();
2864
+ return !CONTENT_VOCAB.has(lc) && !STRUCTURAL_WORDS.has(lc);
2865
+ });
2866
+ const TERM_SHAPES = /* @__PURE__ */ new Set(["reverse", "forward", "where", "when", "ask"]);
2867
+ const attempt = (toks) => {
2868
+ const text = toks.join(" ");
2869
+ const p = parseQuery(text, { nlp });
2870
+ if (answerable(graph, p, contextId) !== true) return null;
2871
+ if (p && !p.node && TERM_SHAPES.has(p.shape)) {
2872
+ if (p.object != null && !hasRealTerm(p.object)) return null;
2873
+ if (p.shape === "ask" && p.subject != null && !hasRealTerm(p.subject)) return null;
2874
+ }
2875
+ const rendered = render(p, traverse(graph, p, { contextId, prev }));
2876
+ return rendered.miss ? null : { parsed: p, text };
2877
+ };
2878
+ const done = (hit) => ({ parsed: hit.parsed, from, to: hit.text, dropped: [...dropped], steps });
2879
+ let guard = 0;
2880
+ const hardCap = Math.max(tokens.length, 1) + 12;
2881
+ for (; guard < hardCap; guard += 1) {
2882
+ let idx = -1;
2883
+ for (let i = 0; i < tokens.length; i += 1) {
2884
+ const lc = tokens[i].toLowerCase();
2885
+ if (CASCADE_NOISE_SET.has(lc) && !CONTENT_VOCAB.has(lc) && !resolvesExact(tokens[i])) {
2886
+ idx = i;
2887
+ break;
2888
+ }
2889
+ }
2890
+ if (idx < 0) break;
2891
+ const removed = tokens[idx];
2892
+ tokens = tokens.filter((_, i) => i !== idx);
2893
+ dropped.push(removed);
2894
+ steps.push(`strip noise "${removed}" \u2192 "${tokens.join(" ")}"`);
2895
+ const hit = attempt(tokens);
2896
+ if (hit) return done(hit);
2897
+ }
2898
+ const survivors = [];
2899
+ const nowDropped = [];
2900
+ const corrected = [];
2901
+ for (const t of tokens) {
2902
+ const lc = t.toLowerCase();
2903
+ const plain = /^[a-z]+$/.test(lc);
2904
+ if (!plain || CONTENT_VOCAB.has(lc) || STRUCTURAL_WORDS.has(lc) || resolvesLiteral(t)) {
2905
+ survivors.push(t);
2906
+ continue;
2907
+ }
2908
+ const fix = fuzzyCascadeWord(lc);
2909
+ if (fix && fix !== lc) {
2910
+ survivors.push(fix);
2911
+ corrected.push(`${t}\u2192${fix}`);
2912
+ continue;
2913
+ }
2914
+ nowDropped.push(t);
2915
+ }
2916
+ if ((corrected.length || nowDropped.length) && survivors.length) {
2917
+ tokens = survivors;
2918
+ dropped.push(...nowDropped);
2919
+ if (corrected.length) steps.push(`fuzzy-correct ${JSON.stringify(corrected)} \u2192 "${tokens.join(" ")}"`);
2920
+ if (nowDropped.length) steps.push(`drop unmatched ${JSON.stringify(nowDropped)} \u2192 "${tokens.join(" ")}"`);
2921
+ const hit = attempt(tokens);
2922
+ if (hit) return done(hit);
2923
+ }
2924
+ let changed = false;
2925
+ const normed = tokens.map((t) => {
2926
+ const lc = t.toLowerCase();
2927
+ if (CASCADE_SYNONYMS[lc] && !resolvesLiteral(t)) {
2928
+ changed = true;
2929
+ return CASCADE_SYNONYMS[lc];
2930
+ }
2931
+ return t;
2932
+ });
2933
+ if (changed) {
2934
+ steps.push(`normalise synonyms \u2192 "${normed.join(" ")}"`);
2935
+ const hit = attempt(normed);
2936
+ if (hit) return done(hit);
2937
+ }
2938
+ const bareLc = splitWords(from).map((t) => t.toLowerCase());
2939
+ const kindWords = [];
2940
+ const others = [];
2941
+ for (const t of bareLc) {
2942
+ if (NOISE_OR_SCAFFOLD.has(t)) continue;
2943
+ const et = ENTITY_TO_TYPE[t];
2944
+ if (et && et !== "Change") kindWords.push(t);
2945
+ else others.push(t);
2946
+ }
2947
+ if (kindWords.length === 1 && others.length === 0) {
2948
+ const hit = attempt(["count", kindWords[0]]);
2949
+ if (hit) {
2950
+ steps.push(`bare kind "${kindWords[0]}" \u2192 count`);
2951
+ return done(hit);
2952
+ }
2953
+ }
2954
+ return null;
2955
+ }
2956
+ function ask(graph, query, { contextId = null, nlp = void 0, prev = null } = {}) {
2957
+ if (isHelpRequest(query)) {
2958
+ return {
2959
+ content: rephraseHint(),
2960
+ tmct_ask: {
2961
+ mechanical: true,
2962
+ parsed: null,
2963
+ matches: [],
2964
+ traversal: null,
2965
+ miss: true,
2966
+ ambiguous: false,
2967
+ matchedVia: null,
2968
+ help: true,
2969
+ relaxed: null
2970
+ }
2971
+ };
2972
+ }
2973
+ const direct = parseQuery(query, { nlp });
2974
+ let parsed = direct;
2975
+ let relaxed = null;
2976
+ if (answerable(graph, direct, contextId) === false) {
2977
+ const r = relaxParse(graph, query, { nlp, contextId, prev });
2978
+ if (r) {
2979
+ parsed = r.parsed;
2980
+ relaxed = { from: r.from, to: r.to, dropped: r.dropped, steps: r.steps };
2981
+ }
2982
+ }
2983
+ const result = traverse(graph, parsed, { contextId, prev });
2984
+ const rendered = render(parsed, result);
2985
+ const content = relaxed && !rendered.miss && relaxed.to !== relaxed.from ? `read as "${relaxed.to}" \u2014 ${rendered.content}` : rendered.content;
2986
+ return {
2987
+ content,
2988
+ tmct_ask: {
2989
+ mechanical: true,
2990
+ parsed: parsed && !parsed.ambiguousParse ? parsed : null,
2991
+ matches: (result.matches || []).map((m) => ({
2992
+ id: m.id,
2993
+ label: m.label,
2994
+ type: m.class,
2995
+ module: m.class ? moduleLabelOf(m) : void 0
2996
+ })),
2997
+ traversal: result.traversal || null,
2998
+ miss: !!rendered.miss,
2999
+ ambiguous: !!rendered.ambiguous,
3000
+ // The relaxation trace: null when the direct parse was used as-is (a clean hit or
3001
+ // an honest miss the cascade couldn't/shouldn't rescue), else what the cascade
3002
+ // dropped/normalised to reach an answer. A caller can assert relaxed===null to
3003
+ // prove the cascade never touched a direct hit.
3004
+ relaxed,
3005
+ // Confidence provenance: "prose" when resolveObject fell through to the tier-4
3006
+ // prose-index fallback (PLAN_PROSE_INDEX.md §6 — matched what the symbol talks
3007
+ // about, not its name); "fuzzy" when the tier-5 bounded-edit-distance pass
3008
+ // resolved a typo'd term (the rendered content also announces it: "assuming you
3009
+ // meant <label>"); null for every literal-identifier tier.
3010
+ matchedVia: result.matchedVia || null,
3011
+ ...rendered.ambiguous ? { candidates: rendered.candidates } : {}
3012
+ }
3013
+ };
3014
+ }
3015
+
3016
+ // packages/seonix/src/ask-browser-entry.mjs
3017
+ function parseEntities(payload) {
3018
+ const individuals = Array.isArray(payload?.individuals) ? payload.individuals : [];
3019
+ const byId = /* @__PURE__ */ new Map();
3020
+ for (const ind of individuals) if (ind && ind.id) byId.set(ind.id, ind);
3021
+ const relations = (Array.isArray(payload?.objectProperties) ? payload.objectProperties : []).map((g) => {
3022
+ const edges = (Array.isArray(g?.examples) ? g.examples : []).filter((e) => e && e.subject && e.object);
3023
+ return { predicate: String(g?.predicate || ""), prop: g?.prop ?? null, count: Number(g?.count) || edges.length, edges };
3024
+ });
3025
+ return {
3026
+ individuals,
3027
+ byId,
3028
+ relations,
3029
+ truncated: Boolean(payload?.truncated),
3030
+ generatedAt: payload?.generated_at || "",
3031
+ proseIndex: payload?.proseIndex || {}
3032
+ };
3033
+ }
3034
+ function ask2(graph, query, opts) {
3035
+ const r = ask(graph, query, opts);
3036
+ return { content: r.content, seonix_ask: r.tmct_ask };
3037
+ }
3038
+ globalThis.parseEntities = parseEntities;
3039
+ globalThis.ask = ask2;
3040
+ })();