@polycode-projects/the-mechanical-code-talker 1.8.12 → 1.8.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,6 +79,12 @@ the connecting sentence itself, citing both sources. The `source: ace:chat:…`
79
79
  part is a real provenance receipt. Every fact tmct stores records where it
80
80
  came from and when (more on that below).
81
81
 
82
+ The first `tell()` call above replies too: `noted — remembered 1 fact:
83
+ controller rdfs:subClassOf handler (controller is a type of handler)`. The
84
+ part in parentheses is a paraphrase. tmct generates it and checks it against
85
+ its own inference rules before showing it, so it never just guesses at
86
+ prose.
87
+
82
88
  ```
83
89
  $ tmct
84
90
  tmct> what talks to the payment module?
@@ -157,11 +163,7 @@ about tmct as of this writing.
157
163
 
158
164
  Every message runs through **multiple concurrent interpretation strategies**:
159
165
  a grammar parse, keyword picking, noise-word removal, fuzzy matching. Their
160
- results are grouped by class:
161
-
162
- - results of the **same class merge** into one ranked answer;
163
- - results of **distinct classes** are surrounded with an explicit
164
- *"if you mean X then …"* so ambiguity is shown, never silently resolved.
166
+ results are grouped by class.
165
167
 
166
168
  One of the strategies is an **ACE-inspired controlled grammar**: when your
167
169
  text fits the controlled fragment, tmct emits OWL-labelled triples from it.
@@ -192,6 +194,23 @@ exactly. It checks the type itself and its subclasses first. Only if nothing
192
194
  matches there does it widen to a related type, and when it does, it says so
193
195
  plainly rather than presenting the looser match as exact.
194
196
 
197
+ **Comparing two things.** "compare TaskController and UserController" or "how
198
+ is TaskController different from UserController" lines up both entities'
199
+ shared and differing edges side by side: *"Comparing TaskController and
200
+ UserController (both Class): inherits [seon:hasSuperType]: TaskController (1)
201
+ -> Controller; UserController (1) -> Controller"*. Every row is a real edge
202
+ or attribute from the graph, never a hand-written diff.
203
+
204
+ **Up-refining to a containing module.** A class rarely has its own
205
+ symbol-precise commit or import record. "who touched TaskController" answers
206
+ from its containing module's real history instead of a confident-looking but
207
+ wrong "nothing touched it".
208
+
209
+ **Following a list.** tmct remembers the last list it gave you. After
210
+ "which modules import src/core/model.mjs", "which of those are tested" or
211
+ "how many of those" resolves "those"/"them" against that list, not a fresh,
212
+ unresolved pronoun.
213
+
195
214
  **Synonyms and everyday phrasing.** tmct matches many of the words people
196
215
  actually use for the same idea, from a curated synonym list plus a filtered
197
216
  ConceptNet slice. A slightly different word for the same concept still
@@ -244,11 +263,12 @@ it gives you more: "give me a detailed summary of how X works" (or "explain
244
263
  in detail how X works", or "...detailed overview/explanation of X") gets a
245
264
  longer, multi-sentence account instead of one line. Every sentence in it is
246
265
  lifted from a real graph edge, attribute, or taught fact — never generated
247
- free text — and it declines outright rather than pad the gap when nothing
248
- clears its own relevance bar.
266
+ free text.
249
267
 
250
- From chat, a real run against the shipped `examples/mini-webapp` fixture
251
- (banner lines trimmed):
268
+ The wording varies a little too. A small, curated, deterministic pool swaps a
269
+ handful of connector words, like "defined in", "located in", or "found in".
270
+ The same fact doesn't read identically for every entity, but the same
271
+ question against the same entity always renders the same way.
252
272
 
253
273
  ```
254
274
  $ node bin/tmct.mjs chat --repo examples/mini-webapp --ephemeral
@@ -322,8 +342,8 @@ from the environment. There's no CLI flag yet — this is a library-level
322
342
  option for now, newer and less exercised than the default backend.
323
343
 
324
344
  Teaching isn't limited to the ACE grammar's fixed shapes. Tell tmct an
325
- arbitrary fact, like "margo eats ribs", and it mints a fact you can later ask
326
- about directly: "what does margo eat", or "does margo eat ribs".
345
+ arbitrary fact, like "margo really eats ribs", and it mints a fact you can
346
+ later ask about directly: "what does margo eat", or "does margo eat ribs".
327
347
 
328
348
  New vocabulary compounds as you teach it. "redis is a cache" mints "redis" as
329
349
  a class-level concept even though it was never in the built-in lexicon, and a
@@ -334,6 +354,10 @@ to ground one side first. Quantified teaching works too: "some functions are
334
354
  risky" stores the quantifier, and a later "how many functions are risky"
335
355
  answers "A few."
336
356
 
357
+ Once you've taught a few facts, "how many facts are there" counts them back.
358
+ That's the same count phrasing a code graph answers "how many classes are
359
+ there" with, just now reading tmct's own memory.
360
+
337
361
  ### Provenance and trust
338
362
 
339
363
  Every fact and text block records **where it came from and when**. Sources are
package/ROADMAP.md CHANGED
@@ -17,94 +17,18 @@ reading it finds in full, or is an honest miss when nothing grounds it at all.
17
17
  Declared, forward-looking goals — not yet achieved, stated here so they steer future work instead of
18
18
  getting silently traded away by inherited caution:
19
19
 
20
- - **Reach for Llama-3-level natural language fluency.** Not by putting an LLM in the product path
21
- (tmct will never do that — see "What tmct will never do"). Instead, by growing rich
20
+ - **Reach for Llama-3-level natural language fluency.** by growing rich
22
21
  template/surface-realization variety, so an answer shape has many valid phrasings instead of one
23
22
  fixed slot-fill.
24
23
  - **Resolve ambiguity breadth-first, always.** Every genuinely valid reading gets its own real answer
25
24
  restated in full, never a bare "could mean X or Y — try rephrasing" punt, bounded only by existing
26
- clipping/pagination limits. Landed for both ambiguity shapes tmct has: parse-level ties
27
- (`renderCore`'s real-answer resolution, `CAPABILITIES_1.7.3.md` item 92) and entity-level ties (one
28
- term matching several real graph individuals — every fuzzy-match tie, every noise-strip alt-object
29
- collision, `PLAN_BREADTH_FIRST_NLU.md` §1). CEFR-confirmed: `BENCHMARK_CEFR_ENGLISH_1.8.0.md`'s
30
- `ambiguity`-tagged cell moved 1.438 → 1.875 (+0.437, the largest single-tag move on record), and
31
- the fix resolved a case pair the `1.7.0` report called permanently unfixable (`am-meta-imports` vs
32
- `g-a1-naming-9` — same input, previously-incompatible expectations; both now score well against
33
- one breadth-first answer). A dedicated audit found the two generic top-level bail-out hints
34
- (`rephraseHint`/`compositionalHint`) are provably unavoidable at their actual miss sites, not
35
- unwired — nothing left to generalize there.
25
+ clipping/pagination limits. L
36
26
  - **Paraphrase alongside the original, verified, never instead of it.** A surface-realization variant
37
27
  sits next to the literal grounded answer, never replacing it, and its accuracy is checked, not
38
28
  assumed — by running tmct's own deterministic inference/consistency machinery (`src/syllogise.mjs`)
39
29
  against both the original and the paraphrase: they must entail the same conclusions, and neither may
40
- contradict the other sentence-by-sentence. The paraphrase generator itself stays template/rule-based,
41
- same as everything else in the product path — the novelty is verifying that variety costs nothing in
42
- accuracy, not the generation mechanism itself.
43
-
44
- These sit alongside, not against, the zero-fabrication discipline: an answer with no grounding is
45
- still an honest miss, and breadth-first resolution means showing every real answer a genuine reading
46
- produces, never inventing one to fill a gap.
47
-
48
- ## Current capability surface
49
-
50
- - **Grammar & parsing** (`src/grammar/`): an ACE-inspired controlled fragment (~8 sentence
51
- patterns), plus multi-candidate ambiguity resolution — when a sentence has genuinely more than one
52
- valid reading, every surviving interpretation is surfaced instead of one being guessed
53
- (`archive/PLAN_DID_YOU_SEE_HER_DUCK.md`).
54
- - **Compositional queries** (`src/ask.mjs`): recursive-descent over relative clauses, boolean
55
- set-algebra (and/or/but-not), qualifiers, aggregates, superlatives, anaphora. Includes real
56
- two-hop object-relative composition ("which modules import something that X depends on" —
57
- `parseNested` → `reverseSet`/`forwardSet`, nesting to depth ≥2), confirmed still working via a
58
- live-tested example (`TOO_HARD_AUDIT.md` U2) after a stale benchmark write-up called it
59
- "known-hard territory" — it was always built and tested, just never re-checked.
60
- - **Memory** (`src/memory/`): an OWL-labelled JSON graph on disk. Three persistence backends: flat
61
- JSON (default), pure in-memory (zero disk I/O), SQLite (cached, incrementally-patched reads).
62
- - **Reasoning** (`src/syllogise.mjs`): an OWL 2 RL-grounded rule ladder (subclass transitivity,
63
- disjointness, someValuesFrom subsumption, cardinality, consistency checking), plus taught-relation
64
- rules learned through ordinary chat (alias/union, fixed-hop composition, property-filtered
65
- composition, recursive/reachability) — none of it hardcoded per domain.
66
- - **Default persona**: a general-knowledge "human-world" vocabulary seeded by default (three size
67
- tiers, `--persona-size small|medium|large`), sourced from Open English WordNet and Schema.org.
68
- Code-domain vocabulary (SEON/ConceptNet) is opt-in (`--with-persona code`). Query coverage
69
- includes forward/reverse CapableOf, reverse-HasA, and reverse-inherits/subClassOf shapes (`"can a
70
- dog bark"`, `"what has a tail"`, `"what inherits from horse"`) against both corpus-seeded and
71
- freshly-taught facts.
72
- - **Genuine multi-reading ambiguity resolves and answers, not just describes**: when a sentence has
73
- two-plus valid readings — whether the ambiguity is in how the sentence PARSES or in which real graph
74
- ENTITY a term names — tmct traverses and renders each one's real answer inline (not just a one-line
75
- label), so the same input always reproduces the same full, useful answer.
76
- - **Every answer carries a canonical restatement of what was understood**: an English gloss in tmct's
77
- own preferred phrasing plus the same fact in a compact, machine-parsable notation
78
- (`shape(kind, args...)` for a query, `fact(subject, predicate, object)` for a taught fact) —
79
- landed for the ask/query and teach/assert lanes; other chat lanes (conversational, commands) don't
80
- have a real canonical form yet.
81
- - **Graph traversal and provenance timestamps extend to the memory graph, now with a real viewer AND
82
- a live embedded chat**: the hub-avoiding `spiralExpand` walk (previously code-graph/Module-only)
83
- generalizes to any graph via a caller-supplied class predicate and id-normalizer; edges carry a
84
- `createdAt` stamp and nodes get a derived `updatedAt`. `tmct viz [--focus <id>] [--output graph.html]`
85
- renders it as one self-contained, locally-navigable HTML file (pan/zoom, click-to-inspect, a depth
86
- stepper, per-class visibility filters, no server, no external deps) — `npm run viz -- --output
87
- graph.html && open graph.html`. The page embeds a real "Ask the graph" chat panel running tmct's
88
- OWN `ask.mjs` engine client-side (bundled via esbuild, adapter-less — no wink model, ~220KB): a
89
- query resolves against the full graph and re-centres the view on the answer (focus-follows-answer),
90
- and a node's class/label are click-to-query affordances.
91
- - **Completions** (`src/completions/`): extractive, multi-sentence answers for broad "how does X
92
- work" questions, grounded and source-cited — never invents a fact beyond what's retrieved, though
93
- see "Ambition" above for growing the phrasing variety around what's retrieved.
94
- - **Capability router** (`src/router/`): a deterministic, closed-toolset agentic router behind an
95
- Anthropic-compatible API — measured by `AGENTBENCH`, not general function-calling. An ambiguous tool
96
- argument stays an honest refusal (never a guess) but, since every registered capability is
97
- read-only, now additionally carries each tied candidate's real dispatched result alongside it.
98
- The C1 resolver defers a ranking/superlative request (a declared `SUPERLATIVE_EXTREMES` cue, e.g.
99
- "what MOST needs a test") to the C2 goal-reasoner's keystone-argmax arbitration instead of
100
- half-answering it with a flat unranked list — AGENTBENCH C2 is 11/11, 100% plan- and
101
- result-complete (`TOO_HARD_AUDIT.md` M2, fixed).
102
- - **Interfaces**: the `tmct` CLI, a documented library `exports` surface, and a Repository Interface
103
- for downstream consumers (seonix).
104
-
105
- Measured state for all of the above: the four `BENCHMARK_<TYPE>_<version>.md` reports
106
- (`AGENT`/`CEFR_ENGLISH`/`CONVERSATION`/`INFERENCE`) and the periodic `CAPABILITIES_<version>.md`
107
- audit — always check the latest-dated one, not this file, for real numbers.
30
+ contradict the other sentence-by-sentence..
31
+
108
32
 
109
33
  ## What's next (feature-shaped — see `HANDOVER.md` for the current task-level list)
110
34
 
@@ -118,51 +42,12 @@ audit — always check the latest-dated one, not this file, for real numbers.
118
42
  unpopulated), which is met; full population everywhere was always a bigger, separately-scoped
119
43
  follow-on. (b) growing the ACE grammar's free-form coverage past its measured 0/2,949-sentence
120
44
  baseline — §6's own stated non-goal was a harness + baseline + first generated batch, not closing
121
- the gap itself, which is met; growing coverage further is `archive/PLAN_TEMPLATE_COVERAGE.md`'s
122
- own remaining scope (needs more grammar patterns or vocabulary, not tooling). What's genuinely
123
- still open: (c) the paraphrase-verified-via-`syllogise.mjs` piece of "Ambition" — not started;
124
- (d) a real "list/count all X of class Y" query shape for memory-graph classes — live testing
125
- during the viz chat panel's build confirmed no such shape exists via `ask.mjs` alone (only
126
- `chat.mjs`'s heavier `factAnswer` cascade has it, out of the browser bundle's scope), so the viz
127
- panel's class-badge click currently falls back to a real client-side filter + a "where is X
128
- mentioned" query rather than a true "list all" — a genuine, now-documented gap, not a silent one.
129
- - **`archive/PLAN_TEMPLATE_COVERAGE.md`** — archived; the coverage-harness/generation design from
130
- (b) above, including the real baseline number and the first 17-row generated batch. **Coverage
131
- growth attempted 2026-07-12**: a residue audit found only 19 words across 2,664 docs sentences
132
- have pure content-word (vocabulary-only) residue — the rest is genuinely grammar-structure
133
- residue (coordination/negation/relative clauses), unfixable by vocabulary alone. Added 9 real,
134
- generalizable words (`danger`/`push`/`refusal`/`tooling`/`legitimate`/`ranked`/`related`/
135
- `sacred`/`unaffected`) to `src/grammar/lexicon-core.json`; real, honest delta: 0→1 hit (60.2%→
136
- 60.1% residue — a few sentences correctly reclassified residue→miss once their vocabulary gap
137
- closed and a separate structural gap was exposed). Confirms the ceiling is structural, not a
138
- vocabulary backlog — closing it further needs real grammar-pattern work (a coordination/relative-
139
- clause-capable parser), not more lexicon entries. **Second follow-on, wiring output variety into
140
- the live answer path — done 2026-07-12, scoped down honestly.** Split in two: (1) a dead-end
141
- example-correctness sweep (`SKILL_AGENT_FAST_LOOP.md`'s "an offered example that itself fails"
142
- rubric) found and fixed 3 genuinely broken examples across `ask.mjs`/`chat.mjs` (a `touches`-verb
143
- hint that could never resolve for any input, a count hint that never parses, an unseeded-session
144
- vocab-hint that lied). (2) surface-variety wiring landed 7 phrasing pools / 14 variants across 8
145
- template sites (`src/answer-variants.mjs`, deterministic per-key hash selection — never random),
146
- scoped down from an original ~50-template/~100-variant target after inventory showed most
147
- candidate templates either name a relation verb (off-limits — never vary `imports`/`calls`/
148
- `tests`/etc.) or are miss/rephrase-hint templates (handled by (1) instead, also off-limits).
149
- Deliberately left `ask.mjs`'s two "X is defined in Y" `whereShape` sites unvaried:
150
- `chatbench/graded-pool-max.jsonl` pins that exact substring as ground truth for 11 cases (2 in
151
- the always-run promoted subset), and that pool is append-only per its own governing skill doc.
152
- - **A fresh `CAPABILITIES_1.8.0.md` audit** — `CAPABILITIES_1.7.3.md` is pinned at commit `981c9b2`
153
- and doesn't cover any of `PLAN_BREADTH_FIRST_NLU.md`'s six tracks; this doc's "Current capability
154
- surface" above covers them narratively, but no full overlay audit has run since. Not done this
155
- pass — `BENCHMARK_CEFR_ENGLISH_1.8.0.md` alone was in scope.
45
+ the gap itself, which is met.
156
46
  - **`PLAN_ADVENTURE.md`** — a text-adventure architectural stretch: an imperative command grammar,
157
47
  mutable turn-by-turn world/player state as ordinary graph nodes (no special player-state store),
158
48
  and an NPC turn scheduler. Design-only.
159
49
  - **`PLAN_SYLLOGIST.md`** — retraction-aware consistency checking under a hard budget and trust
160
50
  tiers, the one open piece of the reasoning engine's research horizon. Design-only.
161
- - **`archive/PLAN_CONVERSATION.md` Finding 4** — an anaphoric "SUBJECT verb which N" inheritance
162
- question misroutes into teach-a-fact; needs a discontiguous verb-frame parser, a POS-aware
163
- mid-sentence interrogative detector, and a union-kind reverse-question fix. Large, three
164
- sub-problems, not attempted in a single pass — a concrete first-increment sketch exists
165
- (`HANDOVER.md`), not an undesignable question.
166
51
  - **`PLAN_GUESS_NUMBER.md` / `PLAN_HANOI.md`** — closed-loop and open-loop planning domains for the
167
52
  `findActionPath`/`findReachableSet` kernels, both already built and proven but not yet wired to
168
53
  either domain. Design-only.
@@ -171,13 +56,7 @@ audit — always check the latest-dated one, not this file, for real numbers.
171
56
  - **`PLAN_AGENTS.md`** — the governing plan for tmct's broader multi-repo arc (marginalia, seonix,
172
57
  a pluggable LLM rung for Claude Code/Bedrock/Copilot). Check its own sequencing table for current
173
58
  phase status, not this file.
174
- - **`archive/PLAN_VIZ.md`** — archived; CLI-wiring and rendering (`tmct viz`, a recency-seeded,
175
- hub-avoiding spiral walk with pseudo-3D depth rendering) shipped and are done. Three real items
176
- remain, un-staffed: (1) the code-graph timestamp-provider architectural decision (provider-
177
- populated vs. a new tmct-owned local-git mode); (2) the maintainer-side git-log-corpus generation
178
- step, wiring seed-time `README.md` ingestion, and a new `Source` "seed" kind; (3) an eager
179
- session/sessionless anchor individual with a `mode` field (deciding the two-Session-classes
180
- question at the same time). See the archived doc's own "Next step" for the full detail.
59
+
181
60
 
182
61
  ## Research horizon
183
62
 
@@ -212,7 +91,7 @@ citations, not stop signs:
212
91
  (2026-07-11): `"tail"` (Unix process vs. animal body part) collides under `normFactTerm`'s
213
92
  cross-corpus flattening, `src/memory/core.mjs:1109-1134`.
214
93
 
215
- **Tier-4: learn-on-miss acquisition** (a real planned capability, not just research). The strongest
94
+ **Tier-4: learn-on-miss acquisition**. The strongest
216
95
  miss signal tmct can emit: lexicon term recognized, query built cleanly, zero matches anywhere — the
217
96
  question was well-formed and the knowledge is simply absent. Web search on the resolved term → clean
218
97
  the fetched text into the ACE-OWL controlled grammar → store with source provenance → answer the
@@ -220,14 +99,6 @@ original question, citing what was just learned. Strictly opt-in, offline defaul
220
99
  Prerequisites: the provenance-trust policy must extend to `via:"learned:web"`, never silently
221
100
  blending web-sourced facts with graph/operator facts.
222
101
 
223
- ## What tmct will never do
224
-
225
- - Run on AWS or maintain a benchmark rig — tmct is a published npm library + CLI with a static
226
- GitLab Pages home page, nothing more.
227
- - Publish automatically — a version release always requires a deliberate version-bump commit.
228
- - Run an MCP server or put an LLM in the product path — a core identity decision, decided once,
229
- not revisited.
230
-
231
102
  ## Design docs
232
103
 
233
104
  Every substantial design lives in its own `PLAN_*.md` at the repo root (active) or `archive/`
@@ -36,7 +36,7 @@
36
36
  # 1. Article selection — a/an by the following word's phonetic onset. The live
37
37
  # defect this fixes: the assert echo "every module is a artifact" -> "an
38
38
  # artifact". Reads the next word (whether in-span or the leading token of the
39
- # following protected span); refuses at a boundary it cannot read safely.
39
+ # following protected span).
40
40
  [[rule]]
41
41
  id = "article-selection"
42
42
  kind = "article"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "1.8.12",
3
+ "version": "1.8.15",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; no codebase index of its own.",
@@ -14,14 +14,6 @@
14
14
  // reuses the real spiralExpand instead, so the browser walk is byte-identical
15
15
  // to the CLI's).
16
16
  //
17
- // Bundled by scripts/build-ask-bundle.mjs into src/ask-browser.bundle.js (an
18
- // IIFE), which viz.mjs inlines verbatim into the viewer page's own <script>.
19
- // Runs adapter-less (no wink model in the browser — ask()'s lemma/POS tier
20
- // degrades to its curated + fuzzy tiers, exactly the boundary
21
- // test/ask-nlp.test.mjs's own "viewer bundle without wink" test proves stays
22
- // answerable) and grammar-lite (no ACE/construction-grammar strategies, both
23
- // fs-dependent — the plain grammar/keyword-spot/noise-strip strategies still
24
- // answer every shape tmct viz's own memory-graph queries need).
25
17
  import { ask, parseQuery } from "./ask.mjs";
26
18
  import {
27
19
  parseEntities, spiralExpand, mostRecentIndividual, derivedUpdatedAt, MEMORY_SPIRAL_EXPAND_KINDS,
@@ -1322,6 +1322,11 @@
1322
1322
  "a",
1323
1323
  "an",
1324
1324
  "some",
1325
+ // "what OTHER classes inherit from Controller" — "other" is a vague determiner
1326
+ // like "some", not a qualifying adjective; without this it was misread as a
1327
+ // fuzzy find TERM ("no classes found matching 'other'") instead of falling
1328
+ // through to the ordinary reverse-inherits parse (fast-loop round 6 finding).
1329
+ "other",
1325
1330
  // topic lead-in filler — "what about the modules", "how about classes": "about"
1326
1331
  // carries no graph meaning here, so stripping it lets the bare kind noun surface for
1327
1332
  // the cascade's bare-kind-noun terminal rule (ask.mjs). ("what"/"how" are structural
@@ -1427,6 +1432,9 @@
1427
1432
  );
1428
1433
  var MISSPELLING_RE = correctionRe(MISSPELLINGS);
1429
1434
  var WRONG_WORD_RE = correctionRe(WRONG_WORDS);
1435
+ var W_SLASH_RE = /(?<=^|\s)w\/(?=\s|$)/gi;
1436
+ var FOR_DIGIT_THANKS_RE = /\b(thx|thanks|thank\s+you|many\s+thanks|ty|cheers)\s+4\b/gi;
1437
+ var FOR_DIGIT_EXAMPLE_RE = /\b4\s+(example|instance)\b(?!\s*[a-z])/gi;
1430
1438
  var KIND_NOUN_ANAPHORA_RE = /\b(this|that)\s+(class|module|function|method|attribute|variable|file|commit)\b/gi;
1431
1439
  var VERB_ALTERNATION = Object.keys(VERB_TO_KIND).sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
1432
1440
  var RELATION_VERB_RE = new RegExp(
@@ -1574,6 +1582,9 @@
1574
1582
  q = q.replace(CONTRACTION_RE, (m) => CONTRACTIONS[m.toLowerCase()]);
1575
1583
  q = q.replace(MISSPELLING_RE, (m) => MISSPELLINGS[m.toLowerCase()]);
1576
1584
  q = q.replace(WRONG_WORD_RE, (m) => WRONG_WORDS[m.toLowerCase()]);
1585
+ q = q.replace(W_SLASH_RE, "with");
1586
+ q = q.replace(FOR_DIGIT_THANKS_RE, (_, w) => `${w} for`);
1587
+ q = q.replace(FOR_DIGIT_EXAMPLE_RE, (_, w) => `for ${w}`);
1577
1588
  q = q.replace(KIND_NOUN_ANAPHORA_RE, (_, pron) => pron);
1578
1589
  q = q.replace(G_DROP, "$1ing");
1579
1590
  q = applyPreambleFrames(q);
@@ -2447,7 +2458,16 @@
2447
2458
  Commit: ["commit", "commits"],
2448
2459
  // "Change" is ask-vocab.mjs's pseudo-type (a wildcard over the touch traversal's
2449
2460
  // results, never a node class) — it still needs noun forms for zero-hit templates.
2450
- Change: ["change", "changes"]
2461
+ Change: ["change", "changes"],
2462
+ // Memory-graph classes (memory/core.mjs) — real noun forms for the dynamic
2463
+ // class count/list fallback (PLAN_BREADTH_FIRST_NLU.md (d), see
2464
+ // dynamicClassQuery below) so "2 facts." reads naturally instead of falling
2465
+ // back to the generic "2 results.".
2466
+ Fact: ["fact", "facts"],
2467
+ Utterance: ["utterance", "utterances"],
2468
+ Session: ["session", "sessions"],
2469
+ Source: ["source", "sources"],
2470
+ Rule: ["rule", "rules"]
2451
2471
  };
2452
2472
  function nounFor(entityType, n) {
2453
2473
  const [s, p] = PLURAL_FORMS[entityType] || ["result", "results"];
@@ -3765,7 +3785,7 @@
3765
3785
  if (!result.matches.length) {
3766
3786
  return { content: `no ${nounFor(result.entityType, 2)} in this index.`, miss: true, ambiguous: false, matches: [] };
3767
3787
  }
3768
- const scopeable = !["Module", "Commit"].includes(result.entityType);
3788
+ const scopeable = !["Module", "Commit", "Fact", "Utterance", "Session", "Source", "Rule"].includes(result.entityType);
3769
3789
  const hint = !result.scoped && scopeable && result.matches.length > OVERFLOW_CAP ? ` \u2014 narrow with "${nounFor(result.entityType, 2)} in <module>"` : "";
3770
3790
  return { content: `${compositeList(result.matches)}${hint}.`, miss: false, ambiguous: false, matches: result.matches };
3771
3791
  }
@@ -4996,6 +5016,37 @@ ${result.branches.map((b, i) => `${i + 1}) ${b.candidate.label}: ${b.rendered.co
4996
5016
  const bareTrimmed = out.trim().replace(/[?.!]+$/, "");
4997
5017
  return BARE_WHEN_COMMIT_RE.test(bareTrimmed) ? `${bareTrimmed} touched` : out;
4998
5018
  }
5019
+ function singularCandidates(word) {
5020
+ const w = String(word || "").toLowerCase();
5021
+ const c = /* @__PURE__ */ new Set([w]);
5022
+ if (w.endsWith("ies")) c.add(`${w.slice(0, -3)}y`);
5023
+ if (w.endsWith("ses")) c.add(w.slice(0, -2));
5024
+ else if (w.endsWith("es")) c.add(w.slice(0, -2));
5025
+ if (w.endsWith("s") && w.length > 1) c.add(w.slice(0, -1));
5026
+ return [...c];
5027
+ }
5028
+ function resolveDynamicClass(graph, word) {
5029
+ const cands = singularCandidates(word);
5030
+ for (const ind of graph?.individuals || []) {
5031
+ if (ind?.class && cands.includes(String(ind.class).toLowerCase())) return ind.class;
5032
+ }
5033
+ return null;
5034
+ }
5035
+ var DYNAMIC_LIST_TRIGGER_RE = /^(?:list|show(?:\s+me)?)\s+(?:all\s+|the\s+)?([a-z][a-z'-]*)\s*(.*)$/i;
5036
+ var DYNAMIC_COUNT_TRIGGER_RE = /^(?:how\s+many|number\s+of|count(?:\s+the)?)\s+([a-z][a-z'-]*)\s*(.*)$/i;
5037
+ var DYNAMIC_TAIL_OK_RE = /^(?:are there(?:\s+in\s+total)?|is there|do you know(?:\s+about)?|do you have|exist(?:s)?|are known|in (?:the |a )?(?:graph|memory)|you know(?:\s+about)?)?[?.!\s]*$/i;
5038
+ function dynamicClassQuery(graph, query) {
5039
+ const q = String(query || "").trim();
5040
+ const listM = q.match(DYNAMIC_LIST_TRIGGER_RE);
5041
+ const countM = !listM ? q.match(DYNAMIC_COUNT_TRIGGER_RE) : null;
5042
+ const m = listM || countM;
5043
+ if (!m || !DYNAMIC_TAIL_OK_RE.test(m[2] || "")) return null;
5044
+ if (ENTITY_TO_TYPE[m[1].toLowerCase()]) return null;
5045
+ const entityType = resolveDynamicClass(graph, m[1]);
5046
+ if (!entityType) return null;
5047
+ const base = { node: "allOfClass", entityType };
5048
+ return listM ? { node: "list", entityType, base, scoped: false } : { node: "count", entityType, base };
5049
+ }
4999
5050
  function ask(graph, query, { contextId = null, nlp = void 0, prev = null } = {}) {
5000
5051
  if (isHelpRequest(query)) {
5001
5052
  return {
@@ -5026,8 +5077,21 @@ ${result.branches.map((b, i) => `${i + 1}) ${b.candidate.label}: ${b.rendered.co
5026
5077
  relaxed = { from: r.from, to: r.to, dropped: r.dropped, steps: r.steps };
5027
5078
  }
5028
5079
  }
5029
- const result = traverse(graph, parsed, { contextId, prev });
5030
- const rendered = render(parsed, result);
5080
+ let result = traverse(graph, parsed, { contextId, prev });
5081
+ let rendered = render(parsed, result);
5082
+ if (rendered.miss && !rendered.ambiguous) {
5083
+ const dyn = dynamicClassQuery(graph, query);
5084
+ if (dyn) {
5085
+ const dynResult = traverse(graph, dyn, { contextId, prev });
5086
+ const dynRendered = render(dyn, dynResult);
5087
+ if (!dynRendered.miss) {
5088
+ parsed = dyn;
5089
+ result = dynResult;
5090
+ rendered = dynRendered;
5091
+ relaxed = null;
5092
+ }
5093
+ }
5094
+ }
5031
5095
  let content = relaxed && !rendered.miss && relaxed.to !== relaxed.from ? `read as "${relaxed.to}" \u2014 ${rendered.content}` : rendered.content;
5032
5096
  if (!relaxed && !rendered.miss && !rendered.ambiguous && directFull.alternates.length) {
5033
5097
  const answered = directFull.alternates.map((a) => {
package/src/ask-nlp.mjs CHANGED
@@ -1,14 +1,5 @@
1
1
  // ask-nlp.mjs — the OPTIONAL wink-nlp adapter behind ask.mjs's lemma/POS tier.
2
2
  //
3
- // BOUNDARY (hard, do not move): this file is Node-only and is NEVER inlined into
4
- // the viewer bundle. viz.mjs's askSource() inlines codegraph.mjs + ask-vocab.mjs +
5
- // ask.mjs ONLY and strips their import lines, so the portable single-file HTML has
6
- // no wink, no model, and no `nlpAdapter` binding at all — ask.mjs reaches this
7
- // module exclusively through a `typeof nlpAdapter === "function"` guard and
8
- // degrades to adapter-less parsing (lemma/POS tiers off; the curated tables and
9
- // the bounded edit-distance tier still work, browser and Node alike). Keeping the
10
- // ~1MB CJS model out of the page is the point of the split.
11
- //
12
3
  // wink-nlp and wink-eng-lite-web-model are loaded through the shared leaf loader
13
4
  // src/wink-model.mjs (Node `createRequire` fallback + a browser registration seam),
14
5
  // so this file no longer carries its own Node-only load block. The load happens