@polycode-projects/the-mechanical-code-talker 1.10.14 → 1.11.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.
@@ -62,6 +62,7 @@
62
62
  var createRequireFromPath = unavailable("createRequireFromPath");
63
63
  var spawnSync = unavailable("spawnSync");
64
64
  var createInterface = unavailable("createInterface");
65
+ var createServer = unavailable("createServer");
65
66
  var DatabaseSync = unavailable("DatabaseSync");
66
67
 
67
68
  // node-stub:node:path
@@ -92,6 +93,7 @@
92
93
  var createRequireFromPath2 = unavailable2("createRequireFromPath");
93
94
  var spawnSync2 = unavailable2("spawnSync");
94
95
  var createInterface2 = unavailable2("createInterface");
96
+ var createServer2 = unavailable2("createServer");
95
97
  var DatabaseSync2 = unavailable2("DatabaseSync");
96
98
 
97
99
  // node-stub:node:url
@@ -121,6 +123,7 @@
121
123
  var createRequireFromPath3 = unavailable3("createRequireFromPath");
122
124
  var spawnSync3 = unavailable3("spawnSync");
123
125
  var createInterface3 = unavailable3("createInterface");
126
+ var createServer3 = unavailable3("createServer");
124
127
  var DatabaseSync3 = unavailable3("DatabaseSync");
125
128
 
126
129
  // node-stub:node:fs/promises
@@ -149,6 +152,7 @@
149
152
  var createRequireFromPath4 = unavailable4("createRequireFromPath");
150
153
  var spawnSync4 = unavailable4("spawnSync");
151
154
  var createInterface4 = unavailable4("createInterface");
155
+ var createServer4 = unavailable4("createServer");
152
156
  var DatabaseSync4 = unavailable4("DatabaseSync");
153
157
 
154
158
  // src/memory/trust.mjs
@@ -198,11 +202,36 @@
198
202
  var RULE_KIND_COMPOSE2 = "compose2";
199
203
  var RULE_KIND_FILTER = "filter";
200
204
  var RULE_KIND_RECURSIVE = "recursive";
201
- var RULE_KINDS = Object.freeze([RULE_KIND_COMPOSE2, RULE_KIND_FILTER, RULE_KIND_RECURSIVE]);
205
+ var RULE_KIND_ACTION_SIGNATURE = "action-signature";
206
+ var RULE_KIND_ACTION_PRECOND = "action-precond";
207
+ var RULE_KIND_ACTION_EFFECT = "action-effect";
208
+ var RULE_KINDS = Object.freeze([
209
+ RULE_KIND_COMPOSE2,
210
+ RULE_KIND_FILTER,
211
+ RULE_KIND_RECURSIVE,
212
+ RULE_KIND_ACTION_SIGNATURE,
213
+ RULE_KIND_ACTION_PRECOND,
214
+ RULE_KIND_ACTION_EFFECT
215
+ ]);
202
216
  var RULE_SLOT_SPEC = {
203
217
  [RULE_KIND_COMPOSE2]: [["base1", "mgx:ruleBase1"], ["base2", "mgx:ruleBase2"]],
204
218
  [RULE_KIND_FILTER]: [["base", "mgx:ruleBase1"], ["property", "mgx:ruleFilterProperty"]],
205
- [RULE_KIND_RECURSIVE]: [["baseCase", "mgx:ruleBaseCase"], ["recStep", "mgx:ruleRecStep"]]
219
+ [RULE_KIND_RECURSIVE]: [["baseCase", "mgx:ruleBaseCase"], ["recStep", "mgx:ruleRecStep"]],
220
+ [RULE_KIND_ACTION_SIGNATURE]: [
221
+ ["subjectClass", "mgx:ruleActionSubjectClass"],
222
+ ["targetClass", "mgx:ruleActionTargetClass"]
223
+ ],
224
+ [RULE_KIND_ACTION_PRECOND]: [
225
+ ["shape", "mgx:ruleActionPrecondShape"],
226
+ ["predicate", "mgx:ruleActionPrecondPredicate"],
227
+ ["role", "mgx:ruleActionPrecondRole"],
228
+ ["scope", "mgx:ruleActionPrecondScope"]
229
+ ],
230
+ [RULE_KIND_ACTION_EFFECT]: [
231
+ ["predicate", "mgx:ruleActionEffectPredicate"],
232
+ ["subjectRole", "mgx:ruleActionEffectSubject"],
233
+ ["objectRole", "mgx:ruleActionEffectObject"]
234
+ ]
206
235
  };
207
236
 
208
237
  // src/codegraph.mjs
@@ -228,10 +257,7 @@
228
257
  relations,
229
258
  truncated,
230
259
  generatedAt: payload?.generated_at || null,
231
- // Second pass (PLAN_PROSE_INDEX.md): word -> [individual ids], passed through
232
- // byte-identical from the payload so ask.mjs's resolveObject can consult it as a
233
- // fallback tier without reaching back into the raw payload itself. {} when the
234
- // build had prose disabled or the payload predates this field.
260
+ // word -> [individual ids]; {} when prose was disabled at build time
235
261
  proseIndex: payload?.proseIndex || {}
236
262
  };
237
263
  }
@@ -246,9 +272,7 @@
246
272
  "seon:hassupertype": "inherits",
247
273
  "mgx:changecoupledwith": "cochange",
248
274
  "mgx:reexports": "reexports",
249
- // fine-grained symbol-level edges (Commit→symbol history, fn→fn in-repo calls).
250
- // These stay SEPARATE kinds from the module-coarse "touches"/"calls" so the impact
251
- // closure (module-coarse) is unchanged.
275
+ // symbol-level edges stay separate kinds so the module-coarse impact closure is unchanged
252
276
  "mgx:touchessymbol": "touchesSymbol",
253
277
  "mgx:callssymbol": "callsSymbol",
254
278
  // legacy tokens (pre-realign graphs) — kept so a stale artifact still classifies
@@ -261,20 +285,12 @@
261
285
  "mg:defines": "defines",
262
286
  "mg:tests": "tests",
263
287
  "mg:touches": "touches",
264
- // memory-graph predicates (src/memory/core.mjs, src/sessions.mjs) each maps to
265
- // itself as its own kind name (no module-rollup abbreviation needed, unlike
266
- // imports/calls) so adjacencyForKinds/edgesOfKind can walk the memory graph too.
288
+ // memory-graph predicates map to themselves so adjacencyForKinds/edgesOfKind can walk them too
267
289
  "mgx:saidinsession": "saidInSession",
268
290
  "mgx:inreplyto": "inReplyTo",
269
291
  "mgx:statedby": "statedBy",
270
292
  "mgx:canonicalisedfrom": "canonicalisedFrom",
271
- // PLAN_VIZ_MEMORY.md Bug 2 fix: the two FIXED structural link kinds
272
- // deriveFactTermGraph (below) synthesizes on every Fact — Fact -> its own
273
- // subject/object Term individual. Without these a walk seeded on a Fact (the
274
- // default mostRecentIndividual seed right after a teach turn) could never
275
- // reach the term graph at all. Distinct from the per-predicate kinds below
276
- // (an open-ended, DYNAMIC set — see relationKind's "factrel:" branch), these
277
- // two are fixed and few, so a plain PROP_KIND row is the simplest fit.
293
+ // structural links deriveFactTermGraph synthesizes on every Fact (Fact -> its own subject/object Term)
278
294
  "mgx:factsubjectterm": "factSubjectTerm",
279
295
  "mgx:factobjectterm": "factObjectTerm"
280
296
  };
@@ -723,8 +739,8 @@
723
739
  imports: {
724
740
  comment: "Module -> Module: subject's import graph references object (usesComplexType).",
725
741
  verbs: [
726
- // formal/neutral ("uses"/"use" moved to the `uses` union family, 2026-07-02 —
727
- // "uses code from" stays here: its phrasing is specifically import-flavored)
742
+ // formal/neutral ("uses code from" stays here: its phrasing is
743
+ // specifically import-flavored)
728
744
  "couples to",
729
745
  "couple to",
730
746
  "depends on",
@@ -753,17 +769,12 @@
753
769
  "are wired to",
754
770
  "is hooked up to",
755
771
  "are hooked up to",
756
- // gerund (g-drop normalization turns dialectal "importin'" into this — §3.5)
772
+ // gerund (g-drop normalization turns dialectal "importin'" into this)
757
773
  "importing"
758
774
  ]
759
775
  },
760
- // "uses" is a QUERY-side union family, not a stored predicate (2026-07-02 query
761
- // families): "what uses X" honestly means BOTH the import graph and the call
762
- // graph, so ask.mjs traverses it as imports + calls + callsSymbol together
763
- // (KIND_UNIONS there). The verbs moved here FROM imports — "which modules use X"
764
- // still answers with the importing modules (the asked Module grain filters the
765
- // union down to module-grain subjects), and "what uses <function>" now also
766
- // reaches the symbol-grain callers instead of silently ignoring them.
776
+ // query-side union, not a stored predicate: ask.mjs traverses "uses" as
777
+ // imports + calls + callsSymbol together (KIND_UNIONS).
767
778
  uses: {
768
779
  comment: "query-side union: imports (Module->Module) + calls (Module->Module) + callsSymbol (fn->fn).",
769
780
  verbs: [
@@ -772,7 +783,7 @@
772
783
  "used by",
773
784
  "makes use of",
774
785
  "make use of",
775
- // gerund (g-drop normalization — §3.5)
786
+ // gerund (g-drop normalization)
776
787
  "using"
777
788
  ]
778
789
  },
@@ -798,7 +809,7 @@
798
809
  "fire",
799
810
  "kicks off",
800
811
  "kick off",
801
- // gerund (g-drop normalization turns dialectal "callin'" into this — §3.5)
812
+ // gerund (g-drop normalization turns dialectal "callin'" into this)
802
813
  "calling"
803
814
  ]
804
815
  },
@@ -813,7 +824,7 @@
813
824
  "have",
814
825
  "holds",
815
826
  "hold",
816
- // gerund (g-drop normalization — §3.5)
827
+ // gerund (g-drop normalization)
817
828
  "defining"
818
829
  ]
819
830
  },
@@ -832,7 +843,7 @@
832
843
  "sit in",
833
844
  "sits inside",
834
845
  "sit inside",
835
- // gerund (g-drop normalization — §3.5)
846
+ // gerund (g-drop normalization)
836
847
  "containing"
837
848
  ]
838
849
  },
@@ -851,7 +862,7 @@
851
862
  "check",
852
863
  "makes sure of",
853
864
  "make sure of",
854
- // gerund (g-drop normalization — §3.5)
865
+ // gerund (g-drop normalization)
855
866
  "testing"
856
867
  ]
857
868
  },
@@ -860,15 +871,6 @@
860
871
  verbs: [
861
872
  "inherits from",
862
873
  "inherit from",
863
- // bare "inherits"/"inherit" (Tier 6 playtest, §3b surface-variation axis):
864
- // this list's own SIBLING verb "extends"/"extend" already works bare, with
865
- // no "from" required, but "inherits"/"inherit" — arguably the MORE common
866
- // everyday phrasing of the two ("TaskController inherits Controller",
867
- // "does TaskController inherit Controller") — had no bare form at all,
868
- // only the "... from" variant. VERB_ALT's longest-first sort (already
869
- // relied on elsewhere in this file for the same reason) means "inherits
870
- // from"/"inherit from" still win whenever "from" actually follows, so
871
- // this is purely additive.
872
874
  "inherits",
873
875
  "inherit",
874
876
  "extends",
@@ -885,22 +887,13 @@
885
887
  "are built off",
886
888
  "is built on top of",
887
889
  "are built on top of",
888
- // gerund (g-drop normalization — §3.5, and the compositional grammar's
889
- // gerund-led boolean gate: "classes inheriting from Base but not tested").
890
- // Both the two-word "inheriting from" (so "from" is consumed into the verb
891
- // phrase, not the object term) and the bare "inheriting" (the single token
892
- // the gerund-lead check reads) are listed; longest-match-first prefers the
893
- // two-word form when "from" follows.
890
+ // gerund, incl. compositional grammar's gerund-led boolean gate
891
+ // ("classes inheriting from Base but not tested")
894
892
  "extending",
895
893
  "inheriting from",
896
894
  "inheriting",
897
895
  "subclassing",
898
896
  "extends from",
899
- // REVERSE phrasings (Seonix Batch 2 Fix 2, see INHERITS_REVERSE_VERB_LIST's own
900
- // comment above): "is/are a|the superclass/parent class of" — folded in here so
901
- // VERB_TO_KIND maps them to "inherits" like every other verb in this list; the
902
- // subject/object SWAP their direction requires is handled at parse time by the
903
- // strategies that build the "ask" shape, not here.
904
897
  ...INHERITS_REVERSE_VERB_LIST
905
898
  ]
906
899
  },
@@ -923,13 +916,8 @@
923
916
  "were tweaked in",
924
917
  "got changed in",
925
918
  "got edited in",
926
- // commit-question forms (2026-07-02, viewer commit-chat fix): the same touch
927
- // relation asked from the commit's side — "which changes touch commit <sha>",
928
- // "what did commit <sha> touch", "which functions changed in <sha>", "which
929
- // changes landed in commit <sha>", "what was touched by commit <sha>". Bare
930
- // "touch" completes the touched/touches pair for the "did … touch" auxiliary
931
- // form; the "by"/"in" phrases are the passive/locative counterparts of forms
932
- // already above (curated per register spread, not a thesaurus dump).
919
+ // the same touch relation asked from the commit's side ("what did
920
+ // commit <sha> touch")
933
921
  "touch",
934
922
  "touched by",
935
923
  "modified by",
@@ -937,23 +925,15 @@
937
925
  "changed in",
938
926
  "landed in",
939
927
  "land in",
940
- // contents-of-a-commit forms (2026-07-02, operator screenshot: "what was in
941
- // commit <sha>" missed on the live site). "was in"/"went into" only read as
942
- // touch questions when the object is a commit — the sha-shaped object keeps
943
- // them from firing on structural questions ("is X in the graph" has no verb
944
- // match anyway). Judgment call: "is in" omitted — too generic without the
945
- // past-tense anchor and risks matching containment phrasings.
928
+ // "was in"/"went into" only read as touch questions against a
929
+ // sha-shaped object
946
930
  "was in",
947
931
  "were in",
948
932
  "went into",
949
933
  "included in",
950
- // when-question forms (2026-07-02 query families): "when was X last
951
- // updated/edited" — bare past participles that only read naturally in the
952
- // temporal shape; the when template routes them, but they are ordinary
953
- // touches verbs so "which modules were updated ..." keeps working too.
954
934
  "updated",
955
935
  "edited",
956
- // gerund (g-drop normalization — §3.5)
936
+ // gerund (g-drop normalization)
957
937
  "touching"
958
938
  ]
959
939
  },
@@ -971,19 +951,9 @@
971
951
  "tend to change together with",
972
952
  "moves together with",
973
953
  "move together with",
974
- // Track-1 trio (temporal lever): the bare "changed/change together with" form
975
- // (no "tends to"/"tend to" prefix) was missing outright — "which modules
976
- // changed together with X" fell through to the "touch(ed)" verb instead (a
977
- // Commit->Module kind, structurally unable to match a Module subject), always
978
- // producing a confidently-empty answer regardless of real cochange data.
979
954
  "changed together with",
980
955
  "change together with",
981
956
  "changes together with",
982
- // Present-tense bare form (Seonix Batch 4/5 follow-up): only the past tense
983
- // "changed with" existed above — "what changes with X"/"what usually changes
984
- // with X" fell through entirely (ENTITY_TO_TYPE's own "changes"->"Change"
985
- // pseudo-type noun risked consuming the word first; see parseRelationalOrQualified's
986
- // guard against exactly that in ask.mjs).
987
957
  "changes with",
988
958
  "change with",
989
959
  "tends to change with",
@@ -1000,13 +970,9 @@
1000
970
  "re-export",
1001
971
  "passes through",
1002
972
  "pass through",
1003
- // API-surface phrasing (2026-07-02 query families): "what does <module>
1004
- // expose". Bare "exposed" is NOT listed — the lemma tier maps it here when
1005
- // an adapter is present, and "how does the API get exposed" has no
1006
- // traversal either way (pinned as an honest miss in the tests).
1007
973
  "exposes",
1008
974
  "expose",
1009
- // gerund (g-drop normalization — §3.5)
975
+ // gerund (g-drop normalization)
1010
976
  "exporting"
1011
977
  ]
1012
978
  }
@@ -1073,10 +1039,6 @@
1073
1039
  methods: "Method",
1074
1040
  class: "Class",
1075
1041
  classes: "Class",
1076
- // "mod"/"mods" (HANDOVER.md 2026-07-10 item 10): a rushed-dev abbreviation
1077
- // prefix ("mod store.mjs imports") used to land in disambiguation instead of
1078
- // resolving cleanly, since nothing recognized "mod" as this same Module noun
1079
- // — same alias-of-Module trade "file"/"files" already make just above.
1080
1042
  module: "Module",
1081
1043
  modules: "Module",
1082
1044
  mod: "Module",
@@ -1091,17 +1053,10 @@
1091
1053
  variables: "GlobalVariable",
1092
1054
  global: "GlobalVariable",
1093
1055
  globals: "GlobalVariable",
1094
- // "changes" in a touch question ("which changes touch commit <sha>") means the
1095
- // code entities on the other end of the touch edges, at WHATEVER grain the graph
1096
- // recorded module (touches) and symbol (touchesSymbol) together when the commit
1097
- // is the given side, and the touching commits themselves when a module/symbol is
1098
- // the given side. Mapped to the pseudo-type "Change" (not a node class): ask.mjs's
1099
- // traverse() reads it as a wildcard over the touch traversal's results rather than
1100
- // aliasing it to ONE real class and silently dropping the other grain of the
1101
- // answer. Listed BEFORE commit/commits: findPhrase (ask.mjs) takes the first
1102
- // same-length phrase in table order, so in "which changes touch commit <sha>" the
1103
- // entity slot must consume "changes" and leave "commit <sha>" intact as the
1104
- // object term.
1056
+ // "Change" is a pseudo-type, not a node class: ask.mjs's traverse() reads it
1057
+ // as a wildcard over touch-traversal results (module or symbol grain).
1058
+ // Listed before commit/commits since findPhrase takes the first
1059
+ // same-length match in table order.
1105
1060
  change: "Change",
1106
1061
  changes: "Change",
1107
1062
  commit: "Commit",
@@ -1208,13 +1163,7 @@
1208
1163
  "touchs": "touches",
1209
1164
  "tuoches": "touches",
1210
1165
  "touhced": "touched",
1211
- // "touchd" (BENCHMARK_CONVERSATION_1.7.0.md routed backlog C3): the
1212
- // dropped-vowel slip of "touched" — distinct from "touhced" above
1213
- // (transposed letters), same curated-typo discipline.
1214
1166
  "touchd": "touched",
1215
- // WHERE_MARKERS typo (0.9.13 Tier-1 playtest): "defined" itself had no typo
1216
- // entry, so "where is it defned" fell through to the bare-object search path
1217
- // instead of the where-shape ("no module matching 'it defned' found").
1218
1167
  "defned": "defined",
1219
1168
  "chagned": "changed",
1220
1169
  "chnaged": "changed",
@@ -1228,50 +1177,20 @@
1228
1177
  "whcih": "which",
1229
1178
  "wich": "which",
1230
1179
  "whihc": "which",
1231
- // "wehre"/"whre" (0.9.13 Tier-1 playtest, "where is it defined" drill-down):
1232
- // the WHERE-DEFINED shape's own anchor word had NO typo tolerance at all
1233
- // (unlike which/what/does/the above), so a plain dropped/transposed letter
1234
- // fell straight through resolveObject and hit either the grammar wall or a
1235
- // bogus "no module matching 'it defined'" search. "were" (the missing-h
1236
- // homophone slip) is NOT curated here — it's a real word already load-bearing
1237
- // as the TEMPORAL_AUX auxiliary ("when were the modules last touched"), so
1238
- // that one typo is handled by its own anchored phrasing frame instead
1239
- // (normalize.mjs PHRASING_FRAMES) to avoid clobbering the legitimate reading.
1180
+ // "were" (the missing-h homophone slip of "where") is NOT curated here —
1181
+ // it's a load-bearing TEMPORAL_AUX word ("when were the modules touched").
1240
1182
  "wehre": "where",
1241
1183
  "whre": "where",
1242
- // "wat" (chatbench cycle 2, tf-wat-calls): the internet-casual spelling of
1243
- // "what" — neither curated noise nor a restorable trigger typo, so "wat calls
1244
- // fnAlpha" used to die as "couldn't resolve one of the terms". Restored here
1245
- // so BOTH parse strategies and the relaxation cascade see the canonical
1246
- // anchor; the correction regex's dotted-extension guard keeps a module
1247
- // literally named "wat.mjs" untouched, same residual trade as every entry.
1248
1184
  "waht": "what",
1249
1185
  "wat": "what",
1250
- // "dat" (BENCHMARK_CONVERSATION_1.7.0.md routed backlog C3): the internet-
1251
- // casual spelling of "that" — same register as "wat"/"waht" just above,
1252
- // curated rather than left to the generic fuzzy tier since "that" is a
1253
- // load-bearing anchor word throughout this grammar (TEACH_RE's own
1254
- // "remember that X", relative-clause objects, etc).
1255
1186
  "dat": "that",
1256
1187
  "dose": "does",
1257
1188
  "doess": "does",
1258
1189
  "teh": "the",
1259
- // aggregate/list TRIGGER words (2026-07-02, trigger-typo work) — a typo of a count
1260
- // or list trigger used to be DROPPED as unmatched by the relaxation cascade, losing
1261
- // the aggregate/list INTENT entirely ("how manyn classes" → the count was lost);
1262
- // curated here so the intended trigger is restored BEFORE parsing (the general
1263
- // bounded fuzzy path in ask.mjs's cascade is the backstop for uncurated typos).
1264
1190
  "manyn": "many",
1265
1191
  "mnay": "many",
1266
1192
  "amny": "many",
1267
1193
  "mnany": "many",
1268
- // "hwo" (Tier 6 playtest, §3b typo axis): a transposed-letter typo of "how" —
1269
- // "hwo many classes are there" used to lose the aggregate/list trigger
1270
- // outright ("how many" only reads as a count trigger when both words are
1271
- // exact), same failure class as "manyn"/"mnay" just above, one word to the
1272
- // left of it. "how" is grammar-owned via AGGREGATE_TRIGGERS' own "how many"/
1273
- // "how much" entries (test/ask-vocab.test.mjs's canonical-value check
1274
- // splits those multi-word triggers into individual words).
1275
1194
  "hwo": "how",
1276
1195
  "coutn": "count",
1277
1196
  "conut": "count",
@@ -1338,14 +1257,6 @@
1338
1257
  "btw",
1339
1258
  "by the way",
1340
1259
  "you",
1341
- // "quick q" (BENCHMARK_CONVERSATION_1.8.14.md item 11): the casual abbreviated
1342
- // sibling of GREETING_PREAMBLE_RE's own "quick question" clause (normalize.mjs)
1343
- // — that frame requires a delimiter immediately after the greeting word
1344
- // ("hey, quick question - …"), so it never matches "hey quick q, …" (no
1345
- // delimiter between "hey" and "quick q"). Filler-stripping instead — this
1346
- // list is matched word-boundary-anywhere, not anchored — closes the gap
1347
- // without needing GREETING_PREAMBLE_RE's own stricter delimiter-position
1348
- // shape.
1349
1260
  "quick q"
1350
1261
  ]);
1351
1262
  var CONTEXT_PRONOUNS = Object.freeze(["this", "it", "that", "here", "this one", "that one"]);
@@ -1394,8 +1305,6 @@
1394
1305
  "node",
1395
1306
  "stuff",
1396
1307
  "code",
1397
- // "symbol(s)" is a grain-agnostic stand-in for any code entity — "exported
1398
- // symbols of X" means whatever X defines, at any grain, filtered by the qualifier.
1399
1308
  "symbol",
1400
1309
  "symbols"
1401
1310
  ]);
@@ -1423,33 +1332,22 @@
1423
1332
  uncovered: { via: "tested", value: false }
1424
1333
  });
1425
1334
  var AGGREGATE_TRIGGERS = Object.freeze([
1426
- // formal ("the number of classes" reaches "number of" once the cascade strips the
1427
- // leading article — keeping the trigger list clear of "the" so it never enters
1428
- // CONTENT_VOCAB and blocks the article's own noise-strip)
1429
1335
  "how many",
1430
1336
  "how much",
1431
1337
  "how many of",
1432
1338
  "number of",
1433
1339
  "total number of",
1434
1340
  "quantity of",
1435
- // "<measure> of <kind>" cardinality forms (widened net, cycle W2P): count = sum = total
1436
- // = tally = number of. The bare single words (sum/total/tally) stay CASCADE_SYNONYMS-
1437
- // mapped to "count" (identifier-fragment risk without the "of" anchor — see that table's
1438
- // note); the multi-word "of" forms are safe to promote to direct triggers because the
1439
- // trailing "of <kind>" pins them to a cardinality question, not a stray identifier.
1440
1341
  "tally of",
1441
1342
  "sum of",
1442
1343
  "total of",
1443
1344
  "amount of",
1444
- // neutral / imperative (bare "tally"/"sum"/"total" stay CASCADE_SYNONYMS-mapped so the
1445
- // "tally the classes" relaxation path — pinned by a cascade test — is preserved).
1446
1345
  "count",
1447
1346
  "count up",
1448
1347
  "count of",
1449
1348
  "tot up"
1450
1349
  ]);
1451
1350
  var LIST_TRIGGERS = Object.freeze([
1452
- // imperative — "<verb> [me/us] [the] <kind>"
1453
1351
  "list",
1454
1352
  "show",
1455
1353
  "show me",
@@ -1467,7 +1365,6 @@
1467
1365
  "run down",
1468
1366
  "run through",
1469
1367
  "ls",
1470
- // interrogative — "what/which are [the] <kind>"
1471
1368
  "what are",
1472
1369
  "which are"
1473
1370
  ]);
@@ -1501,12 +1398,7 @@
1501
1398
  connections: { kind: "*", dir: "both" },
1502
1399
  edges: { kind: "*", dir: "both" },
1503
1400
  connected: { kind: "*", dir: "both" },
1504
- // participle degree-nouns (widened net, cycle W2P): "the most imported / most
1505
- // depended-on / most used <module>" ranks by IN-degree — how many things import/depend
1506
- // on/use it — the ARGMAX-by-degree intent a developer expresses with a passive
1507
- // participle rather than the noun ("importers"). "depended" catches "depended-on" /
1508
- // "depended on" (both tokenize to a bare "depended"); "used" folds the symbol-grain
1509
- // callsSymbol callers in alongside importers so "most used" reads as most-relied-upon.
1401
+ // participle degree-nouns rank by in-degree, same intent as "importers" etc
1510
1402
  imported: { kind: "imports", dir: "in" },
1511
1403
  "depended-on": { kind: "imports", dir: "in" },
1512
1404
  depended: { kind: "imports", dir: "in" },
@@ -1520,27 +1412,12 @@
1520
1412
  var ANAPHORA_TRIGGERS = Object.freeze(["those", "them", "these"]);
1521
1413
  var MEMBERSHIP_KINDS = Object.freeze(["contains", "defines"]);
1522
1414
  var CASCADE_NOISE = Object.freeze([
1523
- // articles / vague determiners (kept OUT of content vocab so they're strippable;
1524
- // the aggregate/where parsers already tolerate a stray "the"/"a", so stripping is
1525
- // belt-and-braces, not load-bearing)
1526
1415
  "the",
1527
1416
  "a",
1528
1417
  "an",
1529
1418
  "some",
1530
- // "what OTHER classes inherit from Controller" — "other" is a vague determiner
1531
- // like "some", not a qualifying adjective; without this it was misread as a
1532
- // fuzzy find TERM ("no classes found matching 'other'") instead of falling
1533
- // through to the ordinary reverse-inherits parse (fast-loop round 6 finding).
1534
1419
  "other",
1535
- // topic lead-in filler — "what about the modules", "how about classes": "about"
1536
- // carries no graph meaning here, so stripping it lets the bare kind noun surface for
1537
- // the cascade's bare-kind-noun terminal rule (ask.mjs). ("what"/"how" are structural
1538
- // question words the drop-pass keeps; only the "about" between them and the kind is
1539
- // noise.) A module literally named "about" is safe-listed by relaxParse's resolvesExact
1540
- // guard, same as every other noise token.
1541
1420
  "about",
1542
- // politeness / hedges (single-token; multi-word "could you"/"please" etc. are
1543
- // FILLER_WORDS, stripped earlier during normalization)
1544
1421
  "please",
1545
1422
  "pls",
1546
1423
  "plz",
@@ -1553,8 +1430,6 @@
1553
1430
  "thank",
1554
1431
  "ta",
1555
1432
  "cheers",
1556
- // greetings a question sometimes opens with (chat.mjs owns standalone greetings;
1557
- // here they're only stripped when embedded in an otherwise-real question)
1558
1433
  "hi",
1559
1434
  "hello",
1560
1435
  "hey",
@@ -1563,7 +1438,6 @@
1563
1438
  "howdy",
1564
1439
  "ok",
1565
1440
  "okay",
1566
- // vocatives / terms of address (the "matey" of the worked example, and its kin)
1567
1441
  "matey",
1568
1442
  "mate",
1569
1443
  "buddy",
@@ -1580,18 +1454,7 @@
1580
1454
  "guys",
1581
1455
  "everyone",
1582
1456
  "dear",
1583
- // the product's OWN name used as an address (chatbench cycle 2, ns-hey-tmct:
1584
- // "hey tmct, what calls fnAlpha thanks") — a vocative like "matey", stripped
1585
- // by the same rules: relaxParse's resolvesExact guard still protects a module
1586
- // literally named "tmct", and noise-strip's template/keyword-spot acceptance
1587
- // bounds the cost of a mid-question strip to an honest object-miss.
1588
1457
  "tmct",
1589
- // presentation frames — the keyword-spotting strategy's blind spot: the
1590
- // compositional grammar skips these as FRAME_WORDS, but "show me what imports X"
1591
- // otherwise decomposes (via keyword-spot) to ask{subject:"show me"}. Stripping
1592
- // them on a miss recovers the underlying reverse/forward question. ("count" is NOT
1593
- // here — it is an aggregate trigger; "find"/"search" are here as presentation
1594
- // verbs, not the tmct_search tool, which ask.mjs never dispatches.)
1595
1458
  "show",
1596
1459
  "tell",
1597
1460
  "give",
@@ -1818,40 +1681,21 @@
1818
1681
  }
1819
1682
  var PHRASING_FRAMES = Object.freeze([
1820
1683
  // MEMBERS-of-class → "what does X contain".
1821
- // "what functions are in Task", "what methods are inside X", "what attributes are in X"
1822
1684
  { re: /^what\s+(?:functions?|methods?|members?|attributes?|fields?|properties)\s+(?:are|is)\s+(?:in|inside|within)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
1823
- // "what functions does Task have", "what methods does X have"
1824
1685
  { re: /^what\s+(?:functions?|methods?|members?|attributes?|fields?|properties)\s+(?:does|do)\s+(.+?)\s+have\??$/i, to: (m) => `what does ${m[1]} contain` },
1825
- // "what are the members of X", "what are the methods in X"
1826
1686
  { re: /^what\s+are\s+(?:the\s+)?(?:functions?|methods?|members?|attributes?|fields?|properties)\s+(?:of|in|inside|within)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
1827
- // "members of X", "methods of X", "contents of X"
1828
1687
  { re: /^(?:the\s+)?(?:members?|methods?|attributes?|contents)\s+of\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
1829
- // "what's in X" / "what is in X" (contraction already expanded; sha handled above)
1830
1688
  { re: /^what\s+is\s+(?:in|inside)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
1831
- // "what else is in X" (0.9.15 Tier-1 single-touch playtest) the natural
1832
- // "besides what I already know" drill-down after a members-of-class answer.
1833
- // Distinct from the "what else does X <verb>" family (which the compositional
1834
- // grammar already tolerates, dropping "else" as noise on its own): the "is
1835
- // in" idiom is NOT a compositional marker, so parseComposite never sees it and
1836
- // "what else is in X" fell through to the strategies with NO candidate at all
1837
- // (neither recognizes the bare "is in" idiom once "else" sits in front of it).
1838
- // The only rescue was the relaxation cascade's drop-unmatched layer — but that
1839
- // layer refuses to accept a relaxed reading that still renders an honest EMPTY
1840
- // , so a genuinely empty class ("what else is in
1841
- // Task.complete" — a method, no members) bottomed out at the bare grammar
1842
- // wall instead of the specific "no contains edges" receipt. Routing this
1843
- // frame onto the SAME direct "what does X contain" path the plain "what is
1844
- // in X" frame above already uses sidesteps the cascade's conservative gate
1845
- // entirely, so a real empty is reported honestly instead of walled.
1689
+ // "what else is in X" drill-down after a members-of-class answer.
1846
1690
  { re: /^what\s+else\s+is\s+(?:in|inside)\s+(?:the\s+)?(.+?)\??$/i, to: (m) => `what does ${m[1]} contain` },
1847
1691
  // WHERE-DEFINED → "where is X defined". PAST TENSE ONLY ("what defined X", "what
1848
1692
  // declared X"): the PRESENT "what defines X" already parses as a reverse-defines
1849
- // query (the module defining symbol X test/ask.test.mjs pins that), so rewriting
1850
- // it would change that receipt. The past-tense form is the one that hit the wall.
1693
+ // query (the module defining symbol X), so rewriting it would change that
1694
+ // receipt. The past-tense form is the one that hit the wall.
1851
1695
  { re: /^what\s+(?:defined|declared)\s+(?:the\s+)?(?:function\s+|method\s+|class\s+|module\s+|variable\s+|constant\s+)?(.+?)\??$/i, to: (m) => `where is ${m[1]} defined` },
1852
1696
  // "where's X defined" (the "where's" contraction is not in the contraction table)
1853
1697
  { re: /^where'?s\s+(?:the\s+)?(.+?)\s+(defined|declared|located|implemented)\??$/i, to: (m) => `where is ${m[1]} ${m[2]}` },
1854
- // "were is X defined" (0.9.13 Tier-1 playtest: the missing-h typo of "where").
1698
+ // "were is X defined" (the missing-h typo of "where").
1855
1699
  // NOT curated as a blanket MISSPELLINGS entry — "were" is a real word already
1856
1700
  // load-bearing as the TEMPORAL_AUX auxiliary ("when were the modules last
1857
1701
  // touched"), so a global word-boundary rewrite would clobber that reading.
@@ -1860,33 +1704,15 @@
1860
1704
  // temporal query produces ("were" as an auxiliary never leads directly into
1861
1705
  // a bare "is").
1862
1706
  { re: /^were\s+is\s+(?:the\s+)?(.+?)\s+(defined|declared|located|implemented)\??$/i, to: (m) => `where is ${m[1]} ${m[2]}` },
1863
- // PREDICATIVE QUALIFIER → the ATTRIBUTIVE form the grammar already answers. The
1864
- // adjective-qualifier post-filters (ask-vocab.mjs QUALIFIERS: tested/untested,
1865
- // public/private, exported, static/abstract/constant, …) parse in the ATTRIBUTIVE
1866
- // slot — "untested modules", "public methods" — but a developer just as naturally
1867
- // asks the PREDICATIVE "which modules are untested" / "what functions are tested",
1868
- // which hit the grammar wall (and, worse, the wall's own hint SUGGESTED "which
1869
- // functions are tested" — a shape it could not then answer). Rewriting the
1870
- // predicative "<which|what> <kind> are <QUALIFIER>" to "<QUALIFIER> <kind>" routes
1871
- // it onto the working attributive filter. Closed to the known qualifier adjectives
1872
- // (not a general "… are X" catch), and the QUALIFIER must sit immediately after
1873
- // are/is, so "which modules are NOT tested" never matches here — that keeps its own
1874
- // set-complement handler (matchNegationSet, downstream in ask.mjs's parseNegation).
1707
+ // PREDICATIVE QUALIFIER ("which modules are untested") → the ATTRIBUTIVE form
1708
+ // ("untested modules") the grammar already answers. The QUALIFIER must sit
1709
+ // immediately after are/is, so "are NOT tested" keeps its own set-complement handler.
1875
1710
  {
1876
1711
  re: /^(?:which|what)\s+(?:the\s+|all\s+)?([a-z][a-z-]*?)\s+(?:are|is)\s+(public|private|protected|static|abstract|constant|exported|re-?exported|tested|covered|untested|uncovered)\??$/i,
1877
1712
  to: (m) => `${m[2].toLowerCase()} ${m[1].toLowerCase()}`
1878
1713
  },
1879
- // BARE COVERAGE SURVEY (no entity kind) → the attributive "<qualifier> modules"
1880
- // the grammar already answers. Once "what is a test" opens the topic, a developer
1881
- // asks the survey the plainest way — "what is untested", "what's not tested",
1882
- // "what isn't covered", "what is covered" — with NO entity noun at all, so the
1883
- // predicative-qualifier frame above (which needs a KIND between what/which and
1884
- // are/is) can't catch it, and it fell through to a soft wall ("no module matching
1885
- // 'not'…" / the "I answer questions…" orientation). Default the surveyed kind to
1886
- // modules (the same set "which modules are not tested" / "untested modules" return)
1887
- // and fold the negation into the qualifier (not tested → untested, not covered →
1888
- // uncovered). Anchored with no object, so "what tests cover X" / "what is a test"
1889
- // never match here.
1714
+ // BARE COVERAGE SURVEY, no entity kind ("what is untested") → defaults the
1715
+ // surveyed kind to modules and folds the negation into the qualifier.
1890
1716
  {
1891
1717
  re: /^what\s+(?:is|are)\s+(not\s+)?(tested|untested|covered|uncovered)\??$/i,
1892
1718
  to: (m) => {
@@ -1895,61 +1721,23 @@
1895
1721
  return `${flipped} modules`;
1896
1722
  }
1897
1723
  },
1898
- // CO-CHANGE → the "co-changes with" canonical the RELATIONS table answers. The
1899
- // cochange verb synonyms (ask-vocab.mjs) include "co-changes with" / "moves
1900
- // together with" / "tends to change together with", but NOT the plainest form a
1901
- // developer types — the one the README itself prints and the relation renders as:
1902
- // "what does X change together with" / "what changes together with X". Both hit a
1903
- // dead-end ("couldn't resolve one of the terms" / the grammar wall); rewriting them
1904
- // onto "what co-changes with X" routes them to the working change-coupling query.
1724
+ // CO-CHANGE → "what co-changes with X" (the plainest phrasing a developer types).
1905
1725
  { re: /^what\s+does\s+(.+?)\s+changes?\s+together\s+with\??$/i, to: (m) => `what co-changes with ${m[1]}` },
1906
1726
  { re: /^what\s+changes?\s+together\s+with\s+(.+?)\??$/i, to: (m) => `what co-changes with ${m[1]}` },
1907
- // AUTHORSHIP → the "who touched X" churn query. "who touched X" now names the
1908
- // commit author beside the sha (the 0.8.1 commit-ref quick-win), which invites the
1909
- // synonyms a developer reaches for next — "who wrote X", "who authored X", "who is
1910
- // the author of X" — and every one of them hit the grammar wall. tmct has no
1911
- // separate authorship edge; "touched" IS the authorship signal (the churn commits
1912
- // carry the author), so these are true synonyms of "who touched X", not a new
1913
- // capability. Anaphora rides through untouched ("who wrote it" → "who touched it").
1914
- // SHA GUARD (0.8.2 feel wave): a COMMIT object is NOT a synonym — "who is the
1915
- // author of abc1234" rewritten to "who touched abc1234" dumps the commit's
1916
- // touch-SET instead of naming its author. The negative lookahead refuses the
1917
- // rewrite when the object is a bare (optionally "commit "-prefixed) 7-40 char
1918
- // hex sha, leaving the un-rewritten form for the author lane to consume;
1919
- // file/symbol objects (anything non-sha, e.g. "deadbeef.mjs") keep the rewrite.
1727
+ // AUTHORSHIP → "who touched X" (tmct's touch edge IS the authorship signal).
1728
+ // A commit sha object is excluded — that dumps the commit's touch-set, not its author.
1920
1729
  { re: /^who\s+(?:wrote|authored)\s+(?:the\s+)?(?!(?:commit\s+)?[0-9a-f]{7,40}\??$)(.+?)\??$/i, to: (m) => `who touched ${m[1]}` },
1921
1730
  { re: /^who\s+is\s+the\s+authors?\s+of\s+(?:the\s+)?(?!(?:commit\s+)?[0-9a-f]{7,40}\??$)(.+?)\??$/i, to: (m) => `who touched ${m[1]}` },
1922
- // HAS-TESTS → the coverage question the RELATIONS table answers. "does X have
1923
- // tests" parses "have" as a defines-verb (VERB_TO_KIND), producing the garbled
1924
- // "No — no defines edge found from X to <whatever resolves>" receipt; "is X
1925
- // tested" traverses tests edges from the WRONG side (subject = X). Both mean
1926
- // the coverage question "what tests X" — rewrite onto it. Closed to a
1927
- // tests/coverage object ("does X have methods/members" stays the members
1928
- // family) and refuses any "not" in the subject span, so the set-complement
1929
- // negations ("is X not tested") keep their own handler downstream.
1731
+ // HAS-TESTS → "what tests X" (the coverage question). Refuses "not" in the
1732
+ // subject so set-complement negations keep their own downstream handler.
1930
1733
  { re: /^(?:does|do)\s+(?!.*\bnot\b)(.+?)\s+have\s+(?:any\s+)?(?:tests?|test\s+coverage|coverage)\??$/i, to: (m) => `what tests ${m[1]}` },
1931
1734
  { re: /^(?:is|are)\s+(?!.*\bnot\b)(.+?)\s+tested\??$/i, to: (m) => `what tests ${m[1]}` },
1932
- // NEEDS-TESTS → the untested-module survey. "what needs tests" / "what needs
1933
- // testing" is the plainest way to ask which modules are uncovered, and it hit the
1934
- // grammar wall ("no module matching 'needs'…"). Route it onto the same attributive
1935
- // survey the bare "what is untested" frame lands on. Closed to the tests/coverage
1936
- // object, so it can't swallow a general "what needs X".
1735
+ // NEEDS-TESTS → the untested-module survey.
1937
1736
  { re: /^what\s+needs\s+(?:to\s+be\s+)?(?:a\s+)?(?:tested|tests?|testing|coverage|covering)\??$/i, to: () => "untested modules" },
1938
- // DOES-X-VERB-ANYTHING-ELSE → the plain forward "what does X <verb>" listing
1939
- // (0.9.15 Tier-1 single-touch playtest). A very natural drill-down follow-up
1940
- // after a relation answer "does listTasks call anything else", "does
1941
- // src/handlers/tasks.mjs import something else" used to dead-end: "anything"/
1942
- // "something" [else] is a placeholder standing in for "the rest of the list",
1943
- // not a real object term, but the two parse strategies disagreed on the SPAN
1944
- // (grammar kept "anything else" whole as the object, keyword-spot dropped
1945
- // "anything" and kept only "else"), landing on the {ambiguousParse} surface —
1946
- // two nonsense readings offered as if one might be right. "what does X <verb>"
1947
- // is the exact working canonical shape (see the MEMBERS-of-class frames above),
1948
- // so rewriting the whole closed pattern onto it sidesteps the disagreement
1949
- // instead of teaching either strategy's tokenizer to special-case "else".
1950
- // Anchored to the closed VERB_TO_KIND vocabulary so it can never swallow a
1951
- // genuine named object that happens to start with "any"/"some" (only the bare
1952
- // placeholder nouns "anything"/"something", optionally trailed by "else", match).
1737
+ // DOES-X-VERB-ANYTHING-ELSE → "what does X <verb>" (drops the placeholder
1738
+ // "anything/something else" object, which otherwise made the two parse
1739
+ // strategies disagree on the span). Anchored to VERB_TO_KIND so it can't
1740
+ // swallow a real object that happens to start with "any"/"some".
1953
1741
  {
1954
1742
  re: new RegExp(`^(?:do|does)\\s+(.+?)\\s+(${VERB_ALTERNATION})\\s+(?:anything|something)(?:\\s+else)?\\??$`, "i"),
1955
1743
  to: (m) => `what does ${m[1]} ${m[2]}`
@@ -1964,7 +1752,6 @@
1964
1752
  }
1965
1753
  var NEGATION_SET_RE = new RegExp(
1966
1754
  "^(?:which|what|who|list|show(?:\\s+me)?|find|give\\s+me)?\\s*(?:the\\s+|all\\s+)?([a-z][a-z-]*)\\s+(?:(?:that|which|who)\\s+)?(?:(?:do|does|did|are|is|was|were|have|has)\\s+)?not\\s+(.+)$",
1967
- // the negation marker + (2) the predicate
1968
1755
  "i"
1969
1756
  );
1970
1757
  function matchNegationSet(text) {
@@ -2005,15 +1792,8 @@
2005
1792
  "nothing",
2006
1793
  "one",
2007
1794
  "any",
2008
- // temporal filler in when-questions ("when was X last touched") — a symbol
2009
- // literally named "last" would be the accepted residual cost, same trade as
2010
- // every other stopword.
2011
1795
  "last",
2012
- // frequency-adverb filler ("what does X usually change together with", "what does
2013
- // X typically call") — found live: "usually" glued onto the object term instead of
2014
- // being stripped, corrupting resolution ("src/core/store.mjs usually" instead of
2015
- // the module alone). Same trade as every other stopword: a symbol literally named
2016
- // "usually" would be the accepted residual cost.
1796
+ // temporal filler ("when was X last touched")
2017
1797
  "usually",
2018
1798
  "typically",
2019
1799
  "generally",
@@ -2021,12 +1801,7 @@
2021
1801
  "often",
2022
1802
  "commonly",
2023
1803
  "mostly",
2024
- // modal auxiliaries ("what SHOULD i look at first") — found live: with no modal in
2025
- // this set, "should" reached the cascade's bounded fuzzy-correction step and landed
2026
- // within edit distance of the unrelated closed-vocab word "hold" ("defines" synonym,
2027
- // ask-vocab.mjs), corrupting the whole query into "what hold i at". Same trade as
2028
- // every other stopword: a symbol literally named "should" would be the accepted
2029
- // residual cost.
1804
+ // frequency-adverb filler
2030
1805
  "should",
2031
1806
  "would",
2032
1807
  "could",
@@ -2035,6 +1810,7 @@
2035
1810
  "shall",
2036
1811
  "might",
2037
1812
  "must"
1813
+ // modal auxiliaries
2038
1814
  ]);
2039
1815
  var splitWords = (text) => String(text).replace(/\?+\s*$/, "").replace(/,/g, " ").split(/\s+/).filter(Boolean);
2040
1816
  var wordsOf = (arr) => arr.flatMap((p) => String(p).toLowerCase().split(" "));
@@ -2095,21 +1871,9 @@
2095
1871
  var MODIFIER_ALT = Object.keys(MODIFIER_TO_KIND).sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
2096
1872
  var META_ALT = META_MEANING_VERBS.slice().sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
2097
1873
  var TEMPLATES = [
2098
- // T1 ASK: "does X import Y" / "is X a subclass of Y" -> Yes/No. Tried FIRST: it starts with
2099
- // does/is/do/did, which the reverse/forward templates below never match (those start with
2100
- // which/what), so precedence between T1 and the rest is structural, not a tie-break guess.
2101
- // "did" joins does/do for the past-tense commit forms ("did commit <sha> touch X").
2102
- // REVERSE VERB SWAP (Seonix Batch 2 Fix 2): this template fixes subject/object by regex
2103
- // capture POSITION, not by the verb's semantic direction — fine for every forward verb
2104
- // ("subclass of", "imports", …), but "is X a superclass of Y" MEANS the reverse of "is X
2105
- // a subclass of Y" (Y inherits from X, not X from Y). INHERITS_REVERSE_VERBS (ask-vocab.mjs)
2106
- // is the closed set of such reverse phrasings; when the matched verb is one of them,
2107
- // subject/object are swapped here, once, at parse time — so downstream evaluation (ask.mjs)
2108
- // sees "is Y a subclass of X" and needs zero changes of its own. (In practice this exact
2109
- // regex only ever matches a does/do/did lead, so "is …" phrasings actually reach the "ask"
2110
- // shape via keywords.mjs's decomposition strategy instead — that strategy applies the same
2111
- // swap for the same reason; this branch is kept for any does/do/did-led phrasing that names
2112
- // a reverse verb, and for structural symmetry with that sibling strategy.)
1874
+ // T1 ASK: "does X import Y" -> Yes/No. REVERSE VERB SWAP: a semantically-reverse
1875
+ // verb ("superclass of") means the opposite of its forward counterpart, so
1876
+ // INHERITS_REVERSE_VERBS (ask-vocab.mjs) swaps subject/object here at parse time.
2113
1877
  {
2114
1878
  name: "ask",
2115
1879
  re: new RegExp(`^(?:does|do|did)\\s+(.+?)\\s+(${VERB_ALT})\\s+(.+?)\\??$`, "i"),
@@ -2122,7 +1886,7 @@
2122
1886
  return { shape: "ask", entityType: null, modifier: "direct", kind, subject, object };
2123
1887
  }
2124
1888
  },
2125
- // T2 reverse: "which <entity> [<modifier>] <verb> <object>" — the operator's own example shape.
1889
+ // T2 reverse: "which <entity> [<modifier>] <verb> <object>".
2126
1890
  {
2127
1891
  name: "reverse",
2128
1892
  re: new RegExp(`^which\\s+(${ENTITY_ALT})\\s+(?:(${MODIFIER_ALT})\\s+)?(${VERB_ALT})\\s+(.+?)\\??$`, "i"),
@@ -2147,40 +1911,17 @@
2147
1911
  object: m[1].trim()
2148
1912
  })
2149
1913
  },
2150
- // T4 meta: "what does <term> mean" — a question about the GRAPH'S OWN VOCABULARY
2151
- // (a SchemaClass/SchemaPredicate label, e.g. "cochange", or a raw prop token, e.g.
2152
- // "mgx:callsSymbol"), not a graph traversal over code edges. Tried after T3: T3 also
2153
- // starts "what does/do", but T3 only fires when the tail is a relation VERB_ALT
2154
- // phrase ("import"/"calls"/…), which "mean"/"means"/etc never are (disjoint tables —
2155
- // ask-vocab.mjs's file comment explains why they're kept separate), so the two never
2156
- // actually compete for the same input.
1914
+ // T4 meta: "what does <term> mean" — a question about the graph's own vocabulary,
1915
+ // not a graph traversal. VERB_ALT and META_ALT are disjoint tables, so this never
1916
+ // competes with T3 for the same input.
2157
1917
  {
2158
1918
  name: "meta-mean",
2159
1919
  re: new RegExp(`^what\\s+(?:does|do|is|are)\\s+(.+?)\\s+(?:${META_ALT})\\??$`, "i"),
2160
1920
  build: (m) => ({ shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: m[1].trim() })
2161
1921
  },
2162
- // T5 meta: "what is a/an <term>" — the OTHER worked phrasing ("what is a Commit").
2163
- // Seonix Batch 2 Fix 1: the indefinite article is now OPTIONAL, but the BARE
2164
- // (no-article) form is restricted to the CLOSED vocabulary ENTITY_TO_TYPE already
2165
- // imported above (function/method/class/module/attribute/variable/change/commit,
2166
- // singular and plural) — build() returns null (same "this template didn't actually
2167
- // match, keep scanning" contract T8/"when" below already relies on — see
2168
- // parseAnchored's own docblock) when the bare form's object isn't one of those
2169
- // closed terms, so the scan falls through exactly as if this template had not
2170
- // matched at all. This keeps "what is a doohickey"/"widget"/"gizmo" (still routed
2171
- // here via the WITH-article, fully unrestricted `(.+?)` path — pinned to resolve as
2172
- // an honest meta miss downstream, ask-combo.test.mjs/chat-readback.test.mjs) working
2173
- // unmodified, while ALSO keeping the two pinned bare-form honest misses intact:
2174
- // "what is the meaning of this codebase" (ask.test.mjs/ask-dual-strategy.test.mjs)
2175
- // and "what is exposed" (ask.test.mjs:840) both have bare objects absent from
2176
- // ENTITY_TO_TYPE, so build() still rejects them and they still fall through to null.
2177
- // Fix 3: stripTrailingScopeFiller (ask-vocab.mjs) trims a curated trailing clause
2178
- // ("what is a Module in this graph" -> "Module") off the object before it's
2179
- // returned, so a scoping tail never corrupts the lookup term either the bare-form
2180
- // check above or downstream resolution/rendering perform. HANDOVER.md 2026-07-10
2181
- // item 8: stripTrailingDiscourseTag trims a bare trailing "then"/"though" the
2182
- // same way ("what is a component then" -> "component") — applied first, since a
2183
- // discourse tag sits outermost when both happen to stack.
1922
+ // T5 meta: "what is a/an <term>" — the bare (no-article) form is restricted to
1923
+ // the closed ENTITY_TO_TYPE vocabulary (build() -> null otherwise, falling
1924
+ // through); the WITH-article form is unrestricted.
2184
1925
  {
2185
1926
  name: "meta-whatis",
2186
1927
  re: new RegExp(`^what\\s+(?:is|are)\\s+(?:(an?)\\s+)?(.+?)\\??$`, "i"),
@@ -2194,8 +1935,8 @@
2194
1935
  return { shape: "meta", entityType: null, modifier: "direct", kind: "meta", object: stripTrailingScopeFiller(object) };
2195
1936
  }
2196
1937
  },
2197
- // T6 mention: "where is <term> mentioned/referenced" — the prose/mentions surface
2198
- // (2026-07-02 query families). Tried BEFORE T7: T7's trailing marker is optional,
1938
+ // T6 mention: "where is <term> mentioned/referenced" — the prose/mentions surface.
1939
+ // Tried BEFORE T7: T7's trailing marker is optional,
2199
1940
  // so without this ordering it would swallow the mention question and lose the
2200
1941
  // marker that distinguishes "locate the definition" from "list the prose mentions".
2201
1942
  {
@@ -2221,19 +1962,19 @@
2221
1962
  re: new RegExp(`^when\\s+(?:did|does|do|was|were|is)\\s+(.+?)\\s+(?:last\\s+)?(${VERB_ALT})\\??$`, "i"),
2222
1963
  build: (m) => VERB_TO_KIND[m[2].toLowerCase()] === "touches" ? { shape: "when", entityType: null, modifier: "direct", kind: "touches", object: m[1].trim() } : null
2223
1964
  },
2224
- // T9 commit-history NP (PLAN_CHAT_FEEL item 6 remainder): "the commit history of
2225
- // X" / "commit history for X" — an NP form of T8's SAME "when did X change"
2226
- // intent; reuses shape="when" verbatim so evaluation/rendering are byte-
2227
- // identical, only the recognizer surface differs.
1965
+ // T9 commit-history NP: "the commit history of X" / "commit history for X" —
1966
+ // an NP form of T8's SAME "when did X change" intent; reuses shape="when"
1967
+ // verbatim so evaluation/rendering are byte-identical, only the recognizer
1968
+ // surface differs.
2228
1969
  {
2229
1970
  name: "commit-history",
2230
1971
  re: /^(?:the\s+)?commit\s+history\s+(?:of|for)\s+(.+?)\??$/i,
2231
1972
  build: (m) => ({ shape: "when", entityType: null, modifier: "direct", kind: "touches", object: m[1].trim() })
2232
1973
  },
2233
- // T10 cochange-partners NP (PLAN_CHAT_FEEL item 6 remainder): "cochange partners
2234
- // of X" — an NP form of the existing "which modules cochange with X" verb-phrase
2235
- // shape (ask-vocab.mjs's cochange verb table); reuses shape="reverse"/
2236
- // kind="cochange" so evaluation is byte-identical.
1974
+ // T10 cochange-partners NP: "cochange partners of X" — an NP form of the
1975
+ // existing "which modules cochange with X" verb-phrase shape (ask-vocab.mjs's
1976
+ // cochange verb table); reuses shape="reverse"/kind="cochange" so evaluation
1977
+ // is byte-identical.
2237
1978
  {
2238
1979
  name: "cochange-partners",
2239
1980
  re: /^co-?change\s+partners\s+(?:of|for|with)\s+(.+?)\??$/i,
@@ -2608,6 +2349,7 @@
2608
2349
  var createRequireFromPath5 = unavailable5("createRequireFromPath");
2609
2350
  var spawnSync5 = unavailable5("spawnSync");
2610
2351
  var createInterface5 = unavailable5("createInterface");
2352
+ var createServer5 = unavailable5("createServer");
2611
2353
  var DatabaseSync5 = unavailable5("DatabaseSync");
2612
2354
 
2613
2355
  // src/answer-variants.mjs
@@ -2669,13 +2411,7 @@
2669
2411
  Attribute: ["attribute", "attributes"],
2670
2412
  GlobalVariable: ["variable", "variables"],
2671
2413
  Commit: ["commit", "commits"],
2672
- // "Change" is ask-vocab.mjs's pseudo-type (a wildcard over the touch traversal's
2673
- // results, never a node class) — it still needs noun forms for zero-hit templates.
2674
2414
  Change: ["change", "changes"],
2675
- // Memory-graph classes (memory/core.mjs) — real noun forms for the dynamic
2676
- // class count/list fallback (PLAN_BREADTH_FIRST_NLU.md (d), see
2677
- // dynamicClassQuery below) so "2 facts." reads naturally instead of falling
2678
- // back to the generic "2 results.".
2679
2415
  Fact: ["fact", "facts"],
2680
2416
  Utterance: ["utterance", "utterances"],
2681
2417
  Session: ["session", "sessions"],
@@ -3675,24 +3411,20 @@
3675
3411
  return traverse(graph, ast.clause, opts).matches || [];
3676
3412
  case "allOfClass":
3677
3413
  return graph.individuals.filter((i) => i.class === ast.entityType);
3678
- // predicate-find (Workstream 2), embedded as a set atom (§6 generalization
3679
- // a find-seed inside a boolean/qualifier fold): the narrow-then-broaden
3680
- // cascade's result, transparently flattened (the "related, not exact" framing
3681
- // is a top-level RENDER concern — evalComposite's dedicated "find" handling
3682
- // below, not this generic embedding).
3414
+ // Predicate-find as a set atom: the narrow-then-broaden cascade's result,
3415
+ // transparently flattened ("related, not exact" is a render concern).
3683
3416
  case "find": {
3684
3417
  const { narrow, broad } = computeFind(graph, ast.entityType, ast.term);
3685
3418
  return narrow.length ? narrow : broad;
3686
3419
  }
3687
- // the SUBJECTS that have ANY edge of a kind (the existential "modules that import
3688
- // anything") the positive set an existential negation ("do not import anything")
3689
- // differences off allOfClass to yield "modules that import nothing".
3420
+ // Subjects with any edge of a kind; an existential negation differences
3421
+ // this off allOfClass to yield "modules that import nothing".
3690
3422
  case "existsEdge": {
3691
3423
  const subs = new Set(kindsFor(ast.kind).flatMap((k) => edgesOfKind2(graph, k)).map((e) => e.subject));
3692
3424
  return graph.individuals.filter((i) => subs.has(i.id) && (!ast.entityType || i.class === ast.entityType));
3693
3425
  }
3694
- // forward complement: the verb's object-grain universe MINUS what the (late-resolved,
3695
- // focus-bindable) subject reaches via that verb "what doesn't it import".
3426
+ // Forward complement: the verb's object-grain universe minus what the
3427
+ // subject reaches via that verb ("what doesn't it import").
3696
3428
  case "forwardComplement": {
3697
3429
  const r = resolveTermOrContext(graph, ast.subjectTerm, opts && opts.contextId);
3698
3430
  if (!r.match) return [];
@@ -3709,11 +3441,9 @@
3709
3441
  const ids = new Set(evalSet(graph, ast.inner, opts).map((i) => i.id));
3710
3442
  return forwardOverSet(graph, ast.kind, ids);
3711
3443
  }
3712
- // the previous list-shaped answer's own id set (parsePluralAnaphoraObject's
3713
- // "those"/"them" leaf)evalComposite's reverseSet/forwardSet dispatch already
3714
- // intercepts the genuinely-empty (no `prev` at all) case as an honest "needs a
3715
- // previous answer" miss, same as evalAnaphora's own no-prev branch; this is only
3716
- // reached with a real, non-empty `prev` in hand.
3444
+ // The previous list-shaped answer's own id set. Only reached with a real,
3445
+ // non-empty `prev`the no-`prev` case is intercepted earlier as an
3446
+ // honest "needs a previous answer" miss.
3717
3447
  case "prevSet": {
3718
3448
  const prev = opts && opts.prev;
3719
3449
  return Array.isArray(prev) ? prev.map((id) => graph.byId.get(id)).filter(Boolean) : [];
@@ -5293,6 +5023,7 @@ ${result.branches.map((b, i) => `${i + 1}) ${b.candidate.label}: ${b.rendered.co
5293
5023
  const base = { node: "allOfClass", entityType };
5294
5024
  return listM ? { node: "list", entityType, base, scoped: false } : { node: "count", entityType, base };
5295
5025
  }
5026
+ var BARE_META_WHATIS_RE = /^what\s+(?:is|are)\s+(?:an?\s+)?(.+?)[?.!\s]*$/i;
5296
5027
  function ask(graph, query, { contextId = null, nlp = void 0, prev = null } = {}) {
5297
5028
  if (isHelpRequest(query)) {
5298
5029
  return {
@@ -5338,6 +5069,17 @@ ${result.branches.map((b, i) => `${i + 1}) ${b.candidate.label}: ${b.rendered.co
5338
5069
  }
5339
5070
  }
5340
5071
  }
5072
+ if (parsed === null && rendered.miss && !rendered.ambiguous) {
5073
+ const bareM = String(query || "").trim().match(BARE_META_WHATIS_RE);
5074
+ const bareTerm = bareM?.[1]?.trim();
5075
+ if (bareTerm && !/\s+(?:for|about)$/i.test(bareTerm)) {
5076
+ const bareParsed = parseQuery(`what is a ${bareTerm}`, { nlp });
5077
+ if (bareParsed?.shape === "meta") {
5078
+ result = traverse(graph, bareParsed, { contextId, prev });
5079
+ rendered = render(bareParsed, result);
5080
+ }
5081
+ }
5082
+ }
5341
5083
  let content = relaxed && !rendered.miss && relaxed.to !== relaxed.from ? `read as "${relaxed.to}" \u2014 ${rendered.content}` : rendered.content;
5342
5084
  if (!relaxed && !rendered.miss && !rendered.ambiguous && directFull.alternates.length) {
5343
5085
  const answered = directFull.alternates.map((a) => {
@@ -5358,13 +5100,6 @@ ${lines.join("\n")}`;
5358
5100
  tmct_ask: {
5359
5101
  mechanical: true,
5360
5102
  parsed: parsed && !parsed.ambiguousParse ? parsed : null,
5361
- // PLAN_BREADTH_FIRST_NLU.md §Track 6 (operator directive): the canonical
5362
- // restatement of what the request was understood to mean, ALWAYS present
5363
- // when anything parsed at all — not gated on ambiguity/miss the way the
5364
- // ambiguity-branch labels are. `english` is the human-readable gloss in
5365
- // tmct's own phrasing; `machine` is the same fact in a compact,
5366
- // machine-parsable notation (a plain `shape(kind, args...)` call form).
5367
- // Both are read straight off `parsed` — never generated.
5368
5103
  canonical: canonicalOf(parsed),
5369
5104
  matches: (result.matches || []).map((m) => ({
5370
5105
  id: m.id,
@@ -5375,16 +5110,12 @@ ${lines.join("\n")}`;
5375
5110
  traversal: result.traversal || null,
5376
5111
  miss: !!rendered.miss,
5377
5112
  ambiguous: !!rendered.ambiguous,
5378
- // The relaxation trace: null when the direct parse was used as-is (a clean hit or
5379
- // an honest miss the cascade couldn't/shouldn't rescue), else what the cascade
5380
- // dropped/normalised to reach an answer. A caller can assert relaxed===null to
5381
- // prove the cascade never touched a direct hit.
5113
+ // null when the direct parse was used as-is; a caller can assert
5114
+ // relaxed===null to prove the cascade never touched a direct hit.
5382
5115
  relaxed,
5383
- // Confidence provenance: "prose" when resolveObject fell through to the tier-4
5384
- // prose-index fallback (PLAN_PROSE_INDEX.md §6 matched what the symbol talks
5385
- // about, not its name); "fuzzy" when the tier-5 bounded-edit-distance pass
5386
- // resolved a typo'd term (the rendered content also announces it: "assuming you
5387
- // meant <label>"); null for every literal-identifier tier.
5116
+ // "prose" (tier-4 prose-index fallback) or "fuzzy" (tier-5 bounded
5117
+ // edit-distance, announced in the content as "assuming you meant …");
5118
+ // null for every literal-identifier tier.
5388
5119
  matchedVia: result.matchedVia || null,
5389
5120
  ...rendered.ambiguous ? { candidates: rendered.candidates } : {}
5390
5121
  }