@polycode-projects/the-mechanical-code-talker 1.9.2 → 1.10.1

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.
Files changed (80) hide show
  1. package/README.md +441 -202
  2. package/bin/tmct.mjs +126 -1
  3. package/package.json +4 -2
  4. package/src/answer-variants.mjs +8 -36
  5. package/src/ask-browser-entry.mjs +5 -23
  6. package/src/ask-browser.bundle.js +1 -2
  7. package/src/ask-nlp.mjs +9 -23
  8. package/src/ask-vocab.mjs +139 -589
  9. package/src/ask.mjs +627 -1729
  10. package/src/chat.mjs +1684 -2872
  11. package/src/cli-args.mjs +14 -28
  12. package/src/codegraph.mjs +236 -644
  13. package/src/completions/complete.mjs +18 -62
  14. package/src/completions/graph-adapter.mjs +14 -60
  15. package/src/completions/group.mjs +12 -68
  16. package/src/completions/infer.mjs +38 -126
  17. package/src/completions/prune.mjs +17 -70
  18. package/src/completions/rank.mjs +16 -69
  19. package/src/completions/search.mjs +8 -31
  20. package/src/concept.mjs +32 -88
  21. package/src/conformance.mjs +11 -15
  22. package/src/corpus/conceptnet.mjs +31 -89
  23. package/src/corpus/templates.mjs +19 -45
  24. package/src/corpus/unknown-ingest.mjs +31 -92
  25. package/src/embed.mjs +10 -22
  26. package/src/extensions.mjs +50 -154
  27. package/src/finish.mjs +35 -91
  28. package/src/grammar/ace.mjs +16 -40
  29. package/src/grammar/assert.mjs +1 -1
  30. package/src/grammar/lexicon-core.json +1 -1
  31. package/src/grammar/lexicon.mjs +9 -27
  32. package/src/graph-merge.mjs +2 -3
  33. package/src/hash.mjs +6 -14
  34. package/src/index.mjs +6 -10
  35. package/src/init.mjs +38 -125
  36. package/src/interpret/fuzzy.mjs +10 -29
  37. package/src/interpret/merge.mjs +9 -27
  38. package/src/interpret/normalize.mjs +137 -585
  39. package/src/interpret/pipeline.mjs +23 -71
  40. package/src/interpret/strategies/ace.mjs +7 -31
  41. package/src/interpret/strategies/constructions.mjs +14 -41
  42. package/src/interpret/strategies/grammar.mjs +21 -60
  43. package/src/interpret/strategies/keywords.mjs +42 -131
  44. package/src/interpret/strategies/noise-strip.mjs +18 -89
  45. package/src/memory/bias.mjs +11 -54
  46. package/src/memory/blocks.mjs +18 -69
  47. package/src/memory/core.mjs +171 -591
  48. package/src/memory/fold.mjs +0 -0
  49. package/src/memory/inspect.mjs +7 -25
  50. package/src/memory/shacl.mjs +10 -39
  51. package/src/memory/trust.mjs +26 -127
  52. package/src/memory-ask-browser-entry.mjs +7 -30
  53. package/src/memory-ask-browser.bundle.js +1 -1
  54. package/src/paraphrase.mjs +20 -53
  55. package/src/planning.mjs +15 -157
  56. package/src/prose-nlp.mjs +4 -17
  57. package/src/prose.mjs +19 -67
  58. package/src/providers/bootstrap.mjs +1 -2
  59. package/src/providers/fixture.mjs +1 -2
  60. package/src/providers/graph-service.mjs +28 -59
  61. package/src/repository-interface.mjs +6 -8
  62. package/src/router/drive.mjs +183 -0
  63. package/src/router/goal-reasoner.mjs +66 -231
  64. package/src/router/guardrail.mjs +20 -58
  65. package/src/router/planner.mjs +15 -46
  66. package/src/router/registry.mjs +13 -43
  67. package/src/router/resolver.mjs +46 -131
  68. package/src/router/results.mjs +231 -0
  69. package/src/schema-docs.mjs +10 -27
  70. package/src/server-http.mjs +10 -19
  71. package/src/server.mjs +22 -28
  72. package/src/sessions.mjs +15 -30
  73. package/src/source-slice.mjs +5 -7
  74. package/src/source.mjs +10 -20
  75. package/src/syllogise.mjs +187 -575
  76. package/src/telemetry.mjs +3 -3
  77. package/src/toml-config.mjs +4 -4
  78. package/src/tui/app.mjs +9 -19
  79. package/src/viz.mjs +66 -123
  80. package/src/wink-model.mjs +10 -24
package/src/ask-vocab.mjs CHANGED
@@ -1,76 +1,19 @@
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, `.tmctignore`'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: tmct'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
- // REVERSE inherits phrasings (Seonix Batch 2 Fix 2) — "is X a superclass of Y" /
35
- // "is X a parent class of Y" name the SAME `inherits` relation as the forward
36
- // "is X a subclass of Y" verbs above, but with subject/object semantically
37
- // SWAPPED: the questioner's X is the base, not the derived class. The "a"-forms
38
- // (below) are folded into RELATIONS.inherits.verbs so VERB_TO_KIND maps them to
39
- // kind "inherits" exactly like every forward verb — every existing
40
- // inherits-consuming template/renderer keeps working unmodified — but ALSO kept
41
- // as this separate exported list so the strategies that build "ask" shapes
42
- // (subject-before/object-after by regex capture POSITION, not semantic
43
- // direction — see grammar.mjs T1 and keywords.mjs's decomposition) can detect a
44
- // reverse verb and swap subject/object at parse time, before evaluation ever
45
- // sees it.
46
- //
47
- // Bare "superclass"/"superclasses" (single word, no "is … of" wrapper) are ALSO
48
- // folded in: keyword-spot's decomposition (keywords.mjs) finds a verb phrase as
49
- // a CONTIGUOUS run of words, and the interrogative word order ("is Base a
50
- // superclass of Widget") puts the subject BETWEEN "is" and "a superclass of",
51
- // breaking that contiguity for the 4-word phrase — exactly mirroring why the
52
- // forward direction already carries bare "subclass"/"subclasses" alongside its
53
- // own "is a subclass of" (both above, in RELATIONS.inherits.verbs): the bare
54
- // stem is what actually lets keyword-spot's decomposition recognize the
55
- // aux-first question form ("is Foo a subclass of Bar" only resolves today via
56
- // that same bare "subclass" stem — see grammar.mjs T1's own comment).
57
- //
58
- // The "the"-DEFINITE forms ("is the superclass of", "are the superclass of",
59
- // "is the parent class of") are named in INHERITS_REVERSE_VERBS below (the
60
- // caller-requested literal set) but deliberately NOT folded into
61
- // RELATIONS.inherits.verbs / VERB_TO_KIND: ask.mjs's CONTENT_VOCAB is built by
62
- // splitting every VERB_TO_KIND key into its individual words (wordsOf), so a
63
- // verb phrase containing the bare word "the" would leak "the" itself into
64
- // CONTENT_VOCAB — and the progressive-relaxation cascade's NOISE-STRIP layer
65
- // treats anything in CONTENT_VOCAB as un-strippable content, not noise. Verified
66
- // live: folding the "the"-forms in broke test/ask-cascade.test.mjs's pinned
67
- // NOISE-STRIP/DROP-UNMATCHED/SYNONYM-NORMALISE cases (each expects "the" to stay
68
- // strippable) and test/chatflow-tier2.test.mjs's ESL-pronoun case, all of which
69
- // rely on the pre-existing invariant that NO verb phrase in this file's tables
70
- // ever contains the bare word "the" (confirmed true before this change). So the
71
- // "the"-forms stay honest misses for now — "is a superclass of"/"are a
72
- // superclass of" (the forms this Batch's own tests exercise) work; reinstating
73
- // the "the"-forms would need a CONTENT_VOCAB fix first, out of this fix's scope.
1
+ // ask-vocab.mjs — the closed, curated vocabulary `ask.mjs`'s grammar, rephrase
2
+ // hint, and renderer noun forms derive from. No NLP library or lemmatiser:
3
+ // plain phrase lists feeding the same fixed-precedence regex grammar.
4
+
5
+ /** relation token -> { comment, verbs[] }. Each `verbs` list spans formal,
6
+ * neutral, and casual phrasings of the same relation; a misparse costs
7
+ * nothing beyond an honest object-miss (resolveObject never guesses). */
8
+ // Reverse `inherits` phrasings ("is X a superclass of Y") name the same
9
+ // relation as the forward verbs above but with subject/object swapped, so
10
+ // they're kept in INHERITS_REVERSE_VERB_LIST too: strategies that build "ask"
11
+ // shapes use it to detect a reverse verb and swap subject/object at parse
12
+ // time. The "the"-definite forms ("is the superclass of") are deliberately
13
+ // NOT folded into RELATIONS.inherits.verbs/VERB_TO_KIND: CONTENT_VOCAB is
14
+ // built by splitting every VERB_TO_KIND key into words, and no verb phrase
15
+ // here may contain the bare word "the" without breaking the relaxation
16
+ // cascade's noise-strip pass so those forms stay honest misses.
74
17
  const INHERITS_REVERSE_VERB_LIST = [
75
18
  "is a superclass of",
76
19
  "are a superclass of",
@@ -83,30 +26,25 @@ export const RELATIONS = {
83
26
  imports: {
84
27
  comment: "Module -> Module: subject's import graph references object (usesComplexType).",
85
28
  verbs: [
86
- // formal/neutral ("uses"/"use" moved to the `uses` union family, 2026-07-02 —
87
- // "uses code from" stays here: its phrasing is specifically import-flavored)
29
+ // formal/neutral ("uses code from" stays here: its phrasing is
30
+ // specifically import-flavored)
88
31
  "couples to", "couple to", "depends on", "imports", "import",
89
32
  "relies on", "rely on", "requires", "require", "references", "reference",
90
33
  "pulls in", "pull in", "built on", "builds on", "build on", "uses code from",
91
34
  // casual/colloquial
92
35
  "grabs", "grab", "pulls from", "pull from", "leans on", "lean on",
93
36
  "is wired to", "are wired to", "is hooked up to", "are hooked up to",
94
- // gerund (g-drop normalization turns dialectal "importin'" into this — §3.5)
37
+ // gerund (g-drop normalization turns dialectal "importin'" into this)
95
38
  "importing",
96
39
  ],
97
40
  },
98
- // "uses" is a QUERY-side union family, not a stored predicate (2026-07-02 query
99
- // families): "what uses X" honestly means BOTH the import graph and the call
100
- // graph, so ask.mjs traverses it as imports + calls + callsSymbol together
101
- // (KIND_UNIONS there). The verbs moved here FROM imports — "which modules use X"
102
- // still answers with the importing modules (the asked Module grain filters the
103
- // union down to module-grain subjects), and "what uses <function>" now also
104
- // reaches the symbol-grain callers instead of silently ignoring them.
41
+ // query-side union, not a stored predicate: ask.mjs traverses "uses" as
42
+ // imports + calls + callsSymbol together (KIND_UNIONS).
105
43
  uses: {
106
44
  comment: "query-side union: imports (Module->Module) + calls (Module->Module) + callsSymbol (fn->fn).",
107
45
  verbs: [
108
46
  "uses", "use", "used by", "makes use of", "make use of",
109
- // gerund (g-drop normalization — §3.5)
47
+ // gerund (g-drop normalization)
110
48
  "using",
111
49
  ],
112
50
  },
@@ -119,7 +57,7 @@ export const RELATIONS = {
119
57
  "calls", "call", "runs", "run", "executes", "execute",
120
58
  // casual/colloquial
121
59
  "hits", "hit", "triggers", "trigger", "fires", "fire", "kicks off", "kick off",
122
- // gerund (g-drop normalization turns dialectal "callin'" into this — §3.5)
60
+ // gerund (g-drop normalization turns dialectal "callin'" into this)
123
61
  "calling",
124
62
  ],
125
63
  },
@@ -128,7 +66,7 @@ export const RELATIONS = {
128
66
  verbs: [
129
67
  "defines", "define", "declares", "declare",
130
68
  "has", "have", "holds", "hold",
131
- // gerund (g-drop normalization — §3.5)
69
+ // gerund (g-drop normalization)
132
70
  "defining",
133
71
  ],
134
72
  },
@@ -137,7 +75,7 @@ export const RELATIONS = {
137
75
  verbs: [
138
76
  "contains", "contain", "lives in", "live in", "is defined in", "are defined in",
139
77
  "is part of", "are part of", "sits in", "sit in", "sits inside", "sit inside",
140
- // gerund (g-drop normalization — §3.5)
78
+ // gerund (g-drop normalization)
141
79
  "containing",
142
80
  ],
143
81
  },
@@ -146,7 +84,7 @@ export const RELATIONS = {
146
84
  verbs: [
147
85
  "tests", "test", "covers", "cover", "verifies", "verify", "exercises", "exercise",
148
86
  "checks", "check", "makes sure of", "make sure of",
149
- // gerund (g-drop normalization — §3.5)
87
+ // gerund (g-drop normalization)
150
88
  "testing",
151
89
  ],
152
90
  },
@@ -154,32 +92,14 @@ export const RELATIONS = {
154
92
  comment: "Class -> Class: subject's declared base resolves to object (subclassOf).",
155
93
  verbs: [
156
94
  "inherits from", "inherit from",
157
- // bare "inherits"/"inherit" (Tier 6 playtest, §3b surface-variation axis):
158
- // this list's own SIBLING verb "extends"/"extend" already works bare, with
159
- // no "from" required, but "inherits"/"inherit" — arguably the MORE common
160
- // everyday phrasing of the two ("TaskController inherits Controller",
161
- // "does TaskController inherit Controller") — had no bare form at all,
162
- // only the "... from" variant. VERB_ALT's longest-first sort (already
163
- // relied on elsewhere in this file for the same reason) means "inherits
164
- // from"/"inherit from" still win whenever "from" actually follows, so
165
- // this is purely additive.
166
95
  "inherits", "inherit",
167
96
  "extends", "extend", "subclasses", "subclass",
168
97
  "derives from", "derive from", "is a subclass of", "are a subclass of",
169
98
  "is a kind of", "are a kind of", "is built off", "are built off",
170
99
  "is built on top of", "are built on top of",
171
- // gerund (g-drop normalization — §3.5, and the compositional grammar's
172
- // gerund-led boolean gate: "classes inheriting from Base but not tested").
173
- // Both the two-word "inheriting from" (so "from" is consumed into the verb
174
- // phrase, not the object term) and the bare "inheriting" (the single token
175
- // the gerund-lead check reads) are listed; longest-match-first prefers the
176
- // two-word form when "from" follows.
100
+ // gerund, incl. compositional grammar's gerund-led boolean gate
101
+ // ("classes inheriting from Base but not tested")
177
102
  "extending", "inheriting from", "inheriting", "subclassing", "extends from",
178
- // REVERSE phrasings (Seonix Batch 2 Fix 2, see INHERITS_REVERSE_VERB_LIST's own
179
- // comment above): "is/are a|the superclass/parent class of" — folded in here so
180
- // VERB_TO_KIND maps them to "inherits" like every other verb in this list; the
181
- // subject/object SWAP their direction requires is handled at parse time by the
182
- // strategies that build the "ask" shape, not here.
183
103
  ...INHERITS_REVERSE_VERB_LIST,
184
104
  ],
185
105
  },
@@ -190,28 +110,15 @@ export const RELATIONS = {
190
110
  "was changed in", "were changed in", "was edited in", "were edited in",
191
111
  "was modified by", "were modified by", "was tweaked in", "were tweaked in",
192
112
  "got changed in", "got edited in",
193
- // commit-question forms (2026-07-02, viewer commit-chat fix): the same touch
194
- // relation asked from the commit's side — "which changes touch commit <sha>",
195
- // "what did commit <sha> touch", "which functions changed in <sha>", "which
196
- // changes landed in commit <sha>", "what was touched by commit <sha>". Bare
197
- // "touch" completes the touched/touches pair for the "did … touch" auxiliary
198
- // form; the "by"/"in" phrases are the passive/locative counterparts of forms
199
- // already above (curated per register spread, not a thesaurus dump).
113
+ // the same touch relation asked from the commit's side ("what did
114
+ // commit <sha> touch")
200
115
  "touch", "touched by", "modified by", "changed by", "changed in",
201
116
  "landed in", "land in",
202
- // contents-of-a-commit forms (2026-07-02, operator screenshot: "what was in
203
- // commit <sha>" missed on the live site). "was in"/"went into" only read as
204
- // touch questions when the object is a commit — the sha-shaped object keeps
205
- // them from firing on structural questions ("is X in the graph" has no verb
206
- // match anyway). Judgment call: "is in" omitted — too generic without the
207
- // past-tense anchor and risks matching containment phrasings.
117
+ // "was in"/"went into" only read as touch questions against a
118
+ // sha-shaped object
208
119
  "was in", "were in", "went into", "included in",
209
- // when-question forms (2026-07-02 query families): "when was X last
210
- // updated/edited" — bare past participles that only read naturally in the
211
- // temporal shape; the when template routes them, but they are ordinary
212
- // touches verbs so "which modules were updated ..." keeps working too.
213
120
  "updated", "edited",
214
- // gerund (g-drop normalization — §3.5)
121
+ // gerund (g-drop normalization)
215
122
  "touching",
216
123
  ],
217
124
  },
@@ -222,17 +129,7 @@ export const RELATIONS = {
222
129
  "change alongside", "shares commits with", "share commits with",
223
130
  "tends to change together with", "tend to change together with",
224
131
  "moves together with", "move together with",
225
- // Track-1 trio (temporal lever): the bare "changed/change together with" form
226
- // (no "tends to"/"tend to" prefix) was missing outright — "which modules
227
- // changed together with X" fell through to the "touch(ed)" verb instead (a
228
- // Commit->Module kind, structurally unable to match a Module subject), always
229
- // producing a confidently-empty answer regardless of real cochange data.
230
132
  "changed together with", "change together with", "changes together with",
231
- // Present-tense bare form (Seonix Batch 4/5 follow-up): only the past tense
232
- // "changed with" existed above — "what changes with X"/"what usually changes
233
- // with X" fell through entirely (ENTITY_TO_TYPE's own "changes"->"Change"
234
- // pseudo-type noun risked consuming the word first; see parseRelationalOrQualified's
235
- // guard against exactly that in ask.mjs).
236
133
  "changes with", "change with", "tends to change with", "tend to change with",
237
134
  "usually changes with",
238
135
  ],
@@ -241,37 +138,23 @@ export const RELATIONS = {
241
138
  comment: "Module -> exported symbol: subject's public API surface (__all__/export list).",
242
139
  verbs: [
243
140
  "exports", "export", "re-exports", "re-export", "passes through", "pass through",
244
- // API-surface phrasing (2026-07-02 query families): "what does <module>
245
- // expose". Bare "exposed" is NOT listed — the lemma tier maps it here when
246
- // an adapter is present, and "how does the API get exposed" has no
247
- // traversal either way (pinned as an honest miss in the tests).
248
141
  "exposes", "expose",
249
- // gerund (g-drop normalization — §3.5)
142
+ // gerund (g-drop normalization)
250
143
  "exporting",
251
144
  ],
252
145
  },
253
146
  };
254
147
 
255
- /** The closed set of reverse `inherits` verb phrasings a strategy that has already
256
- * matched a verb phrase can check ("was this one of the reverse ones?") to decide
257
- * whether to swap subject/object before returning its parsed shape (see the
258
- * comment above INHERITS_REVERSE_VERB_LIST, right before RELATIONS, for the full
259
- * story). Every phrase here EXCEPT the three "the"-definite forms is ALSO folded
260
- * into RELATIONS.inherits.verbs (so VERB_TO_KIND routes it to kind "inherits"
261
- * like any other inherits verb) — the "the"-forms are named here for
262
- * completeness (matching the originally-specified closed set) but are not yet
263
- * reachable through VERB_TO_KIND, so a strategy will never actually see one as
264
- * a matched verb; keeping them in this list is harmless and future-proofs the
265
- * swap check for whenever the CONTENT_VOCAB constraint is lifted. */
148
+ /** The closed set of reverse `inherits` verb phrasings a strategy checks to
149
+ * decide whether to swap subject/object. The three "the"-definite forms are
150
+ * named here but not reachable through VERB_TO_KIND (see above). */
266
151
  export const INHERITS_REVERSE_VERBS = Object.freeze([
267
152
  ...INHERITS_REVERSE_VERB_LIST,
268
153
  "is the superclass of", "are the superclass of", "is the parent class of",
269
154
  ]);
270
155
 
271
- // ---- where/when/mentions markers (2026-07-02 query families) the location and
272
- // prose-mention questions carry NO relation verb ("where is X defined", "where is
273
- // X mentioned"), so ask.mjs routes them by these marker words instead of
274
- // VERB_TO_KIND. Closed lists, same curation discipline as everything above. ----
156
+ // ---- where/when/mentions markers: these questions carry no relation verb,
157
+ // so ask.mjs routes them by marker word instead of VERB_TO_KIND. ----
275
158
 
276
159
  /** Definition-location markers: "where is X <marker>" (or bare "where is X"). */
277
160
  export const WHERE_MARKERS = Object.freeze(["defined", "declared", "located", "implemented"]);
@@ -279,16 +162,11 @@ export const WHERE_MARKERS = Object.freeze(["defined", "declared", "located", "i
279
162
  /** Prose-mention markers: "where is X <marker>" -> the prose/mentions surface. */
280
163
  export const MENTION_MARKERS = Object.freeze(["mentioned", "referenced"]);
281
164
 
282
- // ---- trailing scope filler (Seonix Batch 2 Fix 3) a "what is a <noun phrase>"
283
- // question sometimes tacks on a trailing clause that scopes the question back onto
284
- // the graph/codebase ITSELF, not onto any additional term: "what is a Module in
285
- // this graph" means exactly "what is a Module", not a literal lookup of the glued
286
- // phrase "Module in this graph". Closed and curated like every other table here —
287
- // an unlisted trailing clause is left alone (an honest literal lookup), never
288
- // silently swallowed by a general heuristic. ----
165
+ // ---- trailing scope filler: "what is a Module in this graph" resolves the
166
+ // same term as "what is a Module". ----
289
167
 
290
- /** Trailing filler clauses stripped from the END of a captured meta-whatis object,
291
- * case-insensitive, before the term is used as a lookup key. */
168
+ /** Trailing filler clauses stripped from the end of a captured meta-whatis
169
+ * object, case-insensitive, before the term is used as a lookup key. */
292
170
  export const TRAILING_SCOPE_FILLER = Object.freeze([
293
171
  "in this graph", "in the graph", "in this codebase", "in the codebase",
294
172
  "in this repo", "in the repo", "here",
@@ -298,58 +176,30 @@ const TRAILING_SCOPE_FILLER_RE = new RegExp(
298
176
  `\\s+(?:${TRAILING_SCOPE_FILLER.join("|")})\\s*[?.!]*$`, "i",
299
177
  );
300
178
 
301
- /** Strip ONE trailing scope-filler clause (TRAILING_SCOPE_FILLER, above) off the end
302
- * of a captured meta-whatis object "what is a Module in this graph" resolves the
303
- * same term as "what is a Module". Applied once, not in a loop: no worked phrasing
304
- * stacks two filler clauses. Every TRAILING_SCOPE_FILLER entry is plain words (no
305
- * regex metacharacters), so no escaping is needed building the alternation. */
179
+ /** Strip one trailing scope-filler clause off the end of a captured
180
+ * meta-whatis object. Applied once: no worked phrasing stacks two. */
306
181
  export function stripTrailingScopeFiller(text) {
307
182
  return text.replace(TRAILING_SCOPE_FILLER_RE, "").trim();
308
183
  }
309
184
 
310
- /** Trailing bare discourse tags the same curated "then"/"though" pair
311
- * ask.mjs's PRED_LEAD_SKIP already recognizes as a trailing discourse tag on
312
- * an otherwise-bare follow-up ("how many of those then"). A "what is X"
313
- * meta-whatis question wasn't tolerant of this yet: "what is a component
314
- * then" captured the literal unknown term "component then" instead of
315
- * "component" (HANDOVER.md 2026-07-10, item 8). Stripped the same
316
- * closed-list way as TRAILING_SCOPE_FILLER, just above. "too" (playtest
317
- * sprint round 2): "is UserController a validator too then" — a STACKED
318
- * pair of trailing tags — needed both "too" added to the set AND the strip
319
- * applied twice (below), since a single pass only ever removes the
320
- * outermost tag. */
185
+ /** Trailing bare discourse tags ("how many of those then"). "too" can stack
186
+ * ("is UserController a validator too then"), hence the double pass below. */
321
187
  export const TRAILING_DISCOURSE_TAG = Object.freeze(["then", "though", "too"]);
322
188
 
323
189
  const TRAILING_DISCOURSE_TAG_RE = new RegExp(
324
190
  `\\s+(?:${TRAILING_DISCOURSE_TAG.join("|")})\\s*[?.!]*$`, "i",
325
191
  );
326
192
 
327
- /** Trailing COMMA-delimited discourse clauses ("what is a class, please
328
- * explain") a distinct shape from TRAILING_DISCOURSE_TAG above (a bare
329
- * space-led tag, no comma): an ESL follow-on clause tacked onto a
330
- * meta-whatis term after a comma, meaning "please explain [it]", not part of
331
- * the term itself (BENCHMARK_CONVERSATION_1.7.0.md routed backlog C1). Closed
332
- * and curated exactly like TRAILING_DISCOURSE_TAG — anchored on a LITERAL
333
- * comma immediately before the tag, so this can never fire mid-phrase and
334
- * can never touch a term that merely contains a comma for some other reason
335
- * (no real code identifier ends in ", please explain" or ", explain"). */
193
+ /** Trailing comma-delimited discourse clauses ("what is a class, please
194
+ * explain"), anchored on a literal comma so this never fires mid-phrase. */
336
195
  export const TRAILING_DISCOURSE_CLAUSE = Object.freeze(["please explain", "explain"]);
337
196
 
338
197
  const TRAILING_DISCOURSE_CLAUSE_RE = new RegExp(
339
198
  `,\\s*(?:${TRAILING_DISCOURSE_CLAUSE.join("|")})\\s*[?.!]*$`, "i",
340
199
  );
341
200
 
342
- /** Strip trailing bare discourse tags (TRAILING_DISCOURSE_TAG, above) AND
343
- * trailing comma-delimited discourse clauses (TRAILING_DISCOURSE_CLAUSE,
344
- * above) off the end of a captured meta-whatis term — "what is a component
345
- * then" resolves the same term as "what is a component", and "what is a
346
- * class, please explain" resolves the same term as "what is a class".
347
- * Applied up to twice (a stacked "too then"/"then too" is the only worked
348
- * case that ever needs a second pass; no phrasing seen so far stacks a
349
- * third), mirroring stripTrailingScopeFiller's own single-clause discipline
350
- * for the common single-tag case while still covering the rarer
351
- * double-tag one. The comma-clause strip runs each pass too, so "class,
352
- * please explain then" (an unworked but plausible stack) still resolves. */
201
+ /** Strip trailing discourse tags and comma-delimited clauses off a captured
202
+ * meta-whatis term. Applied up to twice for a stacked "too then". */
353
203
  export function stripTrailingDiscourseTag(text) {
354
204
  let out = text;
355
205
  for (let pass = 0; pass < 2; pass += 1) {
@@ -369,37 +219,14 @@ export const VERB_TO_KIND = Object.freeze(
369
219
  ),
370
220
  );
371
221
 
372
- /** "what is a kind of X" / "what is a subclass of X" collision fix (2026-07-11,
373
- * live-repro: "boney is a dog" -> "what is a dog" -> "what is a kind of
374
- * animal" hit a forced disambiguation wall instead of answering). grammar.mjs's
375
- * T5 "meta-whatis" template reads "what is a/an <object>" as a literal
376
- * glossary/term-definition question ("what is a Commit"). But some registered
377
- * inherits verbs are THEMSELVES phrased "is a <continuation>" ("is a kind
378
- * of", "is a subclass of" RELATIONS.inherits.verbs above) when the object
379
- * T5 captures IS one of these continuations plus a real term ("kind of
380
- * animal"), the sentence isn't asking to define the noun phrase "kind of
381
- * animal"; it's the exact same question as "what inherits from animal", just
382
- * phrased with the verb's own "is a" lead instead of "inherits". keyword-spot
383
- * (keywords.mjs) already reads it that way, unambiguously — the meta reading
384
- * is the spurious one, and it collides with keyword-spot's correct reading to
385
- * manufacture a needless {ambiguousParse} tie (interpret/merge.mjs) over a
386
- * phrasing the engine itself just used a turn earlier. Derived from
387
- * VERB_TO_KIND (not hand-duplicated) so any future plain "is a/are a X of"
388
- * verb phrase added to RELATIONS is covered automatically, without touching
389
- * this file again.
390
- *
391
- * Deliberately EXCLUDES every INHERITS_REVERSE_VERBS entry ("is a superclass
392
- * of", "is a parent class of") even though they match the same "is a
393
- * <continuation>" shape: those verbs' subject/object are semantically
394
- * SWAPPED relative to storage direction (see INHERITS_REVERSE_VERB_LIST's own
395
- * comment above) — "what is a superclass of X" asks a FORWARD question
396
- * (X's own supertype), not this reverse-by-object listing, and keyword-spot's
397
- * decomposition only applies that swap in the two-sided "ask" shape (verb
398
- * between a subject AND an object), not this "reverse"-only afterText-only
399
- * shape. Suppressing the meta reading for these too would trade one honest
400
- * {ambiguousParse} wall for a confusing WRONG answer (a reverse-by-object
401
- * lookup with the direction backwards) — a separate, pre-existing gap in
402
- * keywords.mjs, out of this fix's scope. */
222
+ /** "what is a kind of X" / "what is a subclass of X" collision fix: some
223
+ * inherits verbs are themselves phrased "is a <continuation>", which would
224
+ * otherwise collide with grammar.mjs's literal meta-whatis reading and
225
+ * manufacture a spurious {ambiguousParse}. Derived from VERB_TO_KIND so any
226
+ * future "is a/are a X of" verb is covered automatically. Excludes
227
+ * INHERITS_REVERSE_VERBS: those verbs' subject/object are swapped relative
228
+ * to storage direction, so suppressing their meta reading would produce a
229
+ * wrong answer instead of an honest ambiguity. */
403
230
  const NON_REVERSE_VERB = (v) => !INHERITS_REVERSE_VERBS.includes(v);
404
231
  export const ARTICLE_RELATION_CONTINUATIONS = Object.freeze([
405
232
  ...new Set(
@@ -415,24 +242,13 @@ export const ENTITY_TO_TYPE = Object.freeze({
415
242
  function: "Function", functions: "Function",
416
243
  method: "Method", methods: "Method",
417
244
  class: "Class", classes: "Class",
418
- // "mod"/"mods" (HANDOVER.md 2026-07-10 item 10): a rushed-dev abbreviation
419
- // prefix ("mod store.mjs imports") used to land in disambiguation instead of
420
- // resolving cleanly, since nothing recognized "mod" as this same Module noun
421
- // — same alias-of-Module trade "file"/"files" already make just above.
422
245
  module: "Module", modules: "Module", mod: "Module", mods: "Module", file: "Module", files: "Module",
423
246
  attribute: "Attribute", attributes: "Attribute", field: "Attribute", fields: "Attribute",
424
247
  variable: "GlobalVariable", variables: "GlobalVariable", global: "GlobalVariable", globals: "GlobalVariable",
425
- // "changes" in a touch question ("which changes touch commit <sha>") means the
426
- // code entities on the other end of the touch edges, at WHATEVER grain the graph
427
- // recorded module (touches) and symbol (touchesSymbol) together when the commit
428
- // is the given side, and the touching commits themselves when a module/symbol is
429
- // the given side. Mapped to the pseudo-type "Change" (not a node class): ask.mjs's
430
- // traverse() reads it as a wildcard over the touch traversal's results rather than
431
- // aliasing it to ONE real class and silently dropping the other grain of the
432
- // answer. Listed BEFORE commit/commits: findPhrase (ask.mjs) takes the first
433
- // same-length phrase in table order, so in "which changes touch commit <sha>" the
434
- // entity slot must consume "changes" and leave "commit <sha>" intact as the
435
- // object term.
248
+ // "Change" is a pseudo-type, not a node class: ask.mjs's traverse() reads it
249
+ // as a wildcard over touch-traversal results (module or symbol grain).
250
+ // Listed before commit/commits since findPhrase takes the first
251
+ // same-length match in table order.
436
252
  change: "Change", changes: "Change",
437
253
  commit: "Commit", commits: "Commit",
438
254
  });
@@ -442,18 +258,11 @@ export const MODIFIER_TO_KIND = Object.freeze({
442
258
  transitively: "transitive", indirectly: "transitive",
443
259
  });
444
260
 
445
- // ---- reversible-passive participles (Cycle 6, archive/PLAN_CYCLE_4.md) past participles ->
446
- // relation kind, for the agent-marked passive "X is <participle> by Y". Kept SEPARATE
447
- // from VERB_TO_KIND on purpose: these forms are NOT standalone active verbs in this
448
- // grammar ("defined" belongs to the multi-word "is defined in" and to the WHERE_MARKERS
449
- // location routing; bare "inherited" has no active key), so folding them into
450
- // VERB_TO_KIND would silently re-route "where is X defined" and other queries. This
451
- // table is consulted ONLY by the keyword strategy's passive path, which has already
452
- // confirmed a passive auxiliary AND an agent-marking "by" — so an active query is never
453
- // affected. Most common participles ("imported"/"tested"/"called"/"covered") already
454
- // reach VERB_TO_KIND via the lemma tier; this table backfills the two families the lemma
455
- // tier can't (defines/inherits) plus the obvious siblings, so the passive works
456
- // adapter-free too. ----
261
+ // ---- reversible-passive participles: past participles -> relation kind, for
262
+ // the agent-marked passive "X is <participle> by Y". Kept separate from
263
+ // VERB_TO_KIND because these aren't standalone active verbs ("defined" would
264
+ // otherwise clobber the WHERE_MARKERS routing); only consulted once a passive
265
+ // auxiliary + agent-marking "by" is confirmed. ----
457
266
  export const PASSIVE_PARTICIPLE_TO_KIND = Object.freeze({
458
267
  imported: "imports", called: "calls", used: "uses",
459
268
  tested: "tests", covered: "tests", verified: "tests", exercised: "tests", checked: "tests",
@@ -464,12 +273,8 @@ export const PASSIVE_PARTICIPLE_TO_KIND = Object.freeze({
464
273
  touched: "touches", changed: "touches", modified: "touches", edited: "touches", updated: "touches",
465
274
  });
466
275
 
467
- // ---- §3.5 normalization contractions/informal spellings that would otherwise
468
- // block a match, expanded BEFORE parsing (BOTH the anchored-template strategy
469
- // and the independent keyword-spotting strategy see the same normalized text —
470
- // this table is not owned by either). Small and code-question-scoped, not a
471
- // general slang dictionary — every entry here exists because it appears in an
472
- // actual worked example this file's matcher must handle. ----
276
+ // ---- normalization: contractions/informal spellings expanded before parsing,
277
+ // shared by both parse strategies. ----
473
278
  export const CONTRACTIONS = Object.freeze({
474
279
  "ain't": "is not", "aint": "is not",
475
280
  "isn't": "is not", "isnt": "is not",
@@ -487,28 +292,15 @@ export const CONTRACTIONS = Object.freeze({
487
292
  "gimme": "give me",
488
293
  });
489
294
 
490
- // ---- misspellings and wrong words (2026-07-02, two-level fuzzy work) these are
491
- // CORRECTIONS, not synonyms: the asker typed a broken or incorrect surface form of
492
- // a word this grammar already owns, and we restore the canonical form BEFORE
493
- // parsing (normalizeQuery applies both tables like CONTRACTIONS: word-boundary,
494
- // longest key first, case-insensitive). Synonyms belong in RELATIONS/ENTITY_TO_TYPE;
495
- // these tables exist so a typo'd or misused word maps to canonical language
496
- // deterministically, ahead of (and more precisely than) ask.mjs's bounded
497
- // edit-distance fallback. ask.mjs's correction regex refuses to rewrite a word
498
- // glued to a dotted extension ("revision.mjs" stays a module name), since
499
- // WRONG_WORDS entries are real English words that plausibly name modules; a
500
- // bare standalone token that happens to be a real identifier remains the
501
- // accepted residual exposure, same as CONTRACTIONS. ----
295
+ // ---- misspellings and wrong words: corrections, not synonymsrestore the
296
+ // canonical form before parsing (word-boundary, longest key first,
297
+ // case-insensitive), ahead of ask.mjs's bounded edit-distance fallback. The
298
+ // correction regex refuses to rewrite a word glued to a dotted extension
299
+ // ("revision.mjs" stays a module name). ----
502
300
 
503
- /** Curated common typos of THIS vocabulary's own keywords entity nouns, relation
504
- * verbs, and the grammar's anchor words (which/what/does/the): a typo'd anchor
505
- * kills the anchored templates outright, so anchors earn entries too. Values are
506
- * always the canonical word. Judgment calls logged: "calss" maps to "class" (the
507
- * doubled-s slip of "class"), NOT "calls", though both are edit-distance 1 — the
508
- * exact tie the generic fuzzy tier must refuse to break; a curated table is where
509
- * that call is made deliberately. "dose" is a real English word, but at a word
510
- * boundary in this closed question grammar ("dose X import Y") the intent is
511
- * unambiguous. */
301
+ /** Curated typos of this vocabulary's own keywords: entity nouns, relation
302
+ * verbs, and grammar anchor words (which/what/does/the). Values are always
303
+ * the canonical word. */
512
304
  export const MISSPELLINGS = Object.freeze({
513
305
  // entity nouns
514
306
  "funtion": "function", "funtions": "functions",
@@ -531,14 +323,7 @@ export const MISSPELLINGS = Object.freeze({
531
323
  "inherts": "inherits", "inheirts": "inherits",
532
324
  "extands": "extends", "extneds": "extends",
533
325
  "depnds": "depends",
534
- "touchs": "touches", "tuoches": "touches", "touhced": "touched",
535
- // "touchd" (BENCHMARK_CONVERSATION_1.7.0.md routed backlog C3): the
536
- // dropped-vowel slip of "touched" — distinct from "touhced" above
537
- // (transposed letters), same curated-typo discipline.
538
- "touchd": "touched",
539
- // WHERE_MARKERS typo (0.9.13 Tier-1 playtest): "defined" itself had no typo
540
- // entry, so "where is it defned" fell through to the bare-object search path
541
- // instead of the where-shape ("no module matching 'it defned' found").
326
+ "touchs": "touches", "tuoches": "touches", "touhced": "touched", "touchd": "touched",
542
327
  "defned": "defined",
543
328
  "chagned": "changed", "chnaged": "changed",
544
329
  "chagnes": "changes", "chnages": "changes",
@@ -547,45 +332,14 @@ export const MISSPELLINGS = Object.freeze({
547
332
  "tets": "tests",
548
333
  // grammar anchor words
549
334
  "whcih": "which", "wich": "which", "whihc": "which",
550
- // "wehre"/"whre" (0.9.13 Tier-1 playtest, "where is it defined" drill-down):
551
- // the WHERE-DEFINED shape's own anchor word had NO typo tolerance at all
552
- // (unlike which/what/does/the above), so a plain dropped/transposed letter
553
- // fell straight through resolveObject and hit either the grammar wall or a
554
- // bogus "no module matching 'it defined'" search. "were" (the missing-h
555
- // homophone slip) is NOT curated here — it's a real word already load-bearing
556
- // as the TEMPORAL_AUX auxiliary ("when were the modules last touched"), so
557
- // that one typo is handled by its own anchored phrasing frame instead
558
- // (normalize.mjs PHRASING_FRAMES) to avoid clobbering the legitimate reading.
335
+ // "were" (the missing-h homophone slip of "where") is NOT curated here —
336
+ // it's a load-bearing TEMPORAL_AUX word ("when were the modules touched").
559
337
  "wehre": "where", "whre": "where",
560
- // "wat" (chatbench cycle 2, tf-wat-calls): the internet-casual spelling of
561
- // "what" — neither curated noise nor a restorable trigger typo, so "wat calls
562
- // fnAlpha" used to die as "couldn't resolve one of the terms". Restored here
563
- // so BOTH parse strategies and the relaxation cascade see the canonical
564
- // anchor; the correction regex's dotted-extension guard keeps a module
565
- // literally named "wat.mjs" untouched, same residual trade as every entry.
566
338
  "waht": "what", "wat": "what",
567
- // "dat" (BENCHMARK_CONVERSATION_1.7.0.md routed backlog C3): the internet-
568
- // casual spelling of "that" — same register as "wat"/"waht" just above,
569
- // curated rather than left to the generic fuzzy tier since "that" is a
570
- // load-bearing anchor word throughout this grammar (TEACH_RE's own
571
- // "remember that X", relative-clause objects, etc).
572
339
  "dat": "that",
573
340
  "dose": "does", "doess": "does",
574
341
  "teh": "the",
575
- // aggregate/list TRIGGER words (2026-07-02, trigger-typo work) a typo of a count
576
- // or list trigger used to be DROPPED as unmatched by the relaxation cascade, losing
577
- // the aggregate/list INTENT entirely ("how manyn classes" → the count was lost);
578
- // curated here so the intended trigger is restored BEFORE parsing (the general
579
- // bounded fuzzy path in ask.mjs's cascade is the backstop for uncurated typos).
580
- "manyn": "many", "mnay": "many", "amny": "many", "mnany": "many",
581
- // "hwo" (Tier 6 playtest, §3b typo axis): a transposed-letter typo of "how" —
582
- // "hwo many classes are there" used to lose the aggregate/list trigger
583
- // outright ("how many" only reads as a count trigger when both words are
584
- // exact), same failure class as "manyn"/"mnay" just above, one word to the
585
- // left of it. "how" is grammar-owned via AGGREGATE_TRIGGERS' own "how many"/
586
- // "how much" entries (test/ask-vocab.test.mjs's canonical-value check
587
- // splits those multi-word triggers into individual words).
588
- "hwo": "how",
342
+ "manyn": "many", "mnay": "many", "amny": "many", "mnany": "many", "hwo": "how",
589
343
  "coutn": "count", "conut": "count", "cuont": "count", "ocunt": "count",
590
344
  "numer": "number", "nubmer": "number", "numbr": "number", "nmuber": "number",
591
345
  "lst": "list", "lsit": "list", "ilst": "list",
@@ -594,12 +348,9 @@ export const MISSPELLINGS = Object.freeze({
594
348
  "funtcions": "functions", "funciton": "function", "funcitons": "functions",
595
349
  });
596
350
 
597
- /** Words used INCORRECTLY but with clear intent, mapped to the canonical schema
598
- * term — used like synonyms by the asker, but they are corrections of usage, not
599
- * alternative names (which is why they live here and not in ENTITY_TO_TYPE).
600
- * Only mapped where intent is unambiguous in a code-graph question; words
601
- * deliberately NOT mapped are logged in the omitted-on-purpose block at the end
602
- * of this file. */
351
+ /** Words used incorrectly but with clear intent, mapped to the canonical
352
+ * schema term — corrections of usage, not alternative names (hence not in
353
+ * ENTITY_TO_TYPE). */
603
354
  export const WRONG_WORDS = Object.freeze({
604
355
  // neither a folder nor a directory grain exists in this graph — in "which
605
356
  // folders import X" the only honest referent is the module/file grain.
@@ -614,63 +365,28 @@ export const WRONG_WORDS = Object.freeze({
614
365
  "property": "attribute", "properties": "attributes",
615
366
  });
616
367
 
617
- /** Trailing g-drop ("callin'", "hittin'") a plain suffix restore, not a
618
- * lemmatiser: -in' -> -ing, applied per-word during normalization. The
619
- * apostrophe is REQUIRED (not optional): bare "-in" endings with no
620
- * apostrophe are real English words far more often than dropped g's
621
- * ("cabin", "robin", "chin", "twin") — restoring those would be a false
622
- * positive the anchored templates never had to worry about. No trailing
623
- * `\b` after the apostrophe: `'` is a non-word character, so a `\b` there
624
- * can never match the (non-word) whitespace/end-of-string that follows —
625
- * the apostrophe itself already delimits the word, a second boundary check
626
- * after it is redundant and unmatchable. */
368
+ /** Trailing g-drop ("callin'", "hittin'"): -in' -> -ing. The apostrophe is
369
+ * required — bare "-in" endings are real words far more often than dropped
370
+ * g's ("cabin", "robin", "twin"). */
627
371
  export const G_DROP = /\b([a-z]{3,})in'/gi;
628
372
 
629
373
  /** Words stripped during normalization/keyword-spotting once they carry no
630
- * grammatical weight for this grammar — greetings, politeness, hedges, and
631
- * the discourse fillers a spoken-style question picks up. Never strips a
632
- * relation verb, entity noun, or modifier (those are checked first).
633
- * Bare "you" (0.9.14 Tier-2 playtest, §3b ESL angle): a non-native word-order
634
- * slip ("please you tell me what is Class", "you tell me what is Class") left
635
- * a leading "you" that "could you"/"can you"/"would you" don't cover (those
636
- * are anchored WRAPPERS requiring the verb-first order) — the leftover
637
- * pronoun broke the bare "what is Class" no-article count reading, which
638
- * (unlike the "what is a Class" meta form) requires the WHOLE normalized
639
- * string to match, not just a substring. "you" carries no grammatical weight
640
- * in this code-graph grammar (never a real entity/relation term), so it is
641
- * safe to strip anywhere, same trade as every other word in this list. */
374
+ * grammatical weight — greetings, politeness, hedges, discourse fillers.
375
+ * Never strips a relation verb, entity noun, or modifier. */
642
376
  export const FILLER_WORDS = Object.freeze([
643
377
  "um", "uh", "erm", "so", "like", "yo", "hey", "bru", "bro", "fam", "mate",
644
378
  "please", "could you", "can you", "would you", "tell me", "i wonder",
645
379
  "just wondering", "quickly", "real quick", "kinda", "sorta",
646
380
  "btw", "by the way", "you",
647
- // "quick q" (BENCHMARK_CONVERSATION_1.8.14.md item 11): the casual abbreviated
648
- // sibling of GREETING_PREAMBLE_RE's own "quick question" clause (normalize.mjs)
649
- // — that frame requires a delimiter immediately after the greeting word
650
- // ("hey, quick question - …"), so it never matches "hey quick q, …" (no
651
- // delimiter between "hey" and "quick q"). Filler-stripping instead — this
652
- // list is matched word-boundary-anywhere, not anchored — closes the gap
653
- // without needing GREETING_PREAMBLE_RE's own stricter delimiter-position
654
- // shape.
655
381
  "quick q",
656
382
  ]);
657
383
 
658
- /** Deictic/pronoun terms that refer to a context entity rather than naming one
659
- * directly resolved against an optional `contextId` (ask.mjs §keyword-
660
- * spotting; wired from the graph viewer's currently-selected node, when the
661
- * chat panel is asking "about" whatever the user last clicked). With no
662
- * context available (the bare CLI surface), these produce an honest
663
- * "which node does 'this' refer to?" miss — never a guess. */
384
+ /** Deictic/pronoun terms resolved against an optional `contextId` (the graph
385
+ * viewer's currently-selected node). With no context, an honest miss. */
664
386
  export const CONTEXT_PRONOUNS = Object.freeze(["this", "it", "that", "here", "this one", "that one"]);
665
387
 
666
- // ---- §3.6 negation frames a SMALL, pattern-based set of recognized
667
- // double-negative / negative-rhetorical constructions, normalized to the
668
- // affirmative form of the SAME question (not a general negation-scope parser
669
- // — ELIZA/PARRY's own scope discipline: a handful of recognized frames, not
670
- // an attempt at full logical negation handling). Each frame is tried in
671
- // order; the first match rewrites the whole string and normalization stops
672
- // (a second negation frame firing on an already-rewritten string would be a
673
- // design smell, not a feature). ----
388
+ // ---- negation frames: a small set of double-negative / negative-rhetorical
389
+ // constructions, normalized to the affirmative form. First match wins. ----
674
390
  export const NEGATION_FRAMES = Object.freeze([
675
391
  // "there ain't nothin' calling it" / "there isn't nothing that calls it"
676
392
  // -> "what calls it"
@@ -683,18 +399,9 @@ export const NEGATION_FRAMES = Object.freeze([
683
399
  { re: /\b(?:nobody|nothing|no one)\s+(\S.*?)(?:,?\s+does\s+it\??|,?\s+do\s+they\??)?$/i, to: (m) => `what ${m[1]}` },
684
400
  ]);
685
401
 
686
- // ---- commit-content frames (2026-07-02, operator screenshot: "what was in commit
687
- // ef74e44e25c8" missed on the live site) "what was/is in commit <sha>", "what's
688
- // in <sha>" (the contraction table has already expanded "what's" by the time
689
- // frames run), "what went into <sha>", "what made it into commit <sha>" all mean
690
- // the canonical commit-subject question "what did <sha> touch", so they are
691
- // REWRITTEN to it before either parse strategy runs: the same mechanism and scope
692
- // discipline as NEGATION_FRAMES (a small closed pattern set, first match wins),
693
- // which also makes both strategies parse the family for free. The sha-shaped tail
694
- // is REQUIRED so these frames only ever fire on an actual commit reference: over a
695
- // non-sha object ("what was in walk.mjs") the frame does not match and the text is
696
- // left for the ordinary grammar to handle as it already does, rather than being
697
- // bent into a commit-subject touches query that would then blank. ----
402
+ // ---- commit-content frames: "what was in commit <sha>" etc. rewrite to the
403
+ // canonical "what did <sha> touch" before either parse strategy runs. The
404
+ // sha-shaped tail keeps this from firing on a non-commit object. ----
698
405
  export const COMMIT_CONTENT_FRAMES = Object.freeze([
699
406
  // "what was in commit ef74e44e25c8" / "what is in ef74e44e" / "what's in commit <sha>"
700
407
  { 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` },
@@ -704,49 +411,29 @@ export const COMMIT_CONTENT_FRAMES = Object.freeze([
704
411
  { re: /^what\s+made\s+it\s+into\s+((?:commit\s+)?[0-9a-f]{7,40})\??$/i, to: (m) => `what did ${m[1]} touch` },
705
412
  ]);
706
413
 
707
- // ---- §7 meta-vocabulary trigger phrases for the "meta" query shape (asking what a
708
- // graph vocabulary term itself means: "what does cochange mean", "what does mgx:
709
- // callsSymbol mean"). Deliberately NOT folded into RELATIONS/VERB_TO_KIND: these
710
- // phrases don't name a graph relation kind for edgesOfKind to traverse, they name
711
- // "explain this term" intent. Adding them to VERB_TO_KIND would make
712
- // parseKeywordSpot's decomposition matcher independently derive a *different* shape
713
- // ("forward", the only shape its before-only-text case produces) for the very same
714
- // sentence the anchored meta template resolves to shape:"meta" — a spurious strategy
715
- // DISAGREEMENT (ambiguousParse) on every meta question. So this table is read directly
716
- // by ask.mjs's own meta template, never merged into the shared verb tables. ----
414
+ // ---- meta-vocabulary: trigger phrases for "what does cochange mean"-style
415
+ // term-explanation questions. Kept out of RELATIONS/VERB_TO_KIND these name
416
+ // "explain this term" intent, not a graph relation to traverse. ----
717
417
  export const META_MEANING_VERBS = Object.freeze([
718
418
  "mean", "means", "stand for", "stands for", "signify", "signifies",
719
419
  "represent", "represents", "refer to", "refers to",
720
420
  ]);
721
421
 
722
- // ---- §compositional grammar vocabulary (PLAN §5.16 P3 the ask engine's step up
723
- // from ELIZA keyword-spotting to a real recursive-descent grammar over CLAUSES).
724
- // ask.mjs's parseComposite reads these tables to recognize the compositional
725
- // MARKERS relative clauses, boolean connectives, subject qualifiers, aggregates,
726
- // superlatives, and anaphora — that compose the SAME closed clause vocabulary
727
- // (RELATIONS/ENTITY_TO_TYPE above) into multi-hop / set-algebra queries. The
728
- // grammar COMPOSES the closed vocabulary; it never opens it. Every table here is
729
- // curated + closed, same discipline as everything above: a marker the tables don't
730
- // carry is an honest miss, never a guess. ----
422
+ // ---- compositional grammar vocabulary — ask.mjs's parseComposite reads these
423
+ // to recognize relative clauses, boolean connectives, qualifiers, aggregates,
424
+ // superlatives, and anaphora that compose RELATIONS/ENTITY_TO_TYPE into
425
+ // multi-hop / set-algebra queries. ----
731
426
 
732
- /** Relative-clause introducers: "<noun> that/which/who <predicate>". Only ever
733
- * read as a relative pronoun when it sits AFTER a noun and BEFORE a predicate
734
- * (ask.mjs enforces both) — "that" is also a CONTEXT_PRONOUN ("what calls that"),
735
- * so position, not mere presence, decides. This gates the nested/relative and
736
- * same-subject boolean shapes: their absence is exactly what keeps the bare
737
- * reverse-template query "which classes extends Base and couples to logging" out
738
- * of the compositional path (it stays the existing two-strategy ambiguous parse). */
427
+ /** Relative-clause introducers: "<noun> that/which/who <predicate>". Read as
428
+ * a relative pronoun only after a noun and before a predicate — "that" is
429
+ * also a CONTEXT_PRONOUN, so position decides. */
739
430
  export const RELATIVE_PRONOUNS = Object.freeze(["that", "which", "who"]);
740
431
 
741
- /** Placeholder object nouns the indefinite "something" in "what calls something
742
- * that imports X": they name NO entity, they stand in for the inner clause's
743
- * result set. Mapped to entityType null (any grain); a real entity noun in the
744
- * same slot ("what calls modules that import X") narrows the grain instead. */
432
+ /** Placeholder object nouns ("what calls something that imports X"): name no
433
+ * entity, stand in for the inner clause's result set (entityType null). */
745
434
  export const PLACEHOLDER_NOUNS = Object.freeze([
746
435
  "something", "anything", "everything", "somethings", "things", "thing",
747
436
  "entities", "entity", "nodes", "node", "stuff", "code",
748
- // "symbol(s)" is a grain-agnostic stand-in for any code entity — "exported
749
- // symbols of X" means whatever X defines, at any grain, filtered by the qualifier.
750
437
  "symbol", "symbols",
751
438
  ]);
752
439
 
@@ -761,14 +448,10 @@ export const BOOLEAN_CONNECTIVES = Object.freeze({
761
448
  "or": "union",
762
449
  });
763
450
 
764
- /** Subject QUALIFIERS (adjectives) -> a post-filter over the result set, read off
765
- * attributes/edges the graph already carries (codegraph.mjs): `visibility`
766
- * (private/protected present; public is the default/absent), `isStatic`/
767
- * `isConstant`/`isAbstract` boolean attrs, the reexports edge set (exported), and
768
- * the tests edge set (tested/untested). `isAbstract` is never populated by any
769
- * extractor today (codegraph.mjs says so) — "abstract methods" therefore honestly
770
- * returns an empty set, not an error, exactly like any other zero-hit answer. An
771
- * UNKNOWN qualifier is an honest miss naming these supported ones (ask.mjs). */
451
+ /** Subject qualifiers (adjectives) -> a post-filter over the result set, read
452
+ * off attributes/edges the graph already carries. `isAbstract` is never
453
+ * populated by any extractor today, so "abstract methods" honestly returns
454
+ * an empty set, not an error. */
772
455
  export const QUALIFIERS = Object.freeze({
773
456
  public: { via: "visibility", value: "public" },
774
457
  private: { via: "visibility", value: "private" },
@@ -785,59 +468,24 @@ export const QUALIFIERS = Object.freeze({
785
468
  });
786
469
 
787
470
  /** Aggregate/count triggers: "how many <kind> …", "count <kind>s", "number of
788
- * <kind>". Answered by counting a class of individuals or a clause's result set —
789
- * no header magic, a straight count over the graph (ask.mjs). Register-spread the
790
- * same way RELATIONS' verbs are (2026-07-02): the count question has a formal
791
- * ("how many"/"number of"), a neutral/imperative ("count"/"count up"), and a
792
- * quantity ("quantity of"/"total number of") register a developer actually types.
793
- * Judgement calls, kept OUT deliberately: bare "tally"/"sum"/"total" — those are
794
- * already mapped to "count" by CASCADE_SYNONYMS (ask.mjs's relaxation layer), and a
795
- * cascade test asserts "tally the classes" reaches the count via that synonym path,
796
- * so promoting them to direct triggers here would both duplicate the mapping and
797
- * break that test; and single-word "total"/"sum" would false-match identifier
798
- * fragments ("total price", "sum of squares") the count intent never meant. */
471
+ * <kind>". Bare "tally"/"sum"/"total" are deliberately excluded they're
472
+ * identifier-fragment risks ("total price") and stay CASCADE_SYNONYMS-mapped
473
+ * instead. */
799
474
  export const AGGREGATE_TRIGGERS = Object.freeze([
800
- // formal ("the number of classes" reaches "number of" once the cascade strips the
801
- // leading article — keeping the trigger list clear of "the" so it never enters
802
- // CONTENT_VOCAB and blocks the article's own noise-strip)
803
475
  "how many", "how much", "how many of", "number of",
804
476
  "total number of", "quantity of",
805
- // "<measure> of <kind>" cardinality forms (widened net, cycle W2P): count = sum = total
806
- // = tally = number of. The bare single words (sum/total/tally) stay CASCADE_SYNONYMS-
807
- // mapped to "count" (identifier-fragment risk without the "of" anchor — see that table's
808
- // note); the multi-word "of" forms are safe to promote to direct triggers because the
809
- // trailing "of <kind>" pins them to a cardinality question, not a stray identifier.
810
477
  "tally of", "sum of", "total of", "amount of",
811
- // neutral / imperative (bare "tally"/"sum"/"total" stay CASCADE_SYNONYMS-mapped so the
812
- // "tally the classes" relaxation path — pinned by a cascade test — is preserved).
813
478
  "count", "count up", "count of", "tot up",
814
479
  ]);
815
480
 
816
- /** LIST triggers (2026-07-02, list shape) the many ways a developer asks to SEE the
817
- * individuals of a kind ("list functions", "show me the classes", "what are the
818
- * modules"). Read by ask.mjs's parseList (a sibling of the count node): a trigger
819
- * followed by an entity kind noun lists that class (capped at OVERFLOW_CAP), a
820
- * trailing scope/predicate narrows it, and an unknown kind after a clear list trigger
821
- * is an honest miss naming the kinds. Two registers, wide-but-deliberate (the operator's
822
- * "err toward inclusion", bounded by the file-header discipline: a phrase earns its
823
- * place only if it genuinely means "enumerate these" and won't false-match an unrelated
824
- * identifier — parseList further requires a real entity kind to follow, so a stray
825
- * "show"/"name" in another question is never seized):
826
- * · IMPERATIVE — "<verb> [me/us] [the] <kind>". Bare determiners/objects (the/a/all/
827
- * me/us) after the verb are skipped by parseList's LIST_SKIP, so only the verb stem
828
- * is listed here (not every "... the"/"... all" inflection).
829
- * · INTERROGATIVE — "what/which are [the] <kind>"; the bare "what <kind>"/"which
830
- * <kind>" (+ optional "are there") form is handled directly in parseList, not here.
831
- * Kept OUT on purpose: "tell me" / "give me" collide only where normalizeQuery already
832
- * strips them as FILLER_WORDS ("tell me the classes" → "the classes"), so "tell me" is
833
- * omitted (it never survives to parseList); "gimme" is omitted because CONTRACTIONS
834
- * rewrites it to "give me" before parseList runs. */
481
+ /** List triggers ("list functions", "show me the classes"). Read by
482
+ * ask.mjs's parseList: a trigger followed by an entity kind noun lists that
483
+ * class; an unknown kind is an honest miss. "tell me"/"gimme" are omitted
484
+ * both normalize away before parseList runs. */
835
485
  export const LIST_TRIGGERS = Object.freeze([
836
- // imperative — "<verb> [me/us] [the] <kind>"
837
486
  "list", "show", "show me", "show us", "display", "print", "print out",
838
487
  "dump", "enumerate", "name", "give me", "get me", "spit out", "rattle off",
839
488
  "run down", "run through", "ls",
840
- // interrogative — "what/which are [the] <kind>"
841
489
  "what are", "which are",
842
490
  ]);
843
491
 
@@ -851,13 +499,8 @@ export const SUPERLATIVE_EXTREMES = Object.freeze({
851
499
  });
852
500
 
853
501
  /** Degree-metric nouns for superlatives ("which module has the most <noun>") ->
854
- * {kind, dir} over the SAME classified edge groups. dir "out" counts edges where
855
- * the ranked entity is the subject (its own imports/calls); "in" counts edges
856
- * where it is the object (its importers/callers/tests). "connections"/"edges"/
857
- * "connected" is the total (both directions, all structural kinds). A `sibling`
858
- * fine-grained kind is added to the tally when present (callers include the
859
- * symbol-grain callsSymbol edges, not just module-coarse calls); a `filter`
860
- * restricts the counted objects to one class ("methods"). */
502
+ * {kind, dir}. dir "out" counts the ranked entity as subject; "in" as object.
503
+ * `sibling` adds a fine-grained kind to the tally; `filter` restricts to one class. */
861
504
  export const EDGE_NOUN_TO_METRIC = Object.freeze({
862
505
  imports: { kind: "imports", dir: "out" },
863
506
  dependencies: { kind: "imports", dir: "out" },
@@ -874,12 +517,7 @@ export const EDGE_NOUN_TO_METRIC = Object.freeze({
874
517
  connections: { kind: "*", dir: "both" },
875
518
  edges: { kind: "*", dir: "both" },
876
519
  connected: { kind: "*", dir: "both" },
877
- // participle degree-nouns (widened net, cycle W2P): "the most imported / most
878
- // depended-on / most used <module>" ranks by IN-degree — how many things import/depend
879
- // on/use it — the ARGMAX-by-degree intent a developer expresses with a passive
880
- // participle rather than the noun ("importers"). "depended" catches "depended-on" /
881
- // "depended on" (both tokenize to a bare "depended"); "used" folds the symbol-grain
882
- // callsSymbol callers in alongside importers so "most used" reads as most-relied-upon.
520
+ // participle degree-nouns rank by in-degree, same intent as "importers" etc
883
521
  imported: { kind: "imports", dir: "in" },
884
522
  "depended-on": { kind: "imports", dir: "in" },
885
523
  depended: { kind: "imports", dir: "in" },
@@ -887,13 +525,9 @@ export const EDGE_NOUN_TO_METRIC = Object.freeze({
887
525
  called: { kind: "calls", dir: "in", sibling: "callsSymbol" },
888
526
  });
889
527
 
890
- /** Metric nouns whose edge kind targets exactly ONE entity class in this graph's
891
- * ontology (only a Module is ever the object of a `tests` edge) — read by
892
- * ask.mjs's parseSuperlative to default `entityType` when a superlative names a
893
- * metric but no explicit entity noun ("what most needs a test", vs. the fully
894
- * explicit "which MODULE has the most tests"). Deliberately small: a metric like
895
- * "calls"/"connections" targets more than one class, so it is NOT listed here —
896
- * those keep requiring an explicit entity noun, same as before this table existed. */
528
+ /** Metric nouns whose edge kind targets exactly one entity class lets
529
+ * parseSuperlative default `entityType` when no explicit noun is given
530
+ * ("what most needs a test"). */
897
531
  export const METRIC_IMPLIES_ENTITY = Object.freeze({
898
532
  tests: "Module",
899
533
  test: "Module",
@@ -912,119 +546,35 @@ export const ANAPHORA_TRIGGERS = Object.freeze(["those", "them", "these"]);
912
546
  * the asked entity type narrows the result. */
913
547
  export const MEMBERSHIP_KINDS = Object.freeze(["contains", "defines"]);
914
548
 
915
- // ---- §progressive-relaxation cascade vocabulary (SHRDLU-in-a-code-graph, with a
916
- // Zork parser's forgiveness) the three closed, curated tables ask.mjs's relaxParse
917
- // reads when the DIRECT parse of a query would MISS. The cascade only ever DROPS
918
- // noise/unmatched words or NORMALISES a near-canonical word to the closed vocabulary;
919
- // it never invents a term or guesses an entity, and it bottoms out in the same honest
920
- // rephrase hint. Every entry is hand-curated with inline provenance, same "closed is
921
- // deliberate" ethos as everything above: a word these tables don't carry is left for
922
- // the honest miss, never a general-English stoplist that would silently eat a real
923
- // code term. ----
549
+ // ---- progressive-relaxation cascade vocabulary: the tables ask.mjs's
550
+ // relaxParse reads when the direct parse of a query would miss. Only ever
551
+ // drops noise/unmatched words or normalises a near-canonical word; never
552
+ // invents a term or guesses an entity. ----
924
553
 
925
- /** Politeness / filler / vocative / presentation-frame tokens the cascade may strip
926
- * ONE AT A TIME (leftmost first) when a query misses but only ever a token that is
927
- * NOT itself a content vocabulary word (a relation verb, entity noun, modifier,
928
- * qualifier, aggregate/superlative trigger, …) and does NOT resolve to a graph entity
929
- * (ask.mjs guards both), so a module literally named "show" or "the" is never eaten.
930
- * This is a SUPERSET of the multi-word politeness FILLER_WORDS strips up-front during
931
- * normalization: those handle "could you"/"tell me"/"please" before either parse runs;
932
- * these single tokens catch what a spoken-style question keeps AFTER that pass — the
933
- * bare vocative ("matey"), the article ("the"/"a"), and the presentation frame words
934
- * ("show"/"me"/"list") that the compositional grammar already skips as FRAME_WORDS but
935
- * the keyword-spotting strategy does not, so an un-stripped "show me" otherwise
936
- * decomposes to a bogus ask{subject:"show me"}. Curated, not a general stoplist:
937
- * question words (what/which/…), connectives (and/or), and pronouns (this/it/that)
938
- * are deliberately ABSENT — they carry grammatical weight and must survive. */
554
+ /** Politeness/filler/vocative/presentation-frame tokens the cascade may strip
555
+ * one at a time on a missnever a content-vocabulary word, and never a
556
+ * token that resolves to a graph entity (ask.mjs guards both). */
939
557
  export const CASCADE_NOISE = Object.freeze([
940
- // articles / vague determiners (kept OUT of content vocab so they're strippable;
941
- // the aggregate/where parsers already tolerate a stray "the"/"a", so stripping is
942
- // belt-and-braces, not load-bearing)
943
- "the", "a", "an", "some",
944
- // "what OTHER classes inherit from Controller" — "other" is a vague determiner
945
- // like "some", not a qualifying adjective; without this it was misread as a
946
- // fuzzy find TERM ("no classes found matching 'other'") instead of falling
947
- // through to the ordinary reverse-inherits parse (fast-loop round 6 finding).
948
- "other",
949
- // topic lead-in filler — "what about the modules", "how about classes": "about"
950
- // carries no graph meaning here, so stripping it lets the bare kind noun surface for
951
- // the cascade's bare-kind-noun terminal rule (ask.mjs). ("what"/"how" are structural
952
- // question words the drop-pass keeps; only the "about" between them and the kind is
953
- // noise.) A module literally named "about" is safe-listed by relaxParse's resolvesExact
954
- // guard, same as every other noise token.
955
- "about",
956
- // politeness / hedges (single-token; multi-word "could you"/"please" etc. are
957
- // FILLER_WORDS, stripped earlier during normalization)
558
+ "the", "a", "an", "some", "other", "about",
958
559
  "please", "pls", "plz", "kindly", "just", "simply", "maybe", "perhaps",
959
560
  "thanks", "thank", "ta", "cheers",
960
- // greetings a question sometimes opens with (chat.mjs owns standalone greetings;
961
- // here they're only stripped when embedded in an otherwise-real question)
962
561
  "hi", "hello", "hey", "yo", "hiya", "howdy", "ok", "okay",
963
- // vocatives / terms of address (the "matey" of the worked example, and its kin)
964
562
  "matey", "mate", "buddy", "pal", "dude", "man", "bro", "bru", "fam",
965
563
  "friend", "sir", "maam", "folks", "guys", "everyone", "dear",
966
- // the product's OWN name used as an address (chatbench cycle 2, ns-hey-tmct:
967
- // "hey tmct, what calls fnAlpha thanks") — a vocative like "matey", stripped
968
- // by the same rules: relaxParse's resolvesExact guard still protects a module
969
- // literally named "tmct", and noise-strip's template/keyword-spot acceptance
970
- // bounds the cost of a mid-question strip to an honest object-miss.
971
564
  "tmct",
972
- // presentation frames — the keyword-spotting strategy's blind spot: the
973
- // compositional grammar skips these as FRAME_WORDS, but "show me what imports X"
974
- // otherwise decomposes (via keyword-spot) to ask{subject:"show me"}. Stripping
975
- // them on a miss recovers the underlying reverse/forward question. ("count" is NOT
976
- // here — it is an aggregate trigger; "find"/"search" are here as presentation
977
- // verbs, not the tmct_search tool, which ask.mjs never dispatches.)
978
565
  "show", "tell", "give", "list", "find", "me", "us", "lemme",
979
566
  ]);
980
567
 
981
- /** Near-canonical words the cascade REWRITES to the closed vocabulary once noise and
982
- * unmatched tokens are gone (SYNONYM-NORMALISE, the cascade's third layer). Kept
983
- * deliberately TINY and aggregate-flavoured: the relation/entity synonyms a developer
984
- * actually types already live in RELATIONS/ENTITY_TO_TYPE (and "uses"/"depends on" are
985
- * mapped there); this table only closes the gap for the count family, whose triggers
986
- * (AGGREGATE_TRIGGERS) don't include the "tally the classes"/"total number of classes"
987
- * register. Each key is also kept OUT of the drop pass (ask.mjs treats a synonym key as
988
- * meaningful) so it survives to be normalised rather than dropped as unmatched. The
989
- * rewrite is guarded by ask.mjs (never applied to a token that resolves to a graph
990
- * entity), so a symbol named "total" is never bent into "count". */
568
+ /** Near-canonical words the cascade rewrites to the closed vocabulary once
569
+ * noise/unmatched tokens are gone. Kept tiny: only closes the count-family
570
+ * gap ("tally the classes") that AGGREGATE_TRIGGERS doesn't cover. */
991
571
  export const CASCADE_SYNONYMS = Object.freeze({
992
572
  tally: "count", tallies: "count", sum: "count", total: "count", totals: "count",
993
573
  });
994
574
 
995
- /** Explicit help / orientation requests when the WHOLE query is one of these, ask.mjs
996
- * shows the rephrase hint DIRECTLY (the honest bottom of the cascade, reached on
997
- * demand) rather than pretending to answer or running the relaxation loop. A closed
998
- * set matched against the whole normalized query only, so "which functions call help"
999
- * (a real question about a symbol named "help") is untouched. Standalone greetings and
1000
- * the chat "/help" command are chat.mjs's own surface; this is the bare CLI ask()
1001
- * entry point's equivalent. */
575
+ /** Explicit help/orientation requests: when the whole query is one of these,
576
+ * ask.mjs shows the rephrase hint directly. */
1002
577
  export const HELP_TRIGGERS = Object.freeze([
1003
578
  "help", "help me", "how do i ask", "how do i use this", "what can i ask",
1004
579
  "what can you ask", "usage", "commands", "examples", "syntax", "options",
1005
580
  ]);
1006
-
1007
- // ---- omitted-on-purpose (judgment calls, not oversights) -------------------
1008
- // "runs"/"executes" (calls) are common English words with many non-code
1009
- // senses — accepted anyway, formal-template AND keyword-spotting alike,
1010
- // because a misparse costs nothing beyond an honest object-miss
1011
- // (resolveObject never guesses); the risk is bounded by the render layer,
1012
- // not by narrowing the vocabulary.
1013
- // NOT added: "needs"/"wants" for imports (too weakly code-specific — "which
1014
- // modules need auth" reads as a feature request, not a graph query, in a way
1015
- // "which modules depend on auth" doesn't); "wraps"/"decorates" for calls (a
1016
- // real but distinct relationship codegraph.mjs doesn't classify separately —
1017
- // adding the phrase would silently misroute it onto plain call edges);
1018
- // "belongs to" for contains (already claimed by a hypothetical membership
1019
- // verb elsewhere — kept out to avoid a future collision if one is added).
1020
- // NOT added: a cochange gerund ("co-changing with") — every cochange phrase
1021
- // is already 2-3 words with its own internal "-ing"/"-s" (e.g. "changes
1022
- // alongside"), so g-drop normalization has no bare stem to dialectally
1023
- // contract in the first place; only relations with a genuinely bare-verb
1024
- // casual form ("call", "touch") needed one.
1025
- // WRONG_WORDS not mapped (judgment calls): "script(s)" (routinely names a real
1026
- // module/identifier — rewriting it could corrupt an object term, and file/files
1027
- // already covers the honest synonym); "package(s)" (a genuinely coarser grain
1028
- // this graph doesn't model — mapping it to module would silently answer a
1029
- // different question than the one asked); "fn" (too short and too often a real
1030
- // identifier fragment to rewrite at a word boundary).