@polycode-projects/the-mechanical-code-talker 1.9.1 → 1.10.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.
Files changed (81) hide show
  1. package/README.md +441 -217
  2. package/bin/tmct.mjs +126 -1
  3. package/corpus/seon/README.md +1 -2
  4. package/package.json +4 -2
  5. package/src/answer-variants.mjs +8 -36
  6. package/src/ask-browser-entry.mjs +5 -23
  7. package/src/ask-browser.bundle.js +1 -2
  8. package/src/ask-nlp.mjs +9 -23
  9. package/src/ask-vocab.mjs +139 -589
  10. package/src/ask.mjs +627 -1729
  11. package/src/chat.mjs +1684 -2874
  12. package/src/cli-args.mjs +14 -28
  13. package/src/codegraph.mjs +236 -644
  14. package/src/completions/complete.mjs +18 -62
  15. package/src/completions/graph-adapter.mjs +14 -60
  16. package/src/completions/group.mjs +12 -68
  17. package/src/completions/infer.mjs +38 -126
  18. package/src/completions/prune.mjs +17 -70
  19. package/src/completions/rank.mjs +16 -69
  20. package/src/completions/search.mjs +8 -31
  21. package/src/concept.mjs +32 -88
  22. package/src/conformance.mjs +11 -15
  23. package/src/corpus/conceptnet.mjs +31 -89
  24. package/src/corpus/templates.mjs +19 -45
  25. package/src/corpus/unknown-ingest.mjs +31 -92
  26. package/src/embed.mjs +10 -22
  27. package/src/extensions.mjs +50 -154
  28. package/src/finish.mjs +35 -91
  29. package/src/grammar/ace.mjs +16 -40
  30. package/src/grammar/assert.mjs +1 -1
  31. package/src/grammar/lexicon-core.json +1 -1
  32. package/src/grammar/lexicon.mjs +9 -27
  33. package/src/graph-merge.mjs +2 -3
  34. package/src/hash.mjs +6 -14
  35. package/src/index.mjs +6 -10
  36. package/src/init.mjs +38 -125
  37. package/src/interpret/fuzzy.mjs +10 -29
  38. package/src/interpret/merge.mjs +9 -27
  39. package/src/interpret/normalize.mjs +137 -585
  40. package/src/interpret/pipeline.mjs +23 -71
  41. package/src/interpret/strategies/ace.mjs +7 -31
  42. package/src/interpret/strategies/constructions.mjs +14 -41
  43. package/src/interpret/strategies/grammar.mjs +21 -60
  44. package/src/interpret/strategies/keywords.mjs +42 -131
  45. package/src/interpret/strategies/noise-strip.mjs +18 -89
  46. package/src/memory/bias.mjs +11 -54
  47. package/src/memory/blocks.mjs +18 -69
  48. package/src/memory/core.mjs +171 -591
  49. package/src/memory/fold.mjs +0 -0
  50. package/src/memory/inspect.mjs +7 -25
  51. package/src/memory/shacl.mjs +10 -39
  52. package/src/memory/trust.mjs +26 -127
  53. package/src/memory-ask-browser-entry.mjs +7 -30
  54. package/src/memory-ask-browser.bundle.js +1 -1
  55. package/src/paraphrase.mjs +20 -53
  56. package/src/planning.mjs +15 -157
  57. package/src/prose-nlp.mjs +4 -17
  58. package/src/prose.mjs +19 -67
  59. package/src/providers/bootstrap.mjs +1 -2
  60. package/src/providers/fixture.mjs +1 -2
  61. package/src/providers/graph-service.mjs +28 -59
  62. package/src/repository-interface.mjs +6 -8
  63. package/src/router/drive.mjs +183 -0
  64. package/src/router/goal-reasoner.mjs +66 -231
  65. package/src/router/guardrail.mjs +20 -58
  66. package/src/router/planner.mjs +15 -46
  67. package/src/router/registry.mjs +13 -43
  68. package/src/router/resolver.mjs +46 -131
  69. package/src/router/results.mjs +231 -0
  70. package/src/schema-docs.mjs +10 -27
  71. package/src/server-http.mjs +10 -19
  72. package/src/server.mjs +22 -28
  73. package/src/sessions.mjs +15 -30
  74. package/src/source-slice.mjs +5 -7
  75. package/src/source.mjs +10 -20
  76. package/src/syllogise.mjs +187 -575
  77. package/src/telemetry.mjs +3 -3
  78. package/src/toml-config.mjs +4 -4
  79. package/src/tui/app.mjs +9 -19
  80. package/src/viz.mjs +66 -123
  81. package/src/wink-model.mjs +10 -24
@@ -1,75 +1,32 @@
1
- // interpret/pipeline.mjs — the multi-strategy interpretation pipeline (ROADMAP
2
- // item 8): run the request through ALL the classes of thing it could be, parse it
3
- // with each class's own strategy, then merge same-class results and surround
1
+ // interpret/pipeline.mjs — the multi-strategy interpretation pipeline: run
2
+ // the request through every strategy, merge same-class results, and surround
4
3
  // distinct-class results with "if you mean X then …" (interpret/merge.mjs).
5
4
  //
6
- // A STRATEGY is a plain object:
7
- // { id, class, run(text, ctx) -> {strategyId, class, candidates:[{parsed,
8
- // confidence?, note?, via?}]} | null }
9
- // `via` marks an APPROXIMATE reading ("fuzzy"/"lemma"/"spell"): the merge
10
- // discards approximate candidates whenever anything parsed exactly (the tier
11
- // discipline exact curated match always wins — held across strategies).
12
- // `run` may be sync or async (Promise-returning); a strategy that THROWS or
13
- // rejects is dropped for that request — one broken strategy never takes the
14
- // pipeline down. Strategies are registered in the STRATEGIES array below in
15
- // PRECEDENCE order (earlier wins same-class dedupe ties and confidence ties) —
16
- // a new strategy (e.g. the Phase-2 ACE grammar, interpret/strategies/ace.mjs)
17
- // joins by pushing an entry here, not by editing the pipeline.
18
- //
19
- // NORMALIZATION PRE-PASS (ROADMAP item 10): interpret() normalizes the input
20
- // once (normalizeInput below — the same §3.5 pipeline + rhetorical frames
21
- // ask.mjs always ran) and hands every strategy the normalized text; the raw
22
- // text rides along in ctx.raw, and the returned record says whether
23
- // normalization changed the input (`normalizationChanged`), so a repaired
24
- // spelling/contraction is on the record, never silent.
5
+ // A STRATEGY is { id, class, run(text, ctx) -> {strategyId, class,
6
+ // candidates:[{parsed, confidence?, note?, via?}]} | null }. `via` marks an
7
+ // APPROXIMATE reading ("fuzzy"/"lemma"/"spell"), discarded whenever anything
8
+ // parsed exactly. `run` may be sync or async; a throwing/rejecting strategy
9
+ // is dropped rather than taking the pipeline down. Registered in STRATEGIES
10
+ // below, in precedence order.
25
11
 
26
12
  import { normalizeQuery, applyNegationFrames, applyPhrasingFrames } from "./normalize.mjs";
27
13
  import { grammarStrategy } from "./strategies/grammar.mjs";
28
14
  import { keywordSpotStrategy } from "./strategies/keywords.mjs";
29
15
  import { noiseStripStrategy } from "./strategies/noise-strip.mjs";
30
- // Optional Node-flavored ACE strategy same viewer-bundle boundary as the
31
- // ask-nlp adapter below: the ACE grammar reaches grammar/ace.mjs -> lexicon.mjs,
32
- // which reads its committed JSON via Node fs, so an inlining viewer bundle strips
33
- // this import; the `typeof` guard where STRATEGIES is built then degrades to an
34
- // ace-less registry instead of throwing over an undeclared identifier. (ACE is
35
- // async-only anyway, so the sync parseQuery path the viewer uses never ran it.)
16
+ // The next three are Node-only (fs/path-reading tooling); an inlining viewer
17
+ // bundle strips them, and the `typeof` guards below degrade to a leaner
18
+ // registry instead of throwing on an undeclared identifier.
36
19
  import { aceStrategy } from "./strategies/ace.mjs";
37
- // Optional Node-only construction-grammar bank (PLAN_ADVANCED_GRAMMAR.md track
38
- // (d)) — same viewer-bundle boundary as ACE and the wink adapter: the loader
39
- // reads its committed TOML data via Node fs/path (interpret/strategies/
40
- // constructions.mjs), so an inlining viewer bundle strips this import too; the
41
- // same `typeof` guard below degrades to a constructions-less registry instead
42
- // of throwing over an undeclared identifier (test/ask-nlp.test.mjs's "viewer
43
- // bundle without wink" test proves the boundary — its bundled file list never
44
- // includes this strategy file, matching ace.mjs's own exclusion there).
45
20
  import { constructionsStrategy } from "./strategies/constructions.mjs";
46
21
  import { mergeStrategyResults } from "./merge.mjs";
47
- // Optional Node-only wink adapter — same viewer-bundle boundary as ask.mjs: an
48
- // inlining bundle strips this import and the `typeof` read below degrades to
49
- // adapter-less parsing instead of throwing over an undeclared identifier.
50
22
  import { nlpAdapter } from "../ask-nlp.mjs";
51
23
 
52
- /** The registered strategies, in precedence order. grammar + keyword-spot are
53
- * the two legacy parsers (one shared class, "graph-query" their merge is
54
- * byte-identical to the original two-way agree/disagree behavior); noise-strip
55
- * is the item-10 tolerant fallback (its own class; it only fires when the
56
- * anchored grammar missed the text as-given, so it can never displace an
57
- * existing template parse). interpret/strategies/ace.mjs (Phase 2 / Stage 2) is
58
- * the ACE-OWL controlled-fragment grammar, registered here as an ADDITIVE, own-
59
- * class ("ace-fact") strategy. It is ASYNC on purpose: runStrategiesSync (the
60
- * parseQuery / CHATBENCH-facing path) SKIPS Promise-returning strategies, so ACE
61
- * adds declarative-fragment reach to interpret() while leaving the sync spine
62
- * byte-stable (see strategies/ace.mjs for the full rationale). The `typeof` guard
63
- * mirrors the nlpAdapter degradation: a stripped ACE import (viewer bundle) leaves
64
- * the identifier undeclared, so the registry is ace-less there instead of a crash.
65
- * interpret/strategies/constructions.mjs (PLAN_ADVANCED_GRAMMAR.md track (d)) is
66
- * the construction-grammar template bank — data-driven (data/templates/
67
- * constructions/*.toml), registered as its own additive, own-class
68
- * ("construction") strategy at grammar-level confidence (0.9) so it outranks a
69
- * same-text keyword-spot guess outright instead of colliding with it (see that
70
- * file's header for why: keyword-spot mis-parses the genitive/compound surface
71
- * forms this bank exists to fix). Sync (unlike ACE), so it participates on the
72
- * parseQuery path too, not just interpret(). */
24
+ /** Registered strategies, in precedence order: grammar + keyword-spot (shared
25
+ * "graph-query" class), noise-strip (fires only when the anchored grammar
26
+ * misses), then the optional ACE and construction-grammar strategies, each
27
+ * in its own class so it surfaces as an alternate rather than colliding with
28
+ * a graph-query guess. ACE is async-only, so it participates via interpret()
29
+ * only, not the sync parseQuery path. */
73
30
  // eslint-disable-next-line no-undef
74
31
  const OPTIONAL_STRATEGIES = [
75
32
  ...(typeof aceStrategy !== "undefined" ? [aceStrategy] : []),
@@ -78,9 +35,8 @@ const OPTIONAL_STRATEGIES = [
78
35
  ];
79
36
  export const STRATEGIES = [grammarStrategy, keywordSpotStrategy, noiseStripStrategy, ...OPTIONAL_STRATEGIES];
80
37
 
81
- /** The documented normalization pre-pass: whitespace-collapse + the §3.5
82
- * normalization pipeline + the closed rhetorical-frame rewrites, applied ONCE
83
- * before any strategy runs. Returns {raw, text, changed}. */
38
+ /** Whitespace-collapse + normalization pipeline + rhetorical-frame rewrites,
39
+ * applied once before any strategy runs. Returns {raw, text, changed}. */
84
40
  export function normalizeInput(input) {
85
41
  const raw = String(input || "").trim().replace(/\s+/g, " ");
86
42
  const text = raw ? applyPhrasingFrames(applyNegationFrames(normalizeQuery(raw))) : "";
@@ -91,10 +47,8 @@ function defaultNlp() {
91
47
  return typeof nlpAdapter === "function" ? nlpAdapter() : null;
92
48
  }
93
49
 
94
- /** Synchronous strategy run — the path ask.mjs's parseQuery routes through (its
95
- * callers are synchronous). Skips a strategy that returns a Promise (an async
96
- * strategy can only participate via interpret()); a throwing strategy is
97
- * dropped, never a crash. Returns the strategy results in precedence order. */
50
+ /** Synchronous strategy run (ask.mjs's parseQuery path). Skips a Promise-
51
+ * returning strategy and drops a throwing one; returns results in precedence order. */
98
52
  export function runStrategiesSync(text, ctx = {}, strategies = STRATEGIES) {
99
53
  const results = [];
100
54
  for (const s of strategies) {
@@ -117,10 +71,8 @@ export function runStrategiesSync(text, ctx = {}, strategies = STRATEGIES) {
117
71
  * // {ambiguousParse, candidates} tie), or null
118
72
  * alternates } // distinct-class runners-up for the
119
73
  * // "if you mean X then …" surround
120
- * `ctx.strategies` overrides the registry (tests, embedders); `ctx.nlp`
121
- * overrides the lemma/POS adapter exactly as ask()'s own option does. Pure
122
- * given (text, strategies, adapter) — no graph access here: resolving terms
123
- * against a graph stays ask.mjs's job downstream. */
74
+ * `ctx.strategies`/`ctx.nlp` override the registry/lemma adapter. No graph
75
+ * access here resolving terms against a graph is ask.mjs's job. */
124
76
  export async function interpret(text, ctx = {}) {
125
77
  const strategies = ctx.strategies || STRATEGIES;
126
78
  const { raw, text: normalized, changed } = normalizeInput(text);
@@ -1,34 +1,11 @@
1
- // interpret/strategies/ace.mjs — the ACE-OWL controlled-fragment grammar wired
2
- // into the interpretation pipeline as an ADDITIVE strategy (Stage 2, "ACE reach").
3
- //
4
- // The ACE engine (src/grammar/ace.mjs) has existed since Phase 2, but its pipeline
5
- // ADAPTER was the "real and empty" seam the pipeline header names (interpret/
6
- // pipeline.mjs). This file fills it. The contract is strictly ADD-ONLY:
7
- //
8
- // · Its own class, "ace-fact" — DISJOINT from the graph-query strategies, so a
9
- // clean ACE parse is a distinct-class ALTERNATE ("if you mean X then …"), never
10
- // a same-class competitor that could displace a graph-query winner.
11
- // · It emits a candidate ONLY on a CLEAN parse (parseAce returns triples). A
12
- // structural-fit-with-residue (empty triples) or a total miss returns null, so
13
- // a query sentence that merely LOOKS relation-shaped ("which modules import X",
14
- // whose ACE residue is the "which") contributes nothing — fitting the grammar
15
- // is a strong signal; missing it is a FEATURE and the tolerant strategies win.
16
- //
17
- // WHY ASYNC — the byte-stability guarantee. ask.mjs's parseQuery (the CHATBENCH
18
- // chat-facing path) runs strategies through runStrategiesSync, which — by the
19
- // pipeline's documented contract — SKIPS any Promise-returning strategy ("an async
20
- // strategy can only participate via interpret()"). Registering ACE async therefore
21
- // makes the sync parseQuery path PROVABLY untouched (CHATBENCH neutral, byte-for-
22
- // byte) while interpret() — the async pipeline — gains the declarative-fragment
23
- // reach. The work parseAce does is synchronous; the async wrapper is deliberate,
24
- // the mechanical seam that keeps the chat spine frozen. (grammar/ace.mjs itself is
25
- // imported UNCHANGED — no chat-facing edit.)
1
+ // interpret/strategies/ace.mjs — the ACE-OWL controlled-fragment grammar
2
+ // (src/grammar/ace.mjs) wired into the pipeline as an additive strategy: its
3
+ // own class ("ace-fact"), so a clean parse surfaces as an alternate, never
4
+ // displacing a graph-query winner.
26
5
 
27
6
  import { parseAce } from "../../grammar/ace.mjs";
28
7
 
29
- /** Adapter: a clean ACE parse -> one candidate in its own class; anything else
30
- * (residue-only structural fit, or a hard miss) -> null. `via:"exact"` — a
31
- * controlled-grammar fit is exact evidence, never an approximate rewrite. */
8
+ /** A clean ACE parse -> one candidate; a residue-only fit or a miss -> null. */
32
9
  export function runAce(text) {
33
10
  let parsed = null;
34
11
  try { parsed = parseAce(text); } catch { return null; }
@@ -36,9 +13,8 @@ export function runAce(text) {
36
13
  return { strategyId: "ace", class: "ace-fact", candidates: [{ parsed, confidence: 0.85, via: "exact", note: `ACE ${parsed.pattern}` }] };
37
14
  }
38
15
 
39
- /** Pipeline registration (interpret/pipeline.mjs). ASYNC on purpose (see file
40
- * header): it participates in interpret() but is SKIPPED by runStrategiesSync,
41
- * so parseQuery — and the CHATBENCH spine it feeds — is byte-stable. */
16
+ /** Registered ASYNC so runStrategiesSync (the sync parseQuery path) skips it,
17
+ * keeping that path byte-stable. */
42
18
  export const aceStrategy = {
43
19
  id: "ace",
44
20
  class: "ace-fact",
@@ -1,30 +1,12 @@
1
- // interpret/strategies/constructions.mjs — strategy N+1: construction-grammar
2
- // template banks (PLAN_ADVANCED_GRAMMAR.md track (d)). Per-construction closed
3
- // template families loaded as DATA from data/templates/constructions/*.toml
4
- // (pattern -> AST skeleton, slot types validated against the closed RELATIONS/
5
- // ENTITY_TO_TYPE vocabulary ask-vocab.mjs already owns), registered here as its
6
- // OWN additive class ("construction") — the same "own-class strategy" pattern
7
- // interpret/strategies/ace.mjs and noise-strip.mjs already use, so a construction
8
- // match outranks a same-text keyword-spot GUESS outright (interpret/merge.mjs
9
- // picks the highest-confidence CLASS; within-class disagreement is the honest
10
- // {ambiguousParse} tie, which this strategy deliberately avoids triggering
11
- // against keyword-spot by living in its own class) rather than colliding with it.
1
+ // interpret/strategies/constructions.mjs — construction-grammar template
2
+ // banks, loaded as DATA from data/templates/constructions/*.toml (pattern ->
3
+ // AST skeleton, slot types validated against RELATIONS/ENTITY_TO_TYPE),
4
+ // registered as its own additive class ("construction") so a match outranks
5
+ // a same-text keyword-spot guess rather than colliding with it.
12
6
  //
13
- // The point (mirrors grammar.mjs's own file-header precedent, "same shape, new
14
- // grammatical coverage, not a new mechanism"): grammar GROWTH as committed data,
15
- // not more normalize.mjs/grammar.mjs code — data/templates/grammar-rules.toml
16
- // and data/templates/responses.jsonl already work this way. Continues
17
- // grammar.mjs's T1-T10 numbering (T11+, see the TOML file's own [[construction]]
18
- // `id` fields) without renumbering anything grammar.mjs already owns.
19
- //
20
- // Loader discipline (mirrors src/finish.mjs's loadGrammarRules/grammarRules
21
- // pattern exactly): synchronous (the pipeline is sync-capable), cached once per
22
- // process, and DEFENSIVE — a missing directory, unparseable TOML, or an entry
23
- // that fails validation (an unrecognized `kind`/`entityType`, a malformed
24
- // pattern) is silently DROPPED, never thrown and never guessed into the nearest
25
- // match. "One broken strategy/entry never takes the pipeline down"
26
- // (interpret/pipeline.mjs's own file-header discipline) extends here to one
27
- // broken DATA ROW never taking the strategy down.
7
+ // Loader is synchronous, cached once per process, and defensive: a missing
8
+ // directory, unparseable TOML, or invalid entry is silently dropped, never
9
+ // thrown.
28
10
 
29
11
  import { readFileSync, readdirSync } from "node:fs";
30
12
  import { fileURLToPath } from "node:url";
@@ -72,8 +54,7 @@ export function readConstructionFiles(dir = CONSTRUCTIONS_DIR) {
72
54
  }
73
55
 
74
56
  /** Validate + index the raw [[relation]] rows into noun -> {kind, entityType}.
75
- * Closed-vocabulary validation (the whole point of track (d)'s "slot types
76
- * validated against ENTITY_TO_TYPE/VERB_TO_KIND" deliverable): `kind` MUST be
57
+ * Closed-vocabulary validation: `kind` MUST be
77
58
  * one of RELATIONS' own keys and `entityType` (when present) MUST be one of
78
59
  * ENTITY_TO_TYPE's canonical class names — an entry failing either check is
79
60
  * dropped, never coerced to the nearest-looking valid value. First occurrence
@@ -92,13 +73,8 @@ export function buildAgentNounTable(relations) {
92
73
  return table;
93
74
  }
94
75
 
95
- /** Compile one pattern string ("<AGENT> of <TERM>") against the closed agent-
96
- * noun alternation into {re, agentIndex, termIndex}, or null when the pattern
97
- * doesn't carry exactly one <AGENT> and one <TERM> token (a malformed pattern
98
- * — dropped, not guessed at). Literal text is escaped and whitespace-
99
- * normalized (\s+), matching every other anchored-template regex in this
100
- * codebase (grammar.mjs's own TEMPLATES). Case-insensitive; tolerates one
101
- * optional trailing "?", same as grammar.mjs's own templates. */
76
+ /** Compile one pattern string ("<AGENT> of <TERM>") into {re, agentIndex,
77
+ * termIndex}, or null when it doesn't carry exactly one of each token. */
102
78
  function compilePattern(pattern, agentNouns) {
103
79
  if (typeof pattern !== "string" || !pattern.trim() || !agentNouns.length) return null;
104
80
  const agentAlt = agentNouns.slice().sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
@@ -189,12 +165,9 @@ export function parseConstruction(text, bank = constructionBank()) {
189
165
  return null;
190
166
  }
191
167
 
192
- /** Pipeline registration (interpret/pipeline.mjs): construction-grammar
193
- * templates as their OWN class ("construction"), confidence 0.9 the same
194
- * evidentiary weight as grammar.mjs's anchored T1-T10 (an anchored, closed
195
- * pattern match), so it outright outranks a same-text "graph-query"-class
196
- * keyword-spot guess (0.7) instead of triggering a same-class {ambiguousParse}
197
- * tie against it (see this file's header). */
168
+ /** Pipeline registration: own class ("construction"), confidence 0.9 — same
169
+ * weight as grammar.mjs's anchored templates, so it outranks a same-text
170
+ * keyword-spot guess (0.7) rather than tying against it. */
198
171
  export const constructionsStrategy = {
199
172
  id: "constructions",
200
173
  class: "construction",
@@ -18,21 +18,9 @@ const MODIFIER_ALT = Object.keys(MODIFIER_TO_KIND).sort((a, b) => b.length - a.l
18
18
  const META_ALT = META_MEANING_VERBS.slice().sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
19
19
 
20
20
  const TEMPLATES = [
21
- // T1 ASK: "does X import Y" / "is X a subclass of Y" -> Yes/No. Tried FIRST: it starts with
22
- // does/is/do/did, which the reverse/forward templates below never match (those start with
23
- // which/what), so precedence between T1 and the rest is structural, not a tie-break guess.
24
- // "did" joins does/do for the past-tense commit forms ("did commit <sha> touch X").
25
- // REVERSE VERB SWAP (Seonix Batch 2 Fix 2): this template fixes subject/object by regex
26
- // capture POSITION, not by the verb's semantic direction — fine for every forward verb
27
- // ("subclass of", "imports", …), but "is X a superclass of Y" MEANS the reverse of "is X
28
- // a subclass of Y" (Y inherits from X, not X from Y). INHERITS_REVERSE_VERBS (ask-vocab.mjs)
29
- // is the closed set of such reverse phrasings; when the matched verb is one of them,
30
- // subject/object are swapped here, once, at parse time — so downstream evaluation (ask.mjs)
31
- // sees "is Y a subclass of X" and needs zero changes of its own. (In practice this exact
32
- // regex only ever matches a does/do/did lead, so "is …" phrasings actually reach the "ask"
33
- // shape via keywords.mjs's decomposition strategy instead — that strategy applies the same
34
- // swap for the same reason; this branch is kept for any does/do/did-led phrasing that names
35
- // a reverse verb, and for structural symmetry with that sibling strategy.)
21
+ // T1 ASK: "does X import Y" -> Yes/No. REVERSE VERB SWAP: a semantically-reverse
22
+ // verb ("superclass of") means the opposite of its forward counterpart, so
23
+ // INHERITS_REVERSE_VERBS (ask-vocab.mjs) swaps subject/object here at parse time.
36
24
  {
37
25
  name: "ask",
38
26
  re: new RegExp(`^(?:does|do|did)\\s+(.+?)\\s+(${VERB_ALT})\\s+(.+?)\\??$`, "i"),
@@ -45,7 +33,7 @@ const TEMPLATES = [
45
33
  return { shape: "ask", entityType: null, modifier: "direct", kind, subject, object };
46
34
  },
47
35
  },
48
- // T2 reverse: "which <entity> [<modifier>] <verb> <object>" — the operator's own example shape.
36
+ // T2 reverse: "which <entity> [<modifier>] <verb> <object>".
49
37
  {
50
38
  name: "reverse",
51
39
  re: new RegExp(`^which\\s+(${ENTITY_ALT})\\s+(?:(${MODIFIER_ALT})\\s+)?(${VERB_ALT})\\s+(.+?)\\??$`, "i"),
@@ -67,51 +55,24 @@ const TEMPLATES = [
67
55
  kind: VERB_TO_KIND[m[2].toLowerCase()], object: m[1].trim(),
68
56
  }),
69
57
  },
70
- // T4 meta: "what does <term> mean" — a question about the GRAPH'S OWN VOCABULARY
71
- // (a SchemaClass/SchemaPredicate label, e.g. "cochange", or a raw prop token, e.g.
72
- // "mgx:callsSymbol"), not a graph traversal over code edges. Tried after T3: T3 also
73
- // starts "what does/do", but T3 only fires when the tail is a relation VERB_ALT
74
- // phrase ("import"/"calls"/…), which "mean"/"means"/etc never are (disjoint tables —
75
- // ask-vocab.mjs's file comment explains why they're kept separate), so the two never
76
- // actually compete for the same input.
58
+ // T4 meta: "what does <term> mean" — a question about the graph's own vocabulary,
59
+ // not a graph traversal. VERB_ALT and META_ALT are disjoint tables, so this never
60
+ // competes with T3 for the same input.
77
61
  {
78
62
  name: "meta-mean",
79
63
  re: new RegExp(`^what\\s+(?:does|do|is|are)\\s+(.+?)\\s+(?:${META_ALT})\\??$`, "i"),
80
64
  build: (m) => ({ shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: m[1].trim() }),
81
65
  },
82
- // T5 meta: "what is a/an <term>" — the OTHER worked phrasing ("what is a Commit").
83
- // Seonix Batch 2 Fix 1: the indefinite article is now OPTIONAL, but the BARE
84
- // (no-article) form is restricted to the CLOSED vocabulary ENTITY_TO_TYPE already
85
- // imported above (function/method/class/module/attribute/variable/change/commit,
86
- // singular and plural) — build() returns null (same "this template didn't actually
87
- // match, keep scanning" contract T8/"when" below already relies on — see
88
- // parseAnchored's own docblock) when the bare form's object isn't one of those
89
- // closed terms, so the scan falls through exactly as if this template had not
90
- // matched at all. This keeps "what is a doohickey"/"widget"/"gizmo" (still routed
91
- // here via the WITH-article, fully unrestricted `(.+?)` path — pinned to resolve as
92
- // an honest meta miss downstream, ask-combo.test.mjs/chat-readback.test.mjs) working
93
- // unmodified, while ALSO keeping the two pinned bare-form honest misses intact:
94
- // "what is the meaning of this codebase" (ask.test.mjs/ask-dual-strategy.test.mjs)
95
- // and "what is exposed" (ask.test.mjs:840) both have bare objects absent from
96
- // ENTITY_TO_TYPE, so build() still rejects them and they still fall through to null.
97
- // Fix 3: stripTrailingScopeFiller (ask-vocab.mjs) trims a curated trailing clause
98
- // ("what is a Module in this graph" -> "Module") off the object before it's
99
- // returned, so a scoping tail never corrupts the lookup term either the bare-form
100
- // check above or downstream resolution/rendering perform. HANDOVER.md 2026-07-10
101
- // item 8: stripTrailingDiscourseTag trims a bare trailing "then"/"though" the
102
- // same way ("what is a component then" -> "component") — applied first, since a
103
- // discourse tag sits outermost when both happen to stack.
66
+ // T5 meta: "what is a/an <term>" — the bare (no-article) form is restricted to
67
+ // the closed ENTITY_TO_TYPE vocabulary (build() -> null otherwise, falling
68
+ // through); the WITH-article form is unrestricted.
104
69
  {
105
70
  name: "meta-whatis",
106
71
  re: new RegExp(`^what\\s+(?:is|are)\\s+(?:(an?)\\s+)?(.+?)\\??$`, "i"),
107
72
  build: (m) => {
108
73
  const object = stripTrailingDiscourseTag(m[2].trim());
109
74
  if (!m[1] && !ENTITY_TO_TYPE[object.toLowerCase()]) return null; // bare form: closed-set only
110
- // "what is a kind of X" / "what is a subclass of X" (ARTICLE_RELATION_CONTINUATIONS,
111
- // ask-vocab.mjs): the captured object is itself the tail of a registered inherits
112
- // verb's own "is a .../are a ..." phrasing, not a term to define — reject so this
113
- // template yields no candidate at all and only keyword-spot's (unambiguous) reverse-
114
- // inherits reading survives, instead of a spurious meta/inherits {ambiguousParse} tie.
75
+ // "what is a kind/subclass of X" is an inherits phrasing, not a term to define.
115
76
  const objLower = object.toLowerCase();
116
77
  if (m[1] && ARTICLE_RELATION_CONTINUATIONS.some(
117
78
  (c) => objLower === c || objLower.startsWith(`${c} `),
@@ -119,8 +80,8 @@ const TEMPLATES = [
119
80
  return { shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: stripTrailingScopeFiller(object) };
120
81
  },
121
82
  },
122
- // T6 mention: "where is <term> mentioned/referenced" — the prose/mentions surface
123
- // (2026-07-02 query families). Tried BEFORE T7: T7's trailing marker is optional,
83
+ // T6 mention: "where is <term> mentioned/referenced" — the prose/mentions surface.
84
+ // Tried BEFORE T7: T7's trailing marker is optional,
124
85
  // so without this ordering it would swallow the mention question and lose the
125
86
  // marker that distinguishes "locate the definition" from "list the prose mentions".
126
87
  {
@@ -148,19 +109,19 @@ const TEMPLATES = [
148
109
  ? { shape: "when", entityType: null, modifier: "direct", kind: "touches", object: m[1].trim() }
149
110
  : null),
150
111
  },
151
- // T9 commit-history NP (PLAN_CHAT_FEEL item 6 remainder): "the commit history of
152
- // X" / "commit history for X" — an NP form of T8's SAME "when did X change"
153
- // intent; reuses shape="when" verbatim so evaluation/rendering are byte-
154
- // identical, only the recognizer surface differs.
112
+ // T9 commit-history NP: "the commit history of X" / "commit history for X" —
113
+ // an NP form of T8's SAME "when did X change" intent; reuses shape="when"
114
+ // verbatim so evaluation/rendering are byte-identical, only the recognizer
115
+ // surface differs.
155
116
  {
156
117
  name: "commit-history",
157
118
  re: /^(?:the\s+)?commit\s+history\s+(?:of|for)\s+(.+?)\??$/i,
158
119
  build: (m) => ({ shape: "when", entityType: null, modifier: "direct", kind: "touches", object: m[1].trim() }),
159
120
  },
160
- // T10 cochange-partners NP (PLAN_CHAT_FEEL item 6 remainder): "cochange partners
161
- // of X" — an NP form of the existing "which modules cochange with X" verb-phrase
162
- // shape (ask-vocab.mjs's cochange verb table); reuses shape="reverse"/
163
- // kind="cochange" so evaluation is byte-identical.
121
+ // T10 cochange-partners NP: "cochange partners of X" — an NP form of the
122
+ // existing "which modules cochange with X" verb-phrase shape (ask-vocab.mjs's
123
+ // cochange verb table); reuses shape="reverse"/kind="cochange" so evaluation
124
+ // is byte-identical.
164
125
  {
165
126
  name: "cochange-partners",
166
127
  re: /^co-?change\s+partners\s+(?:of|for|with)\s+(.+?)\??$/i,