@polycode-projects/seonix 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/ask-vocab.mjs DELETED
@@ -1,687 +0,0 @@
1
- // ask-vocab.mjs — the single committed vocabulary `ask.mjs`'s grammar, rephrase
2
- // hint, and renderer noun forms all derive from (§3 of PLAN_MECHANICAL_CHAT.md).
3
- // A shipped, hand-curated resource — the same "closed is deliberate" ethos as
4
- // this repo's other closed vocabularies (SEON/mgx predicates, `.seonixignore`'s
5
- // gitignore-subset grammar): an open/unbounded phrase list is unvalidatable and
6
- // eventually produces a false-positive match against an unrelated code
7
- // identifier, so each relation carries a curated (not exhaustive) set of real
8
- // phrasings a developer would type, not a general-English thesaurus dump.
9
- //
10
- // Modeled on marginalia's app/lib/vocab.mjs pattern (a single source of truth
11
- // with a `verbs: [...]` trigger-phrase array per token) — minus the RDF/OWL
12
- // external-alignment machinery, which doesn't apply here: seonix's vocabulary
13
- // is code-relationship-specific (imports/calls/inherits/…), not a general
14
- // knowledge-graph ontology. No NLP library, no lemmatiser — plain phrase lists
15
- // feeding the same fixed-precedence regex grammar `ask.mjs` already used, so
16
- // broadening coverage never risks the "closed, deterministic, zero model
17
- // calls" contract this repo holds at every layer (extraction through query).
18
-
19
- /** relation token -> { comment, verbs[] }. Every phrase in `verbs` must be
20
- * something a developer would plausibly type asking about THIS relation in
21
- * THIS codebase's terms — not a synonym so generic it risks matching an
22
- * unrelated question (see the file-level comment; judgment calls on omitted
23
- * phrases are logged inline below, not silently dropped).
24
- *
25
- * Register spread (2026-07-02, ELIZA/PARRY-style breadth, PLAN_MECHANICAL_CHAT.md
26
- * §3.5): each relation's `verbs` list deliberately spans formal, neutral, and
27
- * casual/colloquial phrasings — not just formal synonyms — because the value of
28
- * a keyword-spotting matcher is SURFACE-FORM tolerance for the same underlying
29
- * intent, not narrow correctness. Registers are grouped with an inline comment
30
- * per relation rather than a per-phrase tag (kept flat, matching marginalia's
31
- * `verbs: [...]` shape) since ask.mjs only needs phrase -> kind, never the
32
- * register itself. A misparsed casual phrase costs nothing beyond an honest
33
- * object-miss (resolveObject never guesses), so breadth here is genuinely low-risk. */
34
- export const RELATIONS = {
35
- imports: {
36
- comment: "Module -> Module: subject's import graph references object (usesComplexType).",
37
- verbs: [
38
- // formal/neutral ("uses"/"use" moved to the `uses` union family, 2026-07-02 —
39
- // "uses code from" stays here: its phrasing is specifically import-flavored)
40
- "couples to", "couple to", "depends on", "imports", "import",
41
- "relies on", "rely on", "requires", "require", "references", "reference",
42
- "pulls in", "pull in", "built on", "builds on", "build on", "uses code from",
43
- // casual/colloquial
44
- "grabs", "grab", "pulls from", "pull from", "leans on", "lean on",
45
- "is wired to", "are wired to", "is hooked up to", "are hooked up to",
46
- // gerund (g-drop normalization turns dialectal "importin'" into this — §3.5)
47
- "importing",
48
- ],
49
- },
50
- // "uses" is a QUERY-side union family, not a stored predicate (2026-07-02 query
51
- // families): "what uses X" honestly means BOTH the import graph and the call
52
- // graph, so ask.mjs traverses it as imports + calls + callsSymbol together
53
- // (KIND_UNIONS there). The verbs moved here FROM imports — "which modules use X"
54
- // still answers with the importing modules (the asked Module grain filters the
55
- // union down to module-grain subjects), and "what uses <function>" now also
56
- // reaches the symbol-grain callers instead of silently ignoring them.
57
- uses: {
58
- comment: "query-side union: imports (Module->Module) + calls (Module->Module) + callsSymbol (fn->fn).",
59
- verbs: [
60
- "uses", "use", "used by", "makes use of", "make use of",
61
- // gerund (g-drop normalization — §3.5)
62
- "using",
63
- ],
64
- },
65
- calls: {
66
- comment: "Function/Method -> Function/Class (symbol-grain) or Module -> Module (coarse): subject invokes object.",
67
- verbs: [
68
- // formal
69
- "invokes", "invoke",
70
- // neutral
71
- "calls", "call", "runs", "run", "executes", "execute",
72
- // casual/colloquial
73
- "hits", "hit", "triggers", "trigger", "fires", "fire", "kicks off", "kick off",
74
- // gerund (g-drop normalization turns dialectal "callin'" into this — §3.5)
75
- "calling",
76
- ],
77
- },
78
- defines: {
79
- comment: "Module -> top-level Function/Class/Method/Attribute: subject declares object.",
80
- verbs: [
81
- "defines", "define", "declares", "declare",
82
- "has", "have", "holds", "hold",
83
- // gerund (g-drop normalization — §3.5)
84
- "defining",
85
- ],
86
- },
87
- contains: {
88
- comment: "Class -> Method/Attribute: subject's membership includes object.",
89
- verbs: [
90
- "contains", "contain", "lives in", "live in", "is defined in", "are defined in",
91
- "is part of", "are part of", "sits in", "sit in", "sits inside", "sit inside",
92
- // gerund (g-drop normalization — §3.5)
93
- "containing",
94
- ],
95
- },
96
- tests: {
97
- comment: "Module -> Module: subject is a test module importing/covering object.",
98
- verbs: [
99
- "tests", "test", "covers", "cover", "verifies", "verify", "exercises", "exercise",
100
- "checks", "check", "makes sure of", "make sure of",
101
- // gerund (g-drop normalization — §3.5)
102
- "testing",
103
- ],
104
- },
105
- inherits: {
106
- comment: "Class -> Class: subject's declared base resolves to object (subclassOf).",
107
- verbs: [
108
- "inherits from", "inherit from", "extends", "extend", "subclasses", "subclass",
109
- "derives from", "derive from", "is a subclass of", "are a subclass of",
110
- "is a kind of", "are a kind of", "is built off", "are built off",
111
- "is built on top of", "are built on top of",
112
- // gerund (g-drop normalization — §3.5, and the compositional grammar's
113
- // gerund-led boolean gate: "classes inheriting from Base but not tested").
114
- // Both the two-word "inheriting from" (so "from" is consumed into the verb
115
- // phrase, not the object term) and the bare "inheriting" (the single token
116
- // the gerund-lead check reads) are listed; longest-match-first prefers the
117
- // two-word form when "from" follows.
118
- "extending", "inheriting from", "inheriting", "subclassing", "extends from",
119
- ],
120
- },
121
- touches: {
122
- comment: "Commit -> Module (coarse) or Commit -> symbol (fine, touchesSymbol): a commit's changed-line-range intersects object.",
123
- verbs: [
124
- "touched", "touches", "changed", "change", "modified", "modifies",
125
- "was changed in", "were changed in", "was edited in", "were edited in",
126
- "was modified by", "were modified by", "was tweaked in", "were tweaked in",
127
- "got changed in", "got edited in",
128
- // commit-question forms (2026-07-02, viewer commit-chat fix): the same touch
129
- // relation asked from the commit's side — "which changes touch commit <sha>",
130
- // "what did commit <sha> touch", "which functions changed in <sha>", "which
131
- // changes landed in commit <sha>", "what was touched by commit <sha>". Bare
132
- // "touch" completes the touched/touches pair for the "did … touch" auxiliary
133
- // form; the "by"/"in" phrases are the passive/locative counterparts of forms
134
- // already above (curated per register spread, not a thesaurus dump).
135
- "touch", "touched by", "modified by", "changed by", "changed in",
136
- "landed in", "land in",
137
- // contents-of-a-commit forms (2026-07-02, operator screenshot: "what was in
138
- // commit <sha>" missed on the live site). "was in"/"went into" only read as
139
- // touch questions when the object is a commit — the sha-shaped object keeps
140
- // them from firing on structural questions ("is X in the graph" has no verb
141
- // match anyway). Judgment call: "is in" omitted — too generic without the
142
- // past-tense anchor and risks matching containment phrasings.
143
- "was in", "were in", "went into", "included in",
144
- // when-question forms (2026-07-02 query families): "when was X last
145
- // updated/edited" — bare past participles that only read naturally in the
146
- // temporal shape; the when template routes them, but they are ordinary
147
- // touches verbs so "which modules were updated ..." keeps working too.
148
- "updated", "edited",
149
- // gerund (g-drop normalization — §3.5)
150
- "touching",
151
- ],
152
- },
153
- cochange: {
154
- comment: "Module -> Module: subject and object are frequently committed together (changeCoupledWith).",
155
- verbs: [
156
- "changed with", "co-changes with", "co-change with", "changes alongside",
157
- "change alongside", "shares commits with", "share commits with",
158
- "tends to change together with", "tend to change together with",
159
- "moves together with", "move together with",
160
- ],
161
- },
162
- reexports: {
163
- comment: "Module -> exported symbol: subject's public API surface (__all__/export list).",
164
- verbs: [
165
- "exports", "export", "re-exports", "re-export", "passes through", "pass through",
166
- // API-surface phrasing (2026-07-02 query families): "what does <module>
167
- // expose". Bare "exposed" is NOT listed — the lemma tier maps it here when
168
- // an adapter is present, and "how does the API get exposed" has no
169
- // traversal either way (pinned as an honest miss in the tests).
170
- "exposes", "expose",
171
- // gerund (g-drop normalization — §3.5)
172
- "exporting",
173
- ],
174
- },
175
- };
176
-
177
- // ---- where/when/mentions markers (2026-07-02 query families) — the location and
178
- // prose-mention questions carry NO relation verb ("where is X defined", "where is
179
- // X mentioned"), so ask.mjs routes them by these marker words instead of
180
- // VERB_TO_KIND. Closed lists, same curation discipline as everything above. ----
181
-
182
- /** Definition-location markers: "where is X <marker>" (or bare "where is X"). */
183
- export const WHERE_MARKERS = Object.freeze(["defined", "declared", "located", "implemented"]);
184
-
185
- /** Prose-mention markers: "where is X <marker>" -> the prose/mentions surface. */
186
- export const MENTION_MARKERS = Object.freeze(["mentioned", "referenced"]);
187
-
188
- /** relation token -> flat verb-phrase list, the shape ask.mjs's VERB_TO_KIND
189
- * table needs (phrase -> kind), derived once from RELATIONS. */
190
- export const VERB_TO_KIND = Object.freeze(
191
- Object.fromEntries(
192
- Object.entries(RELATIONS).flatMap(([kind, { verbs }]) => verbs.map((v) => [v, kind])),
193
- ),
194
- );
195
-
196
- export const ENTITY_TO_TYPE = Object.freeze({
197
- function: "Function", functions: "Function",
198
- method: "Method", methods: "Method",
199
- class: "Class", classes: "Class",
200
- module: "Module", modules: "Module", file: "Module", files: "Module",
201
- attribute: "Attribute", attributes: "Attribute", field: "Attribute", fields: "Attribute",
202
- variable: "GlobalVariable", variables: "GlobalVariable", global: "GlobalVariable", globals: "GlobalVariable",
203
- // "changes" in a touch question ("which changes touch commit <sha>") means the
204
- // code entities on the other end of the touch edges, at WHATEVER grain the graph
205
- // recorded — module (touches) and symbol (touchesSymbol) together when the commit
206
- // is the given side, and the touching commits themselves when a module/symbol is
207
- // the given side. Mapped to the pseudo-type "Change" (not a node class): ask.mjs's
208
- // traverse() reads it as a wildcard over the touch traversal's results rather than
209
- // aliasing it to ONE real class and silently dropping the other grain of the
210
- // answer. Listed BEFORE commit/commits: findPhrase (ask.mjs) takes the first
211
- // same-length phrase in table order, so in "which changes touch commit <sha>" the
212
- // entity slot must consume "changes" and leave "commit <sha>" intact as the
213
- // object term.
214
- change: "Change", changes: "Change",
215
- commit: "Commit", commits: "Commit",
216
- });
217
-
218
- export const MODIFIER_TO_KIND = Object.freeze({
219
- explicitly: "direct", directly: "direct",
220
- transitively: "transitive", indirectly: "transitive",
221
- });
222
-
223
- // ---- §3.5 normalization — contractions/informal spellings that would otherwise
224
- // block a match, expanded BEFORE parsing (BOTH the anchored-template strategy
225
- // and the independent keyword-spotting strategy see the same normalized text —
226
- // this table is not owned by either). Small and code-question-scoped, not a
227
- // general slang dictionary — every entry here exists because it appears in an
228
- // actual worked example this file's matcher must handle. ----
229
- export const CONTRACTIONS = Object.freeze({
230
- "ain't": "is not", "aint": "is not",
231
- "isn't": "is not", "isnt": "is not",
232
- "aren't": "are not", "arent": "are not",
233
- "doesn't": "does not", "doesnt": "does not",
234
- "don't": "do not", "dont": "do not",
235
- "didn't": "did not", "didnt": "did not",
236
- "there's": "there is", "theres": "there is",
237
- "what's": "what is", "whats": "what is",
238
- "who's": "who is", "whos": "who is",
239
- "gonna": "going to",
240
- "wanna": "want to",
241
- "gotta": "got to",
242
- "yer": "your", "ur": "your",
243
- "gimme": "give me",
244
- });
245
-
246
- // ---- misspellings and wrong words (2026-07-02, two-level fuzzy work) — these are
247
- // CORRECTIONS, not synonyms: the asker typed a broken or incorrect surface form of
248
- // a word this grammar already owns, and we restore the canonical form BEFORE
249
- // parsing (normalizeQuery applies both tables like CONTRACTIONS: word-boundary,
250
- // longest key first, case-insensitive). Synonyms belong in RELATIONS/ENTITY_TO_TYPE;
251
- // these tables exist so a typo'd or misused word maps to canonical language
252
- // deterministically, ahead of (and more precisely than) ask.mjs's bounded
253
- // edit-distance fallback. ask.mjs's correction regex refuses to rewrite a word
254
- // glued to a dotted extension ("revision.mjs" stays a module name), since
255
- // WRONG_WORDS entries are real English words that plausibly name modules; a
256
- // bare standalone token that happens to be a real identifier remains the
257
- // accepted residual exposure, same as CONTRACTIONS. ----
258
-
259
- /** Curated common typos of THIS vocabulary's own keywords — entity nouns, relation
260
- * verbs, and the grammar's anchor words (which/what/does/the): a typo'd anchor
261
- * kills the anchored templates outright, so anchors earn entries too. Values are
262
- * always the canonical word. Judgment calls logged: "calss" maps to "class" (the
263
- * doubled-s slip of "class"), NOT "calls", though both are edit-distance 1 — the
264
- * exact tie the generic fuzzy tier must refuse to break; a curated table is where
265
- * that call is made deliberately. "dose" is a real English word, but at a word
266
- * boundary in this closed question grammar ("dose X import Y") the intent is
267
- * unambiguous. */
268
- export const MISSPELLINGS = Object.freeze({
269
- // entity nouns
270
- "funtion": "function", "funtions": "functions",
271
- "fucntion": "function", "fucntions": "functions",
272
- "functoin": "function", "functoins": "functions",
273
- "calss": "class", "calsses": "classes", "classs": "class", "clases": "classes",
274
- "modul": "module", "moduls": "modules",
275
- "moduel": "module", "moduels": "modules",
276
- "moudle": "module", "moudles": "modules",
277
- "methdo": "method", "methdos": "methods",
278
- "mehtod": "method", "mehtods": "methods",
279
- "comit": "commit", "comits": "commits",
280
- "commmit": "commit", "commmits": "commits",
281
- "varaible": "variable", "varaibles": "variables",
282
- "varibale": "variable", "varibales": "variables",
283
- "atribute": "attribute", "atributes": "attributes",
284
- // relation verbs
285
- "improt": "import", "improts": "imports",
286
- "imoprt": "import", "imoprts": "imports",
287
- "inherts": "inherits", "inheirts": "inherits",
288
- "extands": "extends", "extneds": "extends",
289
- "depnds": "depends",
290
- "touchs": "touches", "tuoches": "touches", "touhced": "touched",
291
- "chagned": "changed", "chnaged": "changed",
292
- "chagnes": "changes", "chnages": "changes",
293
- "calles": "calls",
294
- "exprot": "export", "exprots": "exports",
295
- "tets": "tests",
296
- // grammar anchor words
297
- "whcih": "which", "wich": "which", "whihc": "which",
298
- "waht": "what",
299
- "dose": "does", "doess": "does",
300
- "teh": "the",
301
- // aggregate/list TRIGGER words (2026-07-02, trigger-typo work) — a typo of a count
302
- // or list trigger used to be DROPPED as unmatched by the relaxation cascade, losing
303
- // the aggregate/list INTENT entirely ("how manyn classes" → the count was lost);
304
- // curated here so the intended trigger is restored BEFORE parsing (the general
305
- // bounded fuzzy path in ask.mjs's cascade is the backstop for uncurated typos).
306
- "manyn": "many", "mnay": "many", "amny": "many", "mnany": "many",
307
- "coutn": "count", "conut": "count", "cuont": "count", "ocunt": "count",
308
- "numer": "number", "nubmer": "number", "numbr": "number", "nmuber": "number",
309
- "lst": "list", "lsit": "list", "ilst": "list",
310
- "shwo": "show", "hsow": "show",
311
- "dispaly": "display", "dsiplay": "display",
312
- "funtcions": "functions", "funciton": "function", "funcitons": "functions",
313
- });
314
-
315
- /** Words used INCORRECTLY but with clear intent, mapped to the canonical schema
316
- * term — used like synonyms by the asker, but they are corrections of usage, not
317
- * alternative names (which is why they live here and not in ENTITY_TO_TYPE).
318
- * Only mapped where intent is unambiguous in a code-graph question; words
319
- * deliberately NOT mapped are logged in the omitted-on-purpose block at the end
320
- * of this file. */
321
- export const WRONG_WORDS = Object.freeze({
322
- // neither a folder nor a directory grain exists in this graph — in "which
323
- // folders import X" the only honest referent is the module/file grain.
324
- "folder": "module", "folders": "modules",
325
- "directory": "module", "directories": "modules",
326
- // unambiguous abbreviation of an entity noun this grammar owns
327
- "func": "function", "funcs": "functions",
328
- // VCS terms whose canonical schema class here is Commit
329
- "changeset": "commit", "changesets": "commits",
330
- "revision": "commit", "revisions": "commits",
331
- // code-graph "property" is the Attribute grain in this schema
332
- "property": "attribute", "properties": "attributes",
333
- });
334
-
335
- /** Trailing g-drop ("callin'", "hittin'") — a plain suffix restore, not a
336
- * lemmatiser: -in' -> -ing, applied per-word during normalization. The
337
- * apostrophe is REQUIRED (not optional): bare "-in" endings with no
338
- * apostrophe are real English words far more often than dropped g's
339
- * ("cabin", "robin", "chin", "twin") — restoring those would be a false
340
- * positive the anchored templates never had to worry about. No trailing
341
- * `\b` after the apostrophe: `'` is a non-word character, so a `\b` there
342
- * can never match the (non-word) whitespace/end-of-string that follows —
343
- * the apostrophe itself already delimits the word, a second boundary check
344
- * after it is redundant and unmatchable. */
345
- export const G_DROP = /\b([a-z]{3,})in'/gi;
346
-
347
- /** Words stripped during normalization/keyword-spotting once they carry no
348
- * grammatical weight for this grammar — greetings, politeness, hedges, and
349
- * the discourse fillers a spoken-style question picks up. Never strips a
350
- * relation verb, entity noun, or modifier (those are checked first). */
351
- export const FILLER_WORDS = Object.freeze([
352
- "um", "uh", "erm", "so", "like", "yo", "hey", "bru", "bro", "fam", "mate",
353
- "please", "could you", "can you", "would you", "tell me", "i wonder",
354
- "just wondering", "quickly", "real quick", "kinda", "sorta",
355
- ]);
356
-
357
- /** Deictic/pronoun terms that refer to a context entity rather than naming one
358
- * directly — resolved against an optional `contextId` (ask.mjs §keyword-
359
- * spotting; wired from the graph viewer's currently-selected node, when the
360
- * chat panel is asking "about" whatever the user last clicked). With no
361
- * context available (the bare CLI/MCP surface), these produce an honest
362
- * "which node does 'this' refer to?" miss — never a guess. */
363
- export const CONTEXT_PRONOUNS = Object.freeze(["this", "it", "that", "here", "this one", "that one"]);
364
-
365
- // ---- §3.6 negation frames — a SMALL, pattern-based set of recognized
366
- // double-negative / negative-rhetorical constructions, normalized to the
367
- // affirmative form of the SAME question (not a general negation-scope parser
368
- // — ELIZA/PARRY's own scope discipline: a handful of recognized frames, not
369
- // an attempt at full logical negation handling). Each frame is tried in
370
- // order; the first match rewrites the whole string and normalization stops
371
- // (a second negation frame firing on an already-rewritten string would be a
372
- // design smell, not a feature). ----
373
- export const NEGATION_FRAMES = Object.freeze([
374
- // "there ain't nothin' calling it" / "there isn't nothing that calls it"
375
- // -> "what calls it"
376
- { re: /\bthere\s+is\s+not\s+(?:anything|nothing)\s+(?:that\s+)?(\S.*)$/i, to: (m) => `what ${m[1]}` },
377
- // "there's no module that imports auth" -> "what imports auth"
378
- { re: /\bthere\s+is\s+no\s+\S+\s+(?:that\s+)?(\S.*)$/i, to: (m) => `what ${m[1]}` },
379
- // "isn't there anything calling it" -> "what calls it" (question-inverted form)
380
- { re: /\bis\s+not\s+there\s+(?:anything|nothing)\s+(?:that\s+)?(\S.*)$/i, to: (m) => `what ${m[1]}` },
381
- // "nobody/nothing calls it, does it" (tag-question double-negative) -> "what calls it"
382
- { re: /\b(?:nobody|nothing|no one)\s+(\S.*?)(?:,?\s+does\s+it\??|,?\s+do\s+they\??)?$/i, to: (m) => `what ${m[1]}` },
383
- ]);
384
-
385
- // ---- commit-content frames (2026-07-02, operator screenshot: "what was in commit
386
- // ef74e44e25c8" missed on the live site) — "what was/is in commit <sha>", "what's
387
- // in <sha>" (the contraction table has already expanded "what's" by the time
388
- // frames run), "what went into <sha>", "what made it into commit <sha>" all mean
389
- // the canonical commit-subject question "what did <sha> touch", so they are
390
- // REWRITTEN to it before either parse strategy runs: the same mechanism and scope
391
- // discipline as NEGATION_FRAMES (a small closed pattern set, first match wins),
392
- // which also makes both strategies parse the family for free. The sha-shaped tail
393
- // is REQUIRED so these frames only ever fire on an actual commit reference: over a
394
- // non-sha object ("what was in walk.mjs") the frame does not match and the text is
395
- // left for the ordinary grammar to handle as it already does, rather than being
396
- // bent into a commit-subject touches query that would then blank. ----
397
- export const COMMIT_CONTENT_FRAMES = Object.freeze([
398
- // "what was in commit ef74e44e25c8" / "what is in ef74e44e" / "what's in commit <sha>"
399
- { 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` },
400
- // "what went into commit ef74e44e25c8" / casual "what got into <sha>"
401
- { re: /^what\s+(?:went|got)\s+into\s+((?:commit\s+)?[0-9a-f]{7,40})\??$/i, to: (m) => `what did ${m[1]} touch` },
402
- // "what made it into commit ef74e44e25c8"
403
- { re: /^what\s+made\s+it\s+into\s+((?:commit\s+)?[0-9a-f]{7,40})\??$/i, to: (m) => `what did ${m[1]} touch` },
404
- ]);
405
-
406
- // ---- §7 meta-vocabulary — trigger phrases for the "meta" query shape (asking what a
407
- // graph vocabulary term itself means: "what does cochange mean", "what does mgx:
408
- // callsSymbol mean"). Deliberately NOT folded into RELATIONS/VERB_TO_KIND: these
409
- // phrases don't name a graph relation kind for edgesOfKind to traverse, they name
410
- // "explain this term" intent. Adding them to VERB_TO_KIND would make
411
- // parseKeywordSpot's decomposition matcher independently derive a *different* shape
412
- // ("forward", the only shape its before-only-text case produces) for the very same
413
- // sentence the anchored meta template resolves to shape:"meta" — a spurious strategy
414
- // DISAGREEMENT (ambiguousParse) on every meta question. So this table is read directly
415
- // by ask.mjs's own meta template, never merged into the shared verb tables. ----
416
- export const META_MEANING_VERBS = Object.freeze([
417
- "mean", "means", "stand for", "stands for", "signify", "signifies",
418
- "represent", "represents", "refer to", "refers to",
419
- ]);
420
-
421
- // ---- §compositional grammar vocabulary (PLAN §5.16 P3 — the ask engine's step up
422
- // from ELIZA keyword-spotting to a real recursive-descent grammar over CLAUSES).
423
- // ask.mjs's parseComposite reads these tables to recognize the compositional
424
- // MARKERS — relative clauses, boolean connectives, subject qualifiers, aggregates,
425
- // superlatives, and anaphora — that compose the SAME closed clause vocabulary
426
- // (RELATIONS/ENTITY_TO_TYPE above) into multi-hop / set-algebra queries. The
427
- // grammar COMPOSES the closed vocabulary; it never opens it. Every table here is
428
- // curated + closed, same discipline as everything above: a marker the tables don't
429
- // carry is an honest miss, never a guess. ----
430
-
431
- /** Relative-clause introducers: "<noun> that/which/who <predicate>". Only ever
432
- * read as a relative pronoun when it sits AFTER a noun and BEFORE a predicate
433
- * (ask.mjs enforces both) — "that" is also a CONTEXT_PRONOUN ("what calls that"),
434
- * so position, not mere presence, decides. This gates the nested/relative and
435
- * same-subject boolean shapes: their absence is exactly what keeps the bare
436
- * reverse-template query "which classes extends Base and couples to logging" out
437
- * of the compositional path (it stays the existing two-strategy ambiguous parse). */
438
- export const RELATIVE_PRONOUNS = Object.freeze(["that", "which", "who"]);
439
-
440
- /** Placeholder object nouns — the indefinite "something" in "what calls something
441
- * that imports X": they name NO entity, they stand in for the inner clause's
442
- * result set. Mapped to entityType null (any grain); a real entity noun in the
443
- * same slot ("what calls modules that import X") narrows the grain instead. */
444
- export const PLACEHOLDER_NOUNS = Object.freeze([
445
- "something", "anything", "everything", "somethings", "things", "thing",
446
- "entities", "entity", "nodes", "node", "stuff", "code",
447
- // "symbol(s)" is a grain-agnostic stand-in for any code entity — "exported
448
- // symbols of X" means whatever X defines, at any grain, filtered by the qualifier.
449
- "symbol", "symbols",
450
- ]);
451
-
452
- /** Boolean connectives over same-subject clauses -> a set operation on result ids.
453
- * "and" = intersection, "or" = union, "but not"/"and not"/"without"/"except" =
454
- * difference. Multi-word keys are matched longest-first by ask.mjs so "but not"
455
- * wins over a bare "not". Left-associative in ask.mjs's fold. */
456
- export const BOOLEAN_CONNECTIVES = Object.freeze({
457
- "but not": "difference", "and not": "difference", "except": "difference",
458
- "without": "difference",
459
- "and": "intersection", "plus": "intersection",
460
- "or": "union",
461
- });
462
-
463
- /** Subject QUALIFIERS (adjectives) -> a post-filter over the result set, read off
464
- * attributes/edges the graph already carries (codegraph.mjs): `visibility`
465
- * (private/protected present; public is the default/absent), `isStatic`/
466
- * `isConstant`/`isAbstract` boolean attrs, the reexports edge set (exported), and
467
- * the tests edge set (tested/untested). `isAbstract` is never populated by any
468
- * extractor today (codegraph.mjs says so) — "abstract methods" therefore honestly
469
- * returns an empty set, not an error, exactly like any other zero-hit answer. An
470
- * UNKNOWN qualifier is an honest miss naming these supported ones (ask.mjs). */
471
- export const QUALIFIERS = Object.freeze({
472
- public: { via: "visibility", value: "public" },
473
- private: { via: "visibility", value: "private" },
474
- protected: { via: "visibility", value: "protected" },
475
- static: { via: "attr", attr: "isStatic" },
476
- abstract: { via: "attr", attr: "isAbstract" },
477
- constant: { via: "attr", attr: "isConstant" },
478
- exported: { via: "exported" },
479
- "re-exported": { via: "exported" },
480
- tested: { via: "tested", value: true },
481
- covered: { via: "tested", value: true },
482
- untested: { via: "tested", value: false },
483
- uncovered: { via: "tested", value: false },
484
- });
485
-
486
- /** Aggregate/count triggers: "how many <kind> …", "count <kind>s", "number of
487
- * <kind>". Answered by counting a class of individuals or a clause's result set —
488
- * no header magic, a straight count over the graph (ask.mjs). Register-spread the
489
- * same way RELATIONS' verbs are (2026-07-02): the count question has a formal
490
- * ("how many"/"number of"), a neutral/imperative ("count"/"count up"), and a
491
- * quantity ("quantity of"/"total number of") register a developer actually types.
492
- * Judgement calls, kept OUT deliberately: bare "tally"/"sum"/"total" — those are
493
- * already mapped to "count" by CASCADE_SYNONYMS (ask.mjs's relaxation layer), and a
494
- * cascade test asserts "tally the classes" reaches the count via that synonym path,
495
- * so promoting them to direct triggers here would both duplicate the mapping and
496
- * break that test; and single-word "total"/"sum" would false-match identifier
497
- * fragments ("total price", "sum of squares") the count intent never meant. */
498
- export const AGGREGATE_TRIGGERS = Object.freeze([
499
- // formal ("the number of classes" reaches "number of" once the cascade strips the
500
- // leading article — keeping the trigger list clear of "the" so it never enters
501
- // CONTENT_VOCAB and blocks the article's own noise-strip)
502
- "how many", "how much", "how many of", "number of",
503
- "total number of", "quantity of",
504
- // neutral / imperative
505
- "count", "count up", "count of", "tot up",
506
- ]);
507
-
508
- /** LIST triggers (2026-07-02, list shape) — the many ways a developer asks to SEE the
509
- * individuals of a kind ("list functions", "show me the classes", "what are the
510
- * modules"). Read by ask.mjs's parseList (a sibling of the count node): a trigger
511
- * followed by an entity kind noun lists that class (capped at OVERFLOW_CAP), a
512
- * trailing scope/predicate narrows it, and an unknown kind after a clear list trigger
513
- * is an honest miss naming the kinds. Two registers, wide-but-deliberate (the operator's
514
- * "err toward inclusion", bounded by the file-header discipline: a phrase earns its
515
- * place only if it genuinely means "enumerate these" and won't false-match an unrelated
516
- * identifier — parseList further requires a real entity kind to follow, so a stray
517
- * "show"/"name" in another question is never seized):
518
- * · IMPERATIVE — "<verb> [me/us] [the] <kind>". Bare determiners/objects (the/a/all/
519
- * me/us) after the verb are skipped by parseList's LIST_SKIP, so only the verb stem
520
- * is listed here (not every "... the"/"... all" inflection).
521
- * · INTERROGATIVE — "what/which are [the] <kind>"; the bare "what <kind>"/"which
522
- * <kind>" (+ optional "are there") form is handled directly in parseList, not here.
523
- * Kept OUT on purpose: "tell me" / "give me" collide only where normalizeQuery already
524
- * strips them as FILLER_WORDS ("tell me the classes" → "the classes"), so "tell me" is
525
- * omitted (it never survives to parseList); "gimme" is omitted because CONTRACTIONS
526
- * rewrites it to "give me" before parseList runs. */
527
- export const LIST_TRIGGERS = Object.freeze([
528
- // imperative — "<verb> [me/us] [the] <kind>"
529
- "list", "show", "show me", "show us", "display", "print", "print out",
530
- "dump", "enumerate", "name", "give me", "get me", "spit out", "rattle off",
531
- "run down", "run through", "ls",
532
- // interrogative — "what/which are [the] <kind>"
533
- "what are", "which are",
534
- ]);
535
-
536
- /** Superlative extremes -> ranking direction. "most/greatest/highest/biggest/
537
- * largest/most-connected" rank descending; "fewest/least/smallest/lowest" rank
538
- * ascending. Read by ask.mjs's parseSuperlative alongside EDGE_NOUN_TO_METRIC. */
539
- export const SUPERLATIVE_EXTREMES = Object.freeze({
540
- most: "most", greatest: "most", highest: "most", biggest: "most",
541
- largest: "most", "most-connected": "most", "most connected": "most",
542
- fewest: "fewest", least: "fewest", smallest: "fewest", lowest: "fewest",
543
- });
544
-
545
- /** Degree-metric nouns for superlatives ("which module has the most <noun>") ->
546
- * {kind, dir} over the SAME classified edge groups. dir "out" counts edges where
547
- * the ranked entity is the subject (its own imports/calls); "in" counts edges
548
- * where it is the object (its importers/callers/tests). "connections"/"edges"/
549
- * "connected" is the total (both directions, all structural kinds). A `sibling`
550
- * fine-grained kind is added to the tally when present (callers include the
551
- * symbol-grain callsSymbol edges, not just module-coarse calls); a `filter`
552
- * restricts the counted objects to one class ("methods"). */
553
- export const EDGE_NOUN_TO_METRIC = Object.freeze({
554
- imports: { kind: "imports", dir: "out" },
555
- dependencies: { kind: "imports", dir: "out" },
556
- importers: { kind: "imports", dir: "in" },
557
- dependents: { kind: "imports", dir: "in" },
558
- callers: { kind: "calls", dir: "in", sibling: "callsSymbol" },
559
- callees: { kind: "calls", dir: "out", sibling: "callsSymbol" },
560
- calls: { kind: "calls", dir: "out", sibling: "callsSymbol" },
561
- methods: { kind: "contains", dir: "out", filter: "Method" },
562
- members: { kind: "contains", dir: "out" },
563
- tests: { kind: "tests", dir: "in" },
564
- subclasses: { kind: "inherits", dir: "in" },
565
- connections: { kind: "*", dir: "both" },
566
- edges: { kind: "*", dir: "both" },
567
- connected: { kind: "*", dir: "both" },
568
- });
569
-
570
- /** Anaphora triggers over the PREVIOUS result set (ask()'s `prev` id array):
571
- * "which of those/them/these …", "how many of those …". The pronoun refers to the
572
- * last answer's ids, not a graph term — with no prev supplied it is an honest miss
573
- * (ask.mjs), never a guess, exactly like an unresolved context pronoun. */
574
- export const ANAPHORA_TRIGGERS = Object.freeze(["those", "them", "these"]);
575
-
576
- /** Membership relations for "<entity> of/in <term>" (qualifier/relative inner
577
- * clauses like "public methods of Widget", "untested functions in walk.mjs") ->
578
- * the forward edge kinds whose subject is <term> and whose objects are the
579
- * members. contains (Class->member) and defines (Module->symbol) are both tried;
580
- * the asked entity type narrows the result. */
581
- export const MEMBERSHIP_KINDS = Object.freeze(["contains", "defines"]);
582
-
583
- // ---- §progressive-relaxation cascade vocabulary (SHRDLU-in-a-code-graph, with a
584
- // Zork parser's forgiveness) — the three closed, curated tables ask.mjs's relaxParse
585
- // reads when the DIRECT parse of a query would MISS. The cascade only ever DROPS
586
- // noise/unmatched words or NORMALISES a near-canonical word to the closed vocabulary;
587
- // it never invents a term or guesses an entity, and it bottoms out in the same honest
588
- // rephrase hint. Every entry is hand-curated with inline provenance, same "closed is
589
- // deliberate" ethos as everything above: a word these tables don't carry is left for
590
- // the honest miss, never a general-English stoplist that would silently eat a real
591
- // code term. ----
592
-
593
- /** Politeness / filler / vocative / presentation-frame tokens the cascade may strip
594
- * ONE AT A TIME (leftmost first) when a query misses — but only ever a token that is
595
- * NOT itself a content vocabulary word (a relation verb, entity noun, modifier,
596
- * qualifier, aggregate/superlative trigger, …) and does NOT resolve to a graph entity
597
- * (ask.mjs guards both), so a module literally named "show" or "the" is never eaten.
598
- * This is a SUPERSET of the multi-word politeness FILLER_WORDS strips up-front during
599
- * normalization: those handle "could you"/"tell me"/"please" before either parse runs;
600
- * these single tokens catch what a spoken-style question keeps AFTER that pass — the
601
- * bare vocative ("matey"), the article ("the"/"a"), and the presentation frame words
602
- * ("show"/"me"/"list") that the compositional grammar already skips as FRAME_WORDS but
603
- * the keyword-spotting strategy does not, so an un-stripped "show me" otherwise
604
- * decomposes to a bogus ask{subject:"show me"}. Curated, not a general stoplist:
605
- * question words (what/which/…), connectives (and/or), and pronouns (this/it/that)
606
- * are deliberately ABSENT — they carry grammatical weight and must survive. */
607
- export const CASCADE_NOISE = Object.freeze([
608
- // articles / vague determiners (kept OUT of content vocab so they're strippable;
609
- // the aggregate/where parsers already tolerate a stray "the"/"a", so stripping is
610
- // belt-and-braces, not load-bearing)
611
- "the", "a", "an", "some",
612
- // topic lead-in filler — "what about the modules", "how about classes": "about"
613
- // carries no graph meaning here, so stripping it lets the bare kind noun surface for
614
- // the cascade's bare-kind-noun terminal rule (ask.mjs). ("what"/"how" are structural
615
- // question words the drop-pass keeps; only the "about" between them and the kind is
616
- // noise.) A module literally named "about" is safe-listed by relaxParse's resolvesExact
617
- // guard, same as every other noise token.
618
- "about",
619
- // politeness / hedges (single-token; multi-word "could you"/"please" etc. are
620
- // FILLER_WORDS, stripped earlier during normalization)
621
- "please", "pls", "plz", "kindly", "just", "simply", "maybe", "perhaps",
622
- "thanks", "thank", "ta", "cheers",
623
- // greetings a question sometimes opens with (chat.mjs owns standalone greetings;
624
- // here they're only stripped when embedded in an otherwise-real question)
625
- "hi", "hello", "hey", "yo", "hiya", "howdy", "ok", "okay",
626
- // vocatives / terms of address (the "matey" of the worked example, and its kin)
627
- "matey", "mate", "buddy", "pal", "dude", "man", "bro", "bru", "fam",
628
- "friend", "sir", "maam", "folks", "guys", "everyone", "dear",
629
- // presentation frames — the keyword-spotting strategy's blind spot: the
630
- // compositional grammar skips these as FRAME_WORDS, but "show me what imports X"
631
- // otherwise decomposes (via keyword-spot) to ask{subject:"show me"}. Stripping
632
- // them on a miss recovers the underlying reverse/forward question. ("count" is NOT
633
- // here — it is an aggregate trigger; "find"/"search" are here as presentation
634
- // verbs, not the seonix_search tool, which ask.mjs never dispatches.)
635
- "show", "tell", "give", "list", "find", "me", "us", "lemme",
636
- ]);
637
-
638
- /** Near-canonical words the cascade REWRITES to the closed vocabulary once noise and
639
- * unmatched tokens are gone (SYNONYM-NORMALISE, the cascade's third layer). Kept
640
- * deliberately TINY and aggregate-flavoured: the relation/entity synonyms a developer
641
- * actually types already live in RELATIONS/ENTITY_TO_TYPE (and "uses"/"depends on" are
642
- * mapped there); this table only closes the gap for the count family, whose triggers
643
- * (AGGREGATE_TRIGGERS) don't include the "tally the classes"/"total number of classes"
644
- * register. Each key is also kept OUT of the drop pass (ask.mjs treats a synonym key as
645
- * meaningful) so it survives to be normalised rather than dropped as unmatched. The
646
- * rewrite is guarded by ask.mjs (never applied to a token that resolves to a graph
647
- * entity), so a symbol named "total" is never bent into "count". */
648
- export const CASCADE_SYNONYMS = Object.freeze({
649
- tally: "count", tallies: "count", sum: "count", total: "count", totals: "count",
650
- });
651
-
652
- /** Explicit help / orientation requests — when the WHOLE query is one of these, ask.mjs
653
- * shows the rephrase hint DIRECTLY (the honest bottom of the cascade, reached on
654
- * demand) rather than pretending to answer or running the relaxation loop. A closed
655
- * set matched against the whole normalized query only, so "which functions call help"
656
- * (a real question about a symbol named "help") is untouched. Standalone greetings and
657
- * the chat "/help" command are chat.mjs's own surface; this is the bare CLI/MCP ask()
658
- * entry point's equivalent. */
659
- export const HELP_TRIGGERS = Object.freeze([
660
- "help", "help me", "how do i ask", "how do i use this", "what can i ask",
661
- "what can you ask", "usage", "commands", "examples", "syntax", "options",
662
- ]);
663
-
664
- // ---- omitted-on-purpose (judgment calls, not oversights) -------------------
665
- // "runs"/"executes" (calls) are common English words with many non-code
666
- // senses — accepted anyway, formal-template AND keyword-spotting alike,
667
- // because a misparse costs nothing beyond an honest object-miss
668
- // (resolveObject never guesses); the risk is bounded by the render layer,
669
- // not by narrowing the vocabulary.
670
- // NOT added: "needs"/"wants" for imports (too weakly code-specific — "which
671
- // modules need auth" reads as a feature request, not a graph query, in a way
672
- // "which modules depend on auth" doesn't); "wraps"/"decorates" for calls (a
673
- // real but distinct relationship codegraph.mjs doesn't classify separately —
674
- // adding the phrase would silently misroute it onto plain call edges);
675
- // "belongs to" for contains (already claimed by a hypothetical membership
676
- // verb elsewhere — kept out to avoid a future collision if one is added).
677
- // NOT added: a cochange gerund ("co-changing with") — every cochange phrase
678
- // is already 2-3 words with its own internal "-ing"/"-s" (e.g. "changes
679
- // alongside"), so g-drop normalization has no bare stem to dialectally
680
- // contract in the first place; only relations with a genuinely bare-verb
681
- // casual form ("call", "touch") needed one.
682
- // WRONG_WORDS not mapped (judgment calls): "script(s)" (routinely names a real
683
- // module/identifier — rewriting it could corrupt an object term, and file/files
684
- // already covers the honest synonym); "package(s)" (a genuinely coarser grain
685
- // this graph doesn't model — mapping it to module would silently answer a
686
- // different question than the one asked); "fn" (too short and too often a real
687
- // identifier fragment to rewrite at a word boundary).