@polycode-projects/seonix 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +204 -25
- package/bin/cli.mjs +78 -18
- package/package.json +5 -3
- package/roslyn/Program.cs +79 -11
- package/src/ask-nlp.mjs +73 -0
- package/src/ask-vocab.mjs +370 -5
- package/src/ask.mjs +1523 -83
- package/src/browser.mjs +99 -19
- package/src/chat.mjs +785 -0
- package/src/codegraph.mjs +213 -5
- package/src/cs_treesitter.mjs +57 -34
- package/src/embed.mjs +191 -0
- package/src/extract.mjs +220 -39
- package/src/java_treesitter.mjs +82 -55
- package/src/jsts_tsc.mjs +51 -4
- package/src/nlp-bundle.mjs +120 -0
- package/src/prose-nlp.mjs +52 -0
- package/src/prose.mjs +42 -0
- package/src/schema-docs.mjs +29 -0
- package/src/server.mjs +27 -4
- package/src/sessions.mjs +206 -0
- package/src/temporal.mjs +70 -0
- package/src/timeline.mjs +160 -0
- package/src/viz.mjs +273 -83
package/src/ask-nlp.mjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// ask-nlp.mjs — the OPTIONAL wink-nlp adapter behind ask.mjs's lemma/POS tier.
|
|
2
|
+
//
|
|
3
|
+
// BOUNDARY (hard, do not move): this file is Node-only and is NEVER inlined into
|
|
4
|
+
// the viewer bundle. viz.mjs's askSource() inlines codegraph.mjs + ask-vocab.mjs +
|
|
5
|
+
// ask.mjs ONLY and strips their import lines, so the portable single-file HTML has
|
|
6
|
+
// no wink, no model, and no `nlpAdapter` binding at all — ask.mjs reaches this
|
|
7
|
+
// module exclusively through a `typeof nlpAdapter === "function"` guard and
|
|
8
|
+
// degrades to adapter-less parsing (lemma/POS tiers off; the curated tables and
|
|
9
|
+
// the bounded edit-distance tier still work, browser and Node alike). Keeping the
|
|
10
|
+
// ~1MB CJS model out of the page is the point of the split.
|
|
11
|
+
//
|
|
12
|
+
// wink-nlp and wink-eng-lite-web-model are CJS — loaded via createRequire, the
|
|
13
|
+
// same Node-module-resolution approach viz.mjs uses to locate cytoscape (resolve
|
|
14
|
+
// through the module system, never a guessed path). The require happens lazily on
|
|
15
|
+
// first use and failure is cached as null: a checkout without the optional deps
|
|
16
|
+
// installed answers exactly like the browser bundle, it never throws.
|
|
17
|
+
|
|
18
|
+
import { createRequire } from "node:module";
|
|
19
|
+
|
|
20
|
+
let cached; // undefined = not tried yet; null = unavailable (tried once, honestly off)
|
|
21
|
+
|
|
22
|
+
/** Lazily build the {lemma, posTags} adapter, or null when wink isn't loadable.
|
|
23
|
+
* Deterministic: wink-nlp's tagger/lemmatiser is a fixed model with no sampling,
|
|
24
|
+
* so the same input always yields the same tags/lemmas across processes. */
|
|
25
|
+
export function nlpAdapter() {
|
|
26
|
+
if (cached !== undefined) return cached;
|
|
27
|
+
try {
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const winkNLP = require("wink-nlp");
|
|
30
|
+
const model = require("wink-eng-lite-web-model");
|
|
31
|
+
const nlp = winkNLP(model);
|
|
32
|
+
const its = nlp.its;
|
|
33
|
+
cached = {
|
|
34
|
+
/** Lowercase lemma of a single token ("imported" -> "import"); the word
|
|
35
|
+
* itself when wink has nothing better (unknown words come back as-is). */
|
|
36
|
+
lemma(word) {
|
|
37
|
+
const w = String(word || "");
|
|
38
|
+
try {
|
|
39
|
+
const out = nlp.readDoc(w).tokens().out(its.lemma);
|
|
40
|
+
return String(out[0] || w).toLowerCase();
|
|
41
|
+
} catch {
|
|
42
|
+
return w.toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
/** UPOS tags aligned to the CALLER's word array. wink re-tokenizes (it
|
|
46
|
+
* splits "walk.mjs" into three tokens), so each input word is greedily
|
|
47
|
+
* matched to the run of wink tokens that spell it and takes its FIRST
|
|
48
|
+
* sub-token's tag; null per word on any surprise, never a throw. */
|
|
49
|
+
posTags(words) {
|
|
50
|
+
try {
|
|
51
|
+
const toks = nlp.readDoc(words.join(" ")).tokens();
|
|
52
|
+
const texts = toks.out();
|
|
53
|
+
const tags = toks.out(its.pos);
|
|
54
|
+
const out = [];
|
|
55
|
+
let k = 0;
|
|
56
|
+
for (const w of words) {
|
|
57
|
+
if (k >= texts.length) { out.push(null); continue; }
|
|
58
|
+
out.push(tags[k]);
|
|
59
|
+
let acc = texts[k];
|
|
60
|
+
k += 1;
|
|
61
|
+
while (acc.length < w.length && k < texts.length) { acc += texts[k]; k += 1; }
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
} catch {
|
|
65
|
+
return words.map(() => null);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
} catch {
|
|
70
|
+
cached = null;
|
|
71
|
+
}
|
|
72
|
+
return cached;
|
|
73
|
+
}
|
package/src/ask-vocab.mjs
CHANGED
|
@@ -35,8 +35,9 @@ export const RELATIONS = {
|
|
|
35
35
|
imports: {
|
|
36
36
|
comment: "Module -> Module: subject's import graph references object (usesComplexType).",
|
|
37
37
|
verbs: [
|
|
38
|
-
// formal/neutral
|
|
39
|
-
|
|
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",
|
|
40
41
|
"relies on", "rely on", "requires", "require", "references", "reference",
|
|
41
42
|
"pulls in", "pull in", "built on", "builds on", "build on", "uses code from",
|
|
42
43
|
// casual/colloquial
|
|
@@ -46,6 +47,21 @@ export const RELATIONS = {
|
|
|
46
47
|
"importing",
|
|
47
48
|
],
|
|
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
|
+
},
|
|
49
65
|
calls: {
|
|
50
66
|
comment: "Function/Method -> Function/Class (symbol-grain) or Module -> Module (coarse): subject invokes object.",
|
|
51
67
|
verbs: [
|
|
@@ -93,8 +109,13 @@ export const RELATIONS = {
|
|
|
93
109
|
"derives from", "derive from", "is a subclass of", "are a subclass of",
|
|
94
110
|
"is a kind of", "are a kind of", "is built off", "are built off",
|
|
95
111
|
"is built on top of", "are built on top of",
|
|
96
|
-
// gerund (g-drop normalization — §3.5
|
|
97
|
-
"
|
|
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",
|
|
98
119
|
],
|
|
99
120
|
},
|
|
100
121
|
touches: {
|
|
@@ -104,6 +125,27 @@ export const RELATIONS = {
|
|
|
104
125
|
"was changed in", "were changed in", "was edited in", "were edited in",
|
|
105
126
|
"was modified by", "were modified by", "was tweaked in", "were tweaked in",
|
|
106
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",
|
|
107
149
|
// gerund (g-drop normalization — §3.5)
|
|
108
150
|
"touching",
|
|
109
151
|
],
|
|
@@ -118,15 +160,31 @@ export const RELATIONS = {
|
|
|
118
160
|
],
|
|
119
161
|
},
|
|
120
162
|
reexports: {
|
|
121
|
-
comment: "Module ->
|
|
163
|
+
comment: "Module -> exported symbol: subject's public API surface (__all__/export list).",
|
|
122
164
|
verbs: [
|
|
123
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",
|
|
124
171
|
// gerund (g-drop normalization — §3.5)
|
|
125
172
|
"exporting",
|
|
126
173
|
],
|
|
127
174
|
},
|
|
128
175
|
};
|
|
129
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
|
+
|
|
130
188
|
/** relation token -> flat verb-phrase list, the shape ask.mjs's VERB_TO_KIND
|
|
131
189
|
* table needs (phrase -> kind), derived once from RELATIONS. */
|
|
132
190
|
export const VERB_TO_KIND = Object.freeze(
|
|
@@ -142,6 +200,18 @@ export const ENTITY_TO_TYPE = Object.freeze({
|
|
|
142
200
|
module: "Module", modules: "Module", file: "Module", files: "Module",
|
|
143
201
|
attribute: "Attribute", attributes: "Attribute", field: "Attribute", fields: "Attribute",
|
|
144
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",
|
|
145
215
|
commit: "Commit", commits: "Commit",
|
|
146
216
|
});
|
|
147
217
|
|
|
@@ -173,6 +243,83 @@ export const CONTRACTIONS = Object.freeze({
|
|
|
173
243
|
"gimme": "give me",
|
|
174
244
|
});
|
|
175
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
|
+
});
|
|
302
|
+
|
|
303
|
+
/** Words used INCORRECTLY but with clear intent, mapped to the canonical schema
|
|
304
|
+
* term — used like synonyms by the asker, but they are corrections of usage, not
|
|
305
|
+
* alternative names (which is why they live here and not in ENTITY_TO_TYPE).
|
|
306
|
+
* Only mapped where intent is unambiguous in a code-graph question; words
|
|
307
|
+
* deliberately NOT mapped are logged in the omitted-on-purpose block at the end
|
|
308
|
+
* of this file. */
|
|
309
|
+
export const WRONG_WORDS = Object.freeze({
|
|
310
|
+
// neither a folder nor a directory grain exists in this graph — in "which
|
|
311
|
+
// folders import X" the only honest referent is the module/file grain.
|
|
312
|
+
"folder": "module", "folders": "modules",
|
|
313
|
+
"directory": "module", "directories": "modules",
|
|
314
|
+
// unambiguous abbreviation of an entity noun this grammar owns
|
|
315
|
+
"func": "function", "funcs": "functions",
|
|
316
|
+
// VCS terms whose canonical schema class here is Commit
|
|
317
|
+
"changeset": "commit", "changesets": "commits",
|
|
318
|
+
"revision": "commit", "revisions": "commits",
|
|
319
|
+
// code-graph "property" is the Attribute grain in this schema
|
|
320
|
+
"property": "attribute", "properties": "attributes",
|
|
321
|
+
});
|
|
322
|
+
|
|
176
323
|
/** Trailing g-drop ("callin'", "hittin'") — a plain suffix restore, not a
|
|
177
324
|
* lemmatiser: -in' -> -ing, applied per-word during normalization. The
|
|
178
325
|
* apostrophe is REQUIRED (not optional): bare "-in" endings with no
|
|
@@ -223,6 +370,27 @@ export const NEGATION_FRAMES = Object.freeze([
|
|
|
223
370
|
{ re: /\b(?:nobody|nothing|no one)\s+(\S.*?)(?:,?\s+does\s+it\??|,?\s+do\s+they\??)?$/i, to: (m) => `what ${m[1]}` },
|
|
224
371
|
]);
|
|
225
372
|
|
|
373
|
+
// ---- commit-content frames (2026-07-02, operator screenshot: "what was in commit
|
|
374
|
+
// ef74e44e25c8" missed on the live site) — "what was/is in commit <sha>", "what's
|
|
375
|
+
// in <sha>" (the contraction table has already expanded "what's" by the time
|
|
376
|
+
// frames run), "what went into <sha>", "what made it into commit <sha>" all mean
|
|
377
|
+
// the canonical commit-subject question "what did <sha> touch", so they are
|
|
378
|
+
// REWRITTEN to it before either parse strategy runs: the same mechanism and scope
|
|
379
|
+
// discipline as NEGATION_FRAMES (a small closed pattern set, first match wins),
|
|
380
|
+
// which also makes both strategies parse the family for free. The sha-shaped tail
|
|
381
|
+
// is REQUIRED so these frames only ever fire on an actual commit reference: over a
|
|
382
|
+
// non-sha object ("what was in walk.mjs") the frame does not match and the text is
|
|
383
|
+
// left for the ordinary grammar to handle as it already does, rather than being
|
|
384
|
+
// bent into a commit-subject touches query that would then blank. ----
|
|
385
|
+
export const COMMIT_CONTENT_FRAMES = Object.freeze([
|
|
386
|
+
// "what was in commit ef74e44e25c8" / "what is in ef74e44e" / "what's in commit <sha>"
|
|
387
|
+
{ 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` },
|
|
388
|
+
// "what went into commit ef74e44e25c8" / casual "what got into <sha>"
|
|
389
|
+
{ re: /^what\s+(?:went|got)\s+into\s+((?:commit\s+)?[0-9a-f]{7,40})\??$/i, to: (m) => `what did ${m[1]} touch` },
|
|
390
|
+
// "what made it into commit ef74e44e25c8"
|
|
391
|
+
{ re: /^what\s+made\s+it\s+into\s+((?:commit\s+)?[0-9a-f]{7,40})\??$/i, to: (m) => `what did ${m[1]} touch` },
|
|
392
|
+
]);
|
|
393
|
+
|
|
226
394
|
// ---- §7 meta-vocabulary — trigger phrases for the "meta" query shape (asking what a
|
|
227
395
|
// graph vocabulary term itself means: "what does cochange mean", "what does mgx:
|
|
228
396
|
// callsSymbol mean"). Deliberately NOT folded into RELATIONS/VERB_TO_KIND: these
|
|
@@ -238,6 +406,197 @@ export const META_MEANING_VERBS = Object.freeze([
|
|
|
238
406
|
"represent", "represents", "refer to", "refers to",
|
|
239
407
|
]);
|
|
240
408
|
|
|
409
|
+
// ---- §compositional grammar vocabulary (PLAN §5.16 P3 — the ask engine's step up
|
|
410
|
+
// from ELIZA keyword-spotting to a real recursive-descent grammar over CLAUSES).
|
|
411
|
+
// ask.mjs's parseComposite reads these tables to recognize the compositional
|
|
412
|
+
// MARKERS — relative clauses, boolean connectives, subject qualifiers, aggregates,
|
|
413
|
+
// superlatives, and anaphora — that compose the SAME closed clause vocabulary
|
|
414
|
+
// (RELATIONS/ENTITY_TO_TYPE above) into multi-hop / set-algebra queries. The
|
|
415
|
+
// grammar COMPOSES the closed vocabulary; it never opens it. Every table here is
|
|
416
|
+
// curated + closed, same discipline as everything above: a marker the tables don't
|
|
417
|
+
// carry is an honest miss, never a guess. ----
|
|
418
|
+
|
|
419
|
+
/** Relative-clause introducers: "<noun> that/which/who <predicate>". Only ever
|
|
420
|
+
* read as a relative pronoun when it sits AFTER a noun and BEFORE a predicate
|
|
421
|
+
* (ask.mjs enforces both) — "that" is also a CONTEXT_PRONOUN ("what calls that"),
|
|
422
|
+
* so position, not mere presence, decides. This gates the nested/relative and
|
|
423
|
+
* same-subject boolean shapes: their absence is exactly what keeps the bare
|
|
424
|
+
* reverse-template query "which classes extends Base and couples to logging" out
|
|
425
|
+
* of the compositional path (it stays the existing two-strategy ambiguous parse). */
|
|
426
|
+
export const RELATIVE_PRONOUNS = Object.freeze(["that", "which", "who"]);
|
|
427
|
+
|
|
428
|
+
/** Placeholder object nouns — the indefinite "something" in "what calls something
|
|
429
|
+
* that imports X": they name NO entity, they stand in for the inner clause's
|
|
430
|
+
* result set. Mapped to entityType null (any grain); a real entity noun in the
|
|
431
|
+
* same slot ("what calls modules that import X") narrows the grain instead. */
|
|
432
|
+
export const PLACEHOLDER_NOUNS = Object.freeze([
|
|
433
|
+
"something", "anything", "everything", "somethings", "things", "thing",
|
|
434
|
+
"entities", "entity", "nodes", "node", "stuff", "code",
|
|
435
|
+
// "symbol(s)" is a grain-agnostic stand-in for any code entity — "exported
|
|
436
|
+
// symbols of X" means whatever X defines, at any grain, filtered by the qualifier.
|
|
437
|
+
"symbol", "symbols",
|
|
438
|
+
]);
|
|
439
|
+
|
|
440
|
+
/** Boolean connectives over same-subject clauses -> a set operation on result ids.
|
|
441
|
+
* "and" = intersection, "or" = union, "but not"/"and not"/"without"/"except" =
|
|
442
|
+
* difference. Multi-word keys are matched longest-first by ask.mjs so "but not"
|
|
443
|
+
* wins over a bare "not". Left-associative in ask.mjs's fold. */
|
|
444
|
+
export const BOOLEAN_CONNECTIVES = Object.freeze({
|
|
445
|
+
"but not": "difference", "and not": "difference", "except": "difference",
|
|
446
|
+
"without": "difference",
|
|
447
|
+
"and": "intersection", "plus": "intersection",
|
|
448
|
+
"or": "union",
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
/** Subject QUALIFIERS (adjectives) -> a post-filter over the result set, read off
|
|
452
|
+
* attributes/edges the graph already carries (codegraph.mjs): `visibility`
|
|
453
|
+
* (private/protected present; public is the default/absent), `isStatic`/
|
|
454
|
+
* `isConstant`/`isAbstract` boolean attrs, the reexports edge set (exported), and
|
|
455
|
+
* the tests edge set (tested/untested). `isAbstract` is never populated by any
|
|
456
|
+
* extractor today (codegraph.mjs says so) — "abstract methods" therefore honestly
|
|
457
|
+
* returns an empty set, not an error, exactly like any other zero-hit answer. An
|
|
458
|
+
* UNKNOWN qualifier is an honest miss naming these supported ones (ask.mjs). */
|
|
459
|
+
export const QUALIFIERS = Object.freeze({
|
|
460
|
+
public: { via: "visibility", value: "public" },
|
|
461
|
+
private: { via: "visibility", value: "private" },
|
|
462
|
+
protected: { via: "visibility", value: "protected" },
|
|
463
|
+
static: { via: "attr", attr: "isStatic" },
|
|
464
|
+
abstract: { via: "attr", attr: "isAbstract" },
|
|
465
|
+
constant: { via: "attr", attr: "isConstant" },
|
|
466
|
+
exported: { via: "exported" },
|
|
467
|
+
"re-exported": { via: "exported" },
|
|
468
|
+
tested: { via: "tested", value: true },
|
|
469
|
+
covered: { via: "tested", value: true },
|
|
470
|
+
untested: { via: "tested", value: false },
|
|
471
|
+
uncovered: { via: "tested", value: false },
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
/** Aggregate/count triggers: "how many <kind> …", "count <kind>s", "number of
|
|
475
|
+
* <kind>". Answered by counting a class of individuals or a clause's result set —
|
|
476
|
+
* no header magic, a straight count over the graph (ask.mjs). */
|
|
477
|
+
export const AGGREGATE_TRIGGERS = Object.freeze(["how many", "how much", "count", "number of", "count of"]);
|
|
478
|
+
|
|
479
|
+
/** Superlative extremes -> ranking direction. "most/greatest/highest/biggest/
|
|
480
|
+
* largest/most-connected" rank descending; "fewest/least/smallest/lowest" rank
|
|
481
|
+
* ascending. Read by ask.mjs's parseSuperlative alongside EDGE_NOUN_TO_METRIC. */
|
|
482
|
+
export const SUPERLATIVE_EXTREMES = Object.freeze({
|
|
483
|
+
most: "most", greatest: "most", highest: "most", biggest: "most",
|
|
484
|
+
largest: "most", "most-connected": "most", "most connected": "most",
|
|
485
|
+
fewest: "fewest", least: "fewest", smallest: "fewest", lowest: "fewest",
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
/** Degree-metric nouns for superlatives ("which module has the most <noun>") ->
|
|
489
|
+
* {kind, dir} over the SAME classified edge groups. dir "out" counts edges where
|
|
490
|
+
* the ranked entity is the subject (its own imports/calls); "in" counts edges
|
|
491
|
+
* where it is the object (its importers/callers/tests). "connections"/"edges"/
|
|
492
|
+
* "connected" is the total (both directions, all structural kinds). A `sibling`
|
|
493
|
+
* fine-grained kind is added to the tally when present (callers include the
|
|
494
|
+
* symbol-grain callsSymbol edges, not just module-coarse calls); a `filter`
|
|
495
|
+
* restricts the counted objects to one class ("methods"). */
|
|
496
|
+
export const EDGE_NOUN_TO_METRIC = Object.freeze({
|
|
497
|
+
imports: { kind: "imports", dir: "out" },
|
|
498
|
+
dependencies: { kind: "imports", dir: "out" },
|
|
499
|
+
importers: { kind: "imports", dir: "in" },
|
|
500
|
+
dependents: { kind: "imports", dir: "in" },
|
|
501
|
+
callers: { kind: "calls", dir: "in", sibling: "callsSymbol" },
|
|
502
|
+
callees: { kind: "calls", dir: "out", sibling: "callsSymbol" },
|
|
503
|
+
calls: { kind: "calls", dir: "out", sibling: "callsSymbol" },
|
|
504
|
+
methods: { kind: "contains", dir: "out", filter: "Method" },
|
|
505
|
+
members: { kind: "contains", dir: "out" },
|
|
506
|
+
tests: { kind: "tests", dir: "in" },
|
|
507
|
+
subclasses: { kind: "inherits", dir: "in" },
|
|
508
|
+
connections: { kind: "*", dir: "both" },
|
|
509
|
+
edges: { kind: "*", dir: "both" },
|
|
510
|
+
connected: { kind: "*", dir: "both" },
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
/** Anaphora triggers over the PREVIOUS result set (ask()'s `prev` id array):
|
|
514
|
+
* "which of those/them/these …", "how many of those …". The pronoun refers to the
|
|
515
|
+
* last answer's ids, not a graph term — with no prev supplied it is an honest miss
|
|
516
|
+
* (ask.mjs), never a guess, exactly like an unresolved context pronoun. */
|
|
517
|
+
export const ANAPHORA_TRIGGERS = Object.freeze(["those", "them", "these"]);
|
|
518
|
+
|
|
519
|
+
/** Membership relations for "<entity> of/in <term>" (qualifier/relative inner
|
|
520
|
+
* clauses like "public methods of Widget", "untested functions in walk.mjs") ->
|
|
521
|
+
* the forward edge kinds whose subject is <term> and whose objects are the
|
|
522
|
+
* members. contains (Class->member) and defines (Module->symbol) are both tried;
|
|
523
|
+
* the asked entity type narrows the result. */
|
|
524
|
+
export const MEMBERSHIP_KINDS = Object.freeze(["contains", "defines"]);
|
|
525
|
+
|
|
526
|
+
// ---- §progressive-relaxation cascade vocabulary (SHRDLU-in-a-code-graph, with a
|
|
527
|
+
// Zork parser's forgiveness) — the three closed, curated tables ask.mjs's relaxParse
|
|
528
|
+
// reads when the DIRECT parse of a query would MISS. The cascade only ever DROPS
|
|
529
|
+
// noise/unmatched words or NORMALISES a near-canonical word to the closed vocabulary;
|
|
530
|
+
// it never invents a term or guesses an entity, and it bottoms out in the same honest
|
|
531
|
+
// rephrase hint. Every entry is hand-curated with inline provenance, same "closed is
|
|
532
|
+
// deliberate" ethos as everything above: a word these tables don't carry is left for
|
|
533
|
+
// the honest miss, never a general-English stoplist that would silently eat a real
|
|
534
|
+
// code term. ----
|
|
535
|
+
|
|
536
|
+
/** Politeness / filler / vocative / presentation-frame tokens the cascade may strip
|
|
537
|
+
* ONE AT A TIME (leftmost first) when a query misses — but only ever a token that is
|
|
538
|
+
* NOT itself a content vocabulary word (a relation verb, entity noun, modifier,
|
|
539
|
+
* qualifier, aggregate/superlative trigger, …) and does NOT resolve to a graph entity
|
|
540
|
+
* (ask.mjs guards both), so a module literally named "show" or "the" is never eaten.
|
|
541
|
+
* This is a SUPERSET of the multi-word politeness FILLER_WORDS strips up-front during
|
|
542
|
+
* normalization: those handle "could you"/"tell me"/"please" before either parse runs;
|
|
543
|
+
* these single tokens catch what a spoken-style question keeps AFTER that pass — the
|
|
544
|
+
* bare vocative ("matey"), the article ("the"/"a"), and the presentation frame words
|
|
545
|
+
* ("show"/"me"/"list") that the compositional grammar already skips as FRAME_WORDS but
|
|
546
|
+
* the keyword-spotting strategy does not, so an un-stripped "show me" otherwise
|
|
547
|
+
* decomposes to a bogus ask{subject:"show me"}. Curated, not a general stoplist:
|
|
548
|
+
* question words (what/which/…), connectives (and/or), and pronouns (this/it/that)
|
|
549
|
+
* are deliberately ABSENT — they carry grammatical weight and must survive. */
|
|
550
|
+
export const CASCADE_NOISE = Object.freeze([
|
|
551
|
+
// articles / vague determiners (kept OUT of content vocab so they're strippable;
|
|
552
|
+
// the aggregate/where parsers already tolerate a stray "the"/"a", so stripping is
|
|
553
|
+
// belt-and-braces, not load-bearing)
|
|
554
|
+
"the", "a", "an", "some",
|
|
555
|
+
// politeness / hedges (single-token; multi-word "could you"/"please" etc. are
|
|
556
|
+
// FILLER_WORDS, stripped earlier during normalization)
|
|
557
|
+
"please", "pls", "plz", "kindly", "just", "simply", "maybe", "perhaps",
|
|
558
|
+
"thanks", "thank", "ta", "cheers",
|
|
559
|
+
// greetings a question sometimes opens with (chat.mjs owns standalone greetings;
|
|
560
|
+
// here they're only stripped when embedded in an otherwise-real question)
|
|
561
|
+
"hi", "hello", "hey", "yo", "hiya", "howdy", "ok", "okay",
|
|
562
|
+
// vocatives / terms of address (the "matey" of the worked example, and its kin)
|
|
563
|
+
"matey", "mate", "buddy", "pal", "dude", "man", "bro", "bru", "fam",
|
|
564
|
+
"friend", "sir", "maam", "folks", "guys", "everyone", "dear",
|
|
565
|
+
// presentation frames — the keyword-spotting strategy's blind spot: the
|
|
566
|
+
// compositional grammar skips these as FRAME_WORDS, but "show me what imports X"
|
|
567
|
+
// otherwise decomposes (via keyword-spot) to ask{subject:"show me"}. Stripping
|
|
568
|
+
// them on a miss recovers the underlying reverse/forward question. ("count" is NOT
|
|
569
|
+
// here — it is an aggregate trigger; "find"/"search" are here as presentation
|
|
570
|
+
// verbs, not the seonix_search tool, which ask.mjs never dispatches.)
|
|
571
|
+
"show", "tell", "give", "list", "find", "me", "us", "lemme",
|
|
572
|
+
]);
|
|
573
|
+
|
|
574
|
+
/** Near-canonical words the cascade REWRITES to the closed vocabulary once noise and
|
|
575
|
+
* unmatched tokens are gone (SYNONYM-NORMALISE, the cascade's third layer). Kept
|
|
576
|
+
* deliberately TINY and aggregate-flavoured: the relation/entity synonyms a developer
|
|
577
|
+
* actually types already live in RELATIONS/ENTITY_TO_TYPE (and "uses"/"depends on" are
|
|
578
|
+
* mapped there); this table only closes the gap for the count family, whose triggers
|
|
579
|
+
* (AGGREGATE_TRIGGERS) don't include the "tally the classes"/"total number of classes"
|
|
580
|
+
* register. Each key is also kept OUT of the drop pass (ask.mjs treats a synonym key as
|
|
581
|
+
* meaningful) so it survives to be normalised rather than dropped as unmatched. The
|
|
582
|
+
* rewrite is guarded by ask.mjs (never applied to a token that resolves to a graph
|
|
583
|
+
* entity), so a symbol named "total" is never bent into "count". */
|
|
584
|
+
export const CASCADE_SYNONYMS = Object.freeze({
|
|
585
|
+
tally: "count", tallies: "count", sum: "count", total: "count", totals: "count",
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
/** Explicit help / orientation requests — when the WHOLE query is one of these, ask.mjs
|
|
589
|
+
* shows the rephrase hint DIRECTLY (the honest bottom of the cascade, reached on
|
|
590
|
+
* demand) rather than pretending to answer or running the relaxation loop. A closed
|
|
591
|
+
* set matched against the whole normalized query only, so "which functions call help"
|
|
592
|
+
* (a real question about a symbol named "help") is untouched. Standalone greetings and
|
|
593
|
+
* the chat "/help" command are chat.mjs's own surface; this is the bare CLI/MCP ask()
|
|
594
|
+
* entry point's equivalent. */
|
|
595
|
+
export const HELP_TRIGGERS = Object.freeze([
|
|
596
|
+
"help", "help me", "how do i ask", "how do i use this", "what can i ask",
|
|
597
|
+
"what can you ask", "usage", "commands", "examples", "syntax", "options",
|
|
598
|
+
]);
|
|
599
|
+
|
|
241
600
|
// ---- omitted-on-purpose (judgment calls, not oversights) -------------------
|
|
242
601
|
// "runs"/"executes" (calls) are common English words with many non-code
|
|
243
602
|
// senses — accepted anyway, formal-template AND keyword-spotting alike,
|
|
@@ -256,3 +615,9 @@ export const META_MEANING_VERBS = Object.freeze([
|
|
|
256
615
|
// alongside"), so g-drop normalization has no bare stem to dialectally
|
|
257
616
|
// contract in the first place; only relations with a genuinely bare-verb
|
|
258
617
|
// casual form ("call", "touch") needed one.
|
|
618
|
+
// WRONG_WORDS not mapped (judgment calls): "script(s)" (routinely names a real
|
|
619
|
+
// module/identifier — rewriting it could corrupt an object term, and file/files
|
|
620
|
+
// already covers the honest synonym); "package(s)" (a genuinely coarser grain
|
|
621
|
+
// this graph doesn't model — mapping it to module would silently answer a
|
|
622
|
+
// different question than the one asked); "fn" (too short and too often a real
|
|
623
|
+
// identifier fragment to rewrite at a word boundary).
|