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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +441 -202
  2. package/bin/tmct.mjs +126 -1
  3. package/package.json +4 -2
  4. package/src/answer-variants.mjs +8 -36
  5. package/src/ask-browser-entry.mjs +5 -23
  6. package/src/ask-browser.bundle.js +1 -2
  7. package/src/ask-nlp.mjs +9 -23
  8. package/src/ask-vocab.mjs +139 -589
  9. package/src/ask.mjs +627 -1729
  10. package/src/chat.mjs +1684 -2872
  11. package/src/cli-args.mjs +14 -28
  12. package/src/codegraph.mjs +236 -644
  13. package/src/completions/complete.mjs +18 -62
  14. package/src/completions/graph-adapter.mjs +14 -60
  15. package/src/completions/group.mjs +12 -68
  16. package/src/completions/infer.mjs +38 -126
  17. package/src/completions/prune.mjs +17 -70
  18. package/src/completions/rank.mjs +16 -69
  19. package/src/completions/search.mjs +8 -31
  20. package/src/concept.mjs +32 -88
  21. package/src/conformance.mjs +11 -15
  22. package/src/corpus/conceptnet.mjs +31 -89
  23. package/src/corpus/templates.mjs +19 -45
  24. package/src/corpus/unknown-ingest.mjs +31 -92
  25. package/src/embed.mjs +10 -22
  26. package/src/extensions.mjs +50 -154
  27. package/src/finish.mjs +35 -91
  28. package/src/grammar/ace.mjs +16 -40
  29. package/src/grammar/assert.mjs +1 -1
  30. package/src/grammar/lexicon-core.json +1 -1
  31. package/src/grammar/lexicon.mjs +9 -27
  32. package/src/graph-merge.mjs +2 -3
  33. package/src/hash.mjs +6 -14
  34. package/src/index.mjs +6 -10
  35. package/src/init.mjs +38 -125
  36. package/src/interpret/fuzzy.mjs +10 -29
  37. package/src/interpret/merge.mjs +9 -27
  38. package/src/interpret/normalize.mjs +137 -585
  39. package/src/interpret/pipeline.mjs +23 -71
  40. package/src/interpret/strategies/ace.mjs +7 -31
  41. package/src/interpret/strategies/constructions.mjs +14 -41
  42. package/src/interpret/strategies/grammar.mjs +21 -60
  43. package/src/interpret/strategies/keywords.mjs +42 -131
  44. package/src/interpret/strategies/noise-strip.mjs +18 -89
  45. package/src/memory/bias.mjs +11 -54
  46. package/src/memory/blocks.mjs +18 -69
  47. package/src/memory/core.mjs +171 -591
  48. package/src/memory/fold.mjs +0 -0
  49. package/src/memory/inspect.mjs +7 -25
  50. package/src/memory/shacl.mjs +10 -39
  51. package/src/memory/trust.mjs +26 -127
  52. package/src/memory-ask-browser-entry.mjs +7 -30
  53. package/src/memory-ask-browser.bundle.js +1 -1
  54. package/src/paraphrase.mjs +20 -53
  55. package/src/planning.mjs +15 -157
  56. package/src/prose-nlp.mjs +4 -17
  57. package/src/prose.mjs +19 -67
  58. package/src/providers/bootstrap.mjs +1 -2
  59. package/src/providers/fixture.mjs +1 -2
  60. package/src/providers/graph-service.mjs +28 -59
  61. package/src/repository-interface.mjs +6 -8
  62. package/src/router/drive.mjs +183 -0
  63. package/src/router/goal-reasoner.mjs +66 -231
  64. package/src/router/guardrail.mjs +20 -58
  65. package/src/router/planner.mjs +15 -46
  66. package/src/router/registry.mjs +13 -43
  67. package/src/router/resolver.mjs +46 -131
  68. package/src/router/results.mjs +231 -0
  69. package/src/schema-docs.mjs +10 -27
  70. package/src/server-http.mjs +10 -19
  71. package/src/server.mjs +22 -28
  72. package/src/sessions.mjs +15 -30
  73. package/src/source-slice.mjs +5 -7
  74. package/src/source.mjs +10 -20
  75. package/src/syllogise.mjs +187 -575
  76. package/src/telemetry.mjs +3 -3
  77. package/src/toml-config.mjs +4 -4
  78. package/src/tui/app.mjs +9 -19
  79. package/src/viz.mjs +66 -123
  80. package/src/wink-model.mjs +10 -24
@@ -1,36 +1,13 @@
1
- // src/router/planner.mjs — Stage 3 of the capability router
2
- // (PLAN_CAPABILITY_ROUTER.md): THE PLANNER. Compose a bounded, ordered plan of
3
- // tool calls for a multi-step request, over the SAME operators Stage 1 resolves
4
- // single-shot. Pure-JS POP/HTN + a Steel & Ho monitor-and-replan loop under a
5
- // HARD budget sound/complete INSIDE the declared operator model, honest-refuse
6
- // (escalate) for novelty outside it. Deterministic, no-LLM, glass-box.
7
- //
8
- // THE MODEL, mapped to the literature:
9
- // - HTN decomposition (NONLIN/SHOP2): a compound request is decomposed into an
10
- // ORDERED list of sub-goals by declared METHODS — the sequencing connectives
11
- // ("... then ...", "... and then ...") and the two closed recipes we author:
12
- // the CONDITIONAL method ("if <check>, <action> [instead]") and the
13
- // RELATIVE-FILTER method ("of the <set> <rel> X, which are <Y>"). Each leaf
14
- // sub-goal is resolved by Stage 1 (resolveOne) — the primitive operator.
15
- // - POP causal links (partial-order planning): each step's proof records the
16
- // PRODUCER -> CONDITION -> CONSUMER link. An independent step's producer is
17
- // the grounded graph (graph-loaded); a THREADED step (one whose entity came
18
- // from a prior step via anaphora — "its subclasses", "describe it") records
19
- // the prior STEP as its producer. That link IS the proof chain (grade.mjs's
20
- // connectedness check reads it), never a flat ok-list. Least commitment: we
21
- // only order what the connectives actually order.
22
- // - Steel & Ho monitor-and-replan: after each call we read the tool_result;
23
- // a failed sub-goal (an unresolvable entity / an operator that errors) forces
24
- // an honest STOP (refuse/escalate) rather than pressing on with a broken
25
- // chain. Bounded depth + a hard step counter GUARANTEE termination — no
26
- // unbounded search can ever wedge the caller (the harness also caps us).
27
- //
1
+ // src/router/planner.mjs — the planner. Compose a bounded, ordered plan of tool calls for
2
+ // a multi-step request, over the SAME operators resolver.mjs resolves single-shot. HTN
3
+ // decomposition into leaf sub-goals (each resolved by resolver.mjs),
4
+ // with a POP causal-link proof chain (a threaded step's producer is the prior step; an
5
+ // independent step's is the grounded graph). Monitored: a failed sub-goal stops the plan
6
+ // honestly rather than pressing on. Bounded by MAX_STEPS. Deterministic, no-LLM, glass-box.
28
7
 
29
8
  import { resolveOne, extractEntity } from "./resolver.mjs";
30
9
 
31
- // Hard budget — the planner may emit at most this many steps; a request that
32
- // decomposes to more is REFUSED (escalate) rather than searched. Guarantees
33
- // termination independent of the harness backstop.
10
+ // Hard budget — a request decomposing to more steps is refused rather than searched.
34
11
  export const MAX_STEPS = 8;
35
12
 
36
13
  const PRONOUN_RE = /\b(?:it|its|them|those|these|that|their)\b/i;
@@ -70,14 +47,10 @@ export function decompose(request) {
70
47
  };
71
48
  }
72
49
 
73
- // METHOD 3 — the MEMBER-FILTER recipe: "which/what methods|members of X …
74
- // (end up|eventually)? calling/reaching Y". A C1 surface-syntax recipe like the
75
- // conditional and relative-filter methods above (the C1 discipline: a closed,
76
- // authored shape NOT the C2 goal-reasoner's deduction). Decomposes to
77
- // [enumerate members(X), filter by bounded transitive call-reach of Y]. The
78
- // second segment is the filter TARGET, role "member-filter": the DRIVER owns
79
- // the per-member callees hop + the reachability fold (driver-resolver.mjs) —
80
- // segment 2 is not a resolvable leaf sub-goal on its own.
50
+ // METHOD 3 — the MEMBER-FILTER recipe: "which/what methods|members of X … calling/
51
+ // reaching Y". Decomposes to [enumerate members(X), filter by transitive call-reach of Y];
52
+ // segment 2 (role "member-filter") is not a resolvable leaf on its own — the driver owns
53
+ // the per-member callees hop + reachability fold.
81
54
  const mem = raw.match(
82
55
  /^(?:which|what)\s+(?:methods?|members?)\s+of\s+(.+?)\s+(?:(?:end\s+up|eventually)\s+)?(?:calls?|calling|reach(?:es|ing)?|invokes?|invoking)\s+(.+?)\s*\??$/i,
83
56
  );
@@ -129,9 +102,8 @@ const refuse = (why, driver) => ({ calls: [], refused: true, terminated: true, p
129
102
 
130
103
  /** Plan + execute a multi-step request. Returns a loopResult
131
104
  * { calls, refused, terminated, proof, why, driver, observed }
132
- * with a POP causal-link proof chain. Steel & Ho: each step is monitored; a
133
- * failed sub-goal STOPS the plan honestly (refuse/escalate). Bounded by
134
- * MAX_STEPS + a hard step counter. `driver` labels the row.
105
+ * with a POP causal-link proof chain. Each step is monitored; a failed sub-goal stops the
106
+ * plan honestly. Bounded by MAX_STEPS.
135
107
  *
136
108
  * ctx: { dispatch(name,input)->{ok,text,resolved?}, resolve(term)->resolveObject } */
137
109
  export async function plan(request, declaredNames, ctx, { driver = "resolver-0.8.0" } = {}) {
@@ -155,15 +127,12 @@ export async function plan(request, declaredNames, ctx, { driver = "resolver-0.8
155
127
 
156
128
  const r = await resolveOne(text, declaredNames, ctx, { execute: true });
157
129
  if (r.refused) {
158
- // Steel & Ho: an unresolvable sub-goal breaks the causal chain — STOP
159
- // honestly (escalate), never emit a partial/guessed plan.
160
130
  return refuse(`sub-goal ${i + 1} ("${text}") did not resolve: ${r.reason}`, driver);
161
131
  }
162
132
 
163
133
  calls.push(r.selected);
164
- // POP causal link: the producer is the prior step when this step THREADED an
165
- // anaphor from it; otherwise the grounded graph. Its condition is the arg the
166
- // step needed. This is the "why step i" edge, not a flat ok.
134
+ // Causal link: producer is the prior step when this step threaded an anaphor from it,
135
+ // otherwise the grounded graph.
167
136
  const producer = seg.thread && i > 0 ? `step-${i}` : "graph";
168
137
  const boundLabel = r.resolved?.label ?? Object.values(r.selected.input || {})[0] ?? null;
169
138
  proof.push({ step: "causal-link", producer, condition: boundLabel, consumer: `step-${i + 1}:${r.selected.name}`, role: seg.role, ok: true });
@@ -1,34 +1,14 @@
1
- // src/router/registry.mjs — Stage 0 of the capability router (PLAN_CAPABILITY_ROUTER.md).
1
+ // src/router/registry.mjs — the capability registry.
2
2
  //
3
- // Each tmct tool is modelled as a STRIPS/PDDL operator declared as DATA:
4
- // a `Capability` with typed `Parameter`s, `Precondition`s, and `Effect`s
5
- // (an add-list / delete-list). This is the direct mapping the reference note
6
- // docs/references/planning/STRIPS_PDDL.md calls "the most direct in the whole
7
- // set": a capability IS a STRIPS operator expressed in tmct's OWL vocabulary.
3
+ // Each tmct tool is modelled as a STRIPS/PDDL operator declared as DATA: a `Capability` with
4
+ // typed `Parameter`s, `Precondition`s, and `Effect`s (add-list/delete-list). Preconditions are
5
+ // the safety gate guardrail.mjs checks before a call fires; effects are epistemic
6
+ // (a read-only query "knows" a topic, never mutates) — resolver.mjs backward-chains
7
+ // from a goal `(knows <topic> ?x)` to the capability whose add-list achieves it.
8
8
  //
9
- // - Preconditions are the SAFETY GATE a capability will not fire unless its
10
- // preconditions are provably satisfied (Stage 4, the guardrail, reads these).
11
- // This is why the router REFUSES rather than emitting an unsafe call, the
12
- // same discipline as tmct's honest miss.
13
- // - Effects are the PROOF CHAIN — for a read-only query tool the effect is
14
- // EPISTEMIC (it makes a fact KNOWN to the agent), never a world mutation, so
15
- // every graph-query capability has an EMPTY delete-list (the STRIPS closed-
16
- // world assumption: what is not deleted is unchanged, and a query changes
17
- // nothing in the world). Stage 1 (the resolver) backward-chains from a goal
18
- // `(knows <topic> ?x)` to the capability whose add-list achieves it.
19
- //
20
- // This module is PURE: plain frozen data + pure accessor functions, NO I/O. The
21
- // tool NAMES + parameter ARG KEYS are the exact ones src/server.mjs `dispatchTool`
22
- // reads (verified against its switch), so a bound call this registry validates is
23
- // directly dispatchable. Stage 1 (resolver) and Stage 4 (guardrail) consume this
24
- // substrate; nothing here imports the graph or the network.
25
-
26
- // ---- OWL-labelled vocabulary (tmct's style: urn:tmct:… prefixes) ------------
27
- // The registry declares its OWN vocabulary the way every tmct graph artifact
28
- // does (see the fixture's `prefixes` block + the schema-doc individuals). A
29
- // capability is a `cap:Capability` individual; its parts are `cap:Parameter`,
30
- // `cap:Precondition`, `cap:Effect`. Parameter TYPES range over the same seon/mgx
31
- // entity classes the code graph already speaks (Module, Class, Function, …).
9
+ // Pure: plain frozen data + pure accessors, no I/O. Tool names + parameter arg keys are the
10
+ // exact ones src/server.mjs `dispatchTool` reads, so a bound call this registry validates is
11
+ // directly dispatchable.
32
12
 
33
13
  export const PREFIXES = Object.freeze({
34
14
  cap: "urn:tmct:cap#", // the capability/operator vocabulary (this module)
@@ -208,21 +188,11 @@ const BY_NAME = Object.freeze(
208
188
  );
209
189
 
210
190
  // ---- closed-world / DEFAULT-DENY --------------------------------------------
211
- // The registry is a deliberate, DOCUMENTED STRICT SUBSET of the src/server.mjs
212
- // `dispatchTool` switch. The model is CLOSED-WORLD default-deny: a tool name
213
- // that is NOT a registered capability is treated as UNKNOWN — the guardrail /
214
- // AGENTBENCH grader rejects it as a hallucination (`hallucinationsIn` →
215
- // "unknown-tool"), so a planner/shim that emits an UNREGISTERED tool is an
216
- // AUTOMATIC FAIL, exactly as if it invented a tool that does not exist. This is
217
- // the safety posture: only what is declared (with real preconditions) may fire.
191
+ // The registry is a strict subset of src/server.mjs's `dispatchTool` switch: a tool name
192
+ // not registered here is treated as unknown/hallucinated, never dispatchable.
218
193
  //
219
- // The following dispatch tools are INTENTIONALLY UNREGISTERED — they emit
220
- // UNBOUNDED raw output (a source snippet / a whole edit-context bundle), which
221
- // is the most hallucination-prone surface and NOT a clean STRIPS query with a
222
- // bounded epistemic effect. Registering them would require modelling
223
- // output-size + file-read preconditions we have not committed to; until then,
224
- // default-deny keeps them OUT of the router's provable envelope by design (not
225
- // by omission). Recorded here so the exclusion is a decision, not an accident:
194
+ // The following dispatch tools are INTENTIONALLY UNREGISTERED — they emit unbounded raw
195
+ // output, the most hallucination-prone surface, not a clean bounded-epistemic-effect query.
226
196
  export const EXCLUDED_FROM_REGISTRY = Object.freeze({
227
197
  tmct_context: "unbounded edit-context bundle (multi-file); needs a size/budget precondition",
228
198
  tmct_context_more: "unbounded context continuation; same as tmct_context",
@@ -1,43 +1,17 @@
1
- // src/router/resolver.mjs — Stage 1 of the capability router (PLAN_CAPABILITY_ROUTER.md):
2
- // THE RESOLVER. Turn a request into a SELECTED registry capability with bound
3
- // arguments, by unification + backward chaining over capabilities-as-facts
4
- // (a mini-Datalog/SLD step, exactly the "open-condition satisfaction" the plan
5
- // names). Deterministic, no-LLM, glass-box: every choice is provable.
1
+ // src/router/resolver.mjs — the resolver. Turn a request
2
+ // into a SELECTED registry capability with bound arguments, by backward chaining over
3
+ // capabilities-as-facts. Deterministic, no-LLM, glass-box: every choice is provable.
6
4
  //
7
- // THE CORE is the ask-kind -> capability MAPPING. A request becomes an epistemic
8
- // GOAL `(knows <topic> ?of)`; each capability's add-effect declares which topic
9
- // it achieves (registry.mjs `knows(topic, of)`); backward chaining finds the
10
- // capability whose add-list unifies with the goal and binds ?of to the request's
11
- // object term. Three fact sources feed the SAME backward-chaining step:
5
+ // A request becomes an epistemic GOAL `(knows <topic> ?of)`; backward chaining finds the
6
+ // capability whose add-effect achieves it and binds `?of` to the request's object term.
7
+ // Three fact sources feed the same backward-chaining step, tried in order: the command
8
+ // register (server-http.mjs's terse verbs, ground truth, tried first since a terse command
9
+ // can mis-parse through the NL grammar), the NL parse (ask.mjs's relational grammar, via
10
+ // NL_INTENTS), and imperative intent FRAMES (curated phrasings the relational grammar
11
+ // doesn't carry, and a rescue path when the NL parse selects an out-of-set capability).
12
12
  //
13
- // 1. THE COMMAND REGISTER (server-http.mjs `selectTool`, the terse verbs
14
- // "describe X" / "callers X" / "untested") — exact + unambiguous, tried
15
- // FIRST because a terse command mis-parses through the NL grammar (e.g.
16
- // "callees Widget.render" keyword-spots to shape:reverse/kind:calls, which
17
- // would wrongly route to callers). A literal command verb is ground truth.
18
- // 2. THE NL PARSE (ask.mjs `parseQuery` -> {shape, kind, entityType, object}) —
19
- // the relational grammar ("which functions call X", "what does X export").
20
- // NL_INTENTS maps a {shape,kind} to the epistemic TOPIC; backwardChain maps
21
- // the topic to the capability. This is the Stage-1 deliverable proper.
22
- // 3. IMPERATIVE INTENT FRAMES (Stage 2, this module's FRAMES table) — curated
23
- // phrasings the relational grammar does not carry ("blast radius of X",
24
- // "who calls X", "search for X", "the call edges of X", "explain X"): a
25
- // regex -> {topic, arg}. Same backward chaining (topic -> capability), same
26
- // resolveObject binding. This is the surface that lifts NL reach above the
27
- // command register: it reaches tmct_calls (the raw call-edge dump — a grain
28
- // the relational "call" verb collides with) via an EXPLICIT edge-dump frame,
29
- // and it rescues a request whose NL parse selected an OUT-OF-SET capability
30
- // by re-selecting a DECLARED one (resolveOne falls through to the frame).
31
- //
32
- // ENTITY BINDING is DELEGATED to `resolveObject` (ask.mjs — the tiered lemma/
33
- // fuzzy binding oracle with honest ambiguity). This module NEVER re-implements
34
- // resolution: the registry's `resolves(param, as)` precondition maps exactly to
35
- // a resolveObject call, and an ambiguous / no-match term is an HONEST REFUSE,
36
- // never a guess. On any no-fit -> refuse.
37
- //
38
- // Pure-ish: mapParse/mapFrame/backwardChain/commandCapability are pure; resolveOne
39
- // is async only because it consults ctx.resolve (the graph binding oracle) and
40
- // ctx.dispatch (executes the grounded call). No network, no Date.now.
13
+ // Entity binding is delegated to `resolveObject` (ask.mjs); an ambiguous or no-match term
14
+ // is an honest refuse, never a guess.
41
15
 
42
16
  import { parseQuery } from "../ask.mjs";
43
17
  import { selectTool } from "../server-http.mjs";
@@ -47,23 +21,17 @@ import {
47
21
  } from "./registry.mjs";
48
22
  import { hallucinationsIn } from "./call-validator.mjs";
49
23
 
50
- // A ranking/superlative cue ("most", "biggest", …) in the request — the SAME
51
- // declared vocabulary ask.mjs's own superlative grammar reads (SUPERLATIVE_EXTREMES),
52
- // never a new keyword table. Used below to keep a flat imperative frame (a
53
- // single unranked capability call) from claiming a request that is actually
54
- // asking to be RANKED — that's the goal-reasoner's job (src/router/goal-reasoner.mjs's
55
- // keystone argmax over a declared priorityTopic), not a flat listing's.
24
+ // A ranking/superlative cue ("most", "biggest", …), reusing ask.mjs's own vocabulary
25
+ // (SUPERLATIVE_EXTREMES). Used below to keep a flat imperative frame from claiming a
26
+ // request that's actually asking to be ranked that's the goal-reasoner's job.
56
27
  const SUPERLATIVE_RE = new RegExp(
57
28
  `\\b(?:${Object.keys(SUPERLATIVE_EXTREMES).map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\s+/g, "\\s+")).join("|")})\\b`,
58
29
  "i",
59
30
  );
60
31
 
61
32
  // ---- the ask-kind -> epistemic-topic MAPPING (the Stage-1 core) --------------
62
- // Keyed `${shape}:${kind}` off parseQuery's simple-clause output. The VALUE is
63
- // the epistemic TOPIC a capability's add-effect must achieve; backwardChain then
64
- // unifies that topic with the registry (capabilities-as-facts). Every entry's
65
- // topic MUST be achievable by exactly one registered capability (the bidirectional
66
- // conformance test proves it). `arg` is the parameter grain the object binds to.
33
+ // Keyed `${shape}:${kind}` off parseQuery's output. Every topic must be achievable by
34
+ // exactly one registered capability (the bidirectional conformance test proves it).
67
35
  export const NL_INTENTS = Object.freeze({
68
36
  "reverse:calls": { topic: "callers", arg: "symbol" }, // "which fns call X" -> callers of X
69
37
  "forward:calls": { topic: "callees", arg: "symbol" }, // "what does X call" -> callees of X
@@ -78,11 +46,8 @@ export const NL_INTENTS = Object.freeze({
78
46
  });
79
47
 
80
48
  // ---- ask-vocab RELATION kinds with NO capability — the HONEST ceiling --------
81
- // Every ask-vocab.mjs RELATIONS key must be either mapped (above) or listed here
82
- // with a reason (the bidirectional conformance test enforces the partition no
83
- // silent gap). These are relations tmct's grammar SPEAKS but the read-only
84
- // graph-query registry has no operator for: routing them anywhere would be a
85
- // mis-route, so the resolver REFUSES (never a guess).
49
+ // Every ask-vocab.mjs RELATIONS key must be either mapped (above) or listed here with a
50
+ // reason (the conformance test enforces the partition). Refuse rather than mis-route.
86
51
  export const UNMAPPED_KINDS = Object.freeze({
87
52
  imports: "no importer/imports query tool in the registry (there is no tmct_imports); refusing beats mis-routing to calls",
88
53
  uses: "a query-side UNION (imports+calls+callsSymbol) with no single capability; a router that must emit ONE call cannot honour it — refuse",
@@ -90,43 +55,20 @@ export const UNMAPPED_KINDS = Object.freeze({
90
55
  });
91
56
 
92
57
  // ---- capabilities the NL surface cannot reach today (named, not accidental) ---
93
- // A declared capability with no NL/command/frame path is a ROUTING GAP. The
94
- // conformance test FAILS on an untagged gap; a genuinely-unreachable cap must be
95
- // tagged HERE with the Stage it needs, so the ceiling is honest rather than a
96
- // silent low-completion refuse. (Coordinator reinforcement 2.)
97
- //
98
- // EMPTY as of Stage 2. tmct_calls — the raw call-edge dump that USED to sit here —
99
- // is now reached by a DEDICATED imperative frame keyed on the "call edges / call
100
- // graph / outgoing calls of X" phrasings the relational grammar does NOT carry
101
- // (see FRAMES below). The collision the old tag named is real, so the frame does
102
- // NOT touch the relational "call" verb (that still routes callers/callees); it
103
- // opens a SECOND, distinct surface that names the edge-dump grain explicitly. With
104
- // it every declared capability is NL/command/frame-reachable — the ceiling is
105
- // genuinely empty, not a silenced gap. (The conformance test enforces both
106
- // directions: an over-claimed tag would now fail, since tmct_calls IS reachable.)
58
+ // A declared capability with no NL/command/frame path is a routing gap and must be tagged
59
+ // here with the reason. Every capability is currently reachable, so this is empty.
107
60
  export const NOT_NL_REACHABLE = Object.freeze({});
108
61
 
109
- // ---- imperative intent FRAMES (Stage 2 — fills what the relational grammar and
110
- // the command register both miss). regex -> { topic, arg | noArg }. `arg` names
111
- // the parameter grain; the entity is pulled by extractEntity (or, for search, the
112
- // residual query text). Ordered: first match wins. Every frame's topic is
113
- // backward-chained to a capability just like an NL intent, so the frame table
114
- // adds PHRASINGS, never a new routing path. ----
62
+ // ---- imperative intent FRAMES (fills what the relational grammar and command register
63
+ // both miss). regex -> { topic, arg | noArg }. Ordered: first match wins.
115
64
  export const FRAMES = Object.freeze([
116
- // skipIfSuperlative: "untested" is a flat listing (which achieves it says
117
- // nothing about RANK). A request carrying a superlative cue ("what MOST
118
- // needs a test") is asking to be ranked — that's the goal-reasoner's
119
- // keystone-argmax job (declared priorityTopic:"impact"), not this frame's.
120
- // Skipping here lets the request fall through to an honest C1 refuse, which
121
- // driver-goal.mjs escalates to the C2 meta-loop that already ranks.
65
+ // skipIfSuperlative: a request carrying a superlative cue ("what MOST needs a test") is
66
+ // asking to be ranked the goal-reasoner's job, not this flat listing's.
122
67
  { re: /\buntested\b|\bwithout\s+(?:a\s+)?tests?\b|\bhas\s+no\s+tests?\b|\bneeds?\s+(?:a\s+)?tests?\b/i, topic: "untested", noArg: true, skipIfSuperlative: true },
123
68
  { re: /\bblast\s*radius\b|\bimpacts?\b|\bimpacted\b|what\s+(?:a\s+)?change.*(?:reach|affect|touch)|what\s+(?:depends?\s+on|dependents?)\b/i, topic: "impact", arg: "module" },
124
- // tmct_calls (Stage 2 — the reachability win): the RAW call-edge dump, a grain
125
- // the relational "call" verb collides with (which routes callers/callees). This
126
- // frame does NOT use the bare verb — it keys on the EXPLICIT edge-dump nouns
127
- // ("call edges", "call graph", "outgoing calls of X") the relational grammar
128
- // never emits, so it opens a distinct surface without touching callers/callees.
129
- // FIRST so its explicit phrasing wins before the callees/callers verb frames.
69
+ // tmct_calls: the RAW call-edge dump, a grain the relational "call" verb collides with.
70
+ // Keys on explicit edge-dump nouns ("call edges", "call graph") so it opens a distinct
71
+ // surface without touching callers/callees. FIRST so it wins before the verb frames below.
130
72
  { re: /\bcall[\s-]*edges?\b|\bcall[\s-]*graph\b|\boutgoing\s+calls?\b|\bcall[\s-]*sites?\s+(?:of|in|out|from)\b/i, topic: "calls", arg: "symbol" },
131
73
  { re: /\bcallees?\b|wh(?:at|o)\s+does\s+\S+\s+call\b/i, topic: "callees", arg: "symbol" },
132
74
  { re: /\bcallers?\b|who\s+calls\b|what\s+calls\b/i, topic: "callers", arg: "symbol" },
@@ -144,9 +86,7 @@ export const FRAMES = Object.freeze([
144
86
  // ---- backward chaining (the SLD/Datalog step) --------------------------------
145
87
 
146
88
  /** Backward-chain a goal `(knows <topic> ?of)` to the registered capability whose
147
- * add-effect ACHIEVES it. Pure over the registry; returns the capability or null.
148
- * This is the whole selection primitive — a capability is chosen ONLY because its
149
- * declared effect unifies with the request's epistemic goal, never by name. */
89
+ * add-effect achieves it. Pure over the registry; returns the capability or null. */
150
90
  export function backwardChain(topic) {
151
91
  for (const cap of capabilities()) {
152
92
  if (effectsOf(cap.name).add.some((e) => e.topic === topic)) return cap;
@@ -154,9 +94,8 @@ export function backwardChain(topic) {
154
94
  return null;
155
95
  }
156
96
 
157
- // The stopword set for the imperative-frame entity extractor (Stage-2 slot
158
- // filling). Deliberately generous: a wrong pick is caught by the resolveObject
159
- // miss -> honest refuse, never emitted.
97
+ // Stopwords for the imperative-frame entity extractor. Deliberately generous: a wrong
98
+ // pick is caught by the resolveObject miss -> honest refuse, never emitted.
160
99
  const STOP = new Set([
161
100
  "what", "whats", "which", "who", "whom", "does", "do", "did", "is", "are", "the", "a", "an",
162
101
  "of", "for", "to", "in", "on", "by", "me", "us", "tell", "about", "show", "list", "give", "get",
@@ -167,8 +106,6 @@ const STOP = new Set([
167
106
  "export", "exports", "history", "commit", "commits", "signature", "search", "find", "look",
168
107
  "module", "modules", "class", "classes", "function", "functions", "symbol", "symbols",
169
108
  "untested", "blast", "radius", "change", "changes", "changing", "reach", "reaches", "affect", "affects",
170
- // Stage-2 edge-dump + imperative-verb tokens (tmct_calls frame + explain/outgoing
171
- // phrasings): none names an entity, so keep them out of the slot-filler's pool.
172
109
  "explain", "edge", "edges", "graph", "outgoing", "site", "sites", "invoke", "invokes", "run", "runs", "execute", "executes",
173
110
  ]);
174
111
 
@@ -245,9 +182,8 @@ export function commandCapability(request, declaredNames) {
245
182
 
246
183
  // ---- the full single-call resolver (async — binds + grounds) -----------------
247
184
 
248
- /** Build the glass-box proof chain for a grounded single call: its preconditions
249
- * (graphLoaded + resolves(param) with the BOUND value + any-present) then the
250
- * epistemic add-effect. Dispatch has SUCCEEDED, so `resolves` steps are ok. */
185
+ /** Build the glass-box proof chain for a grounded single call: its preconditions then the
186
+ * epistemic add-effect. Dispatch has succeeded, so `resolves` steps are ok. */
251
187
  export function proofFor(name, input) {
252
188
  const steps = [];
253
189
  for (const pre of preconditionsOf(name)) {
@@ -261,14 +197,9 @@ export function proofFor(name, input) {
261
197
 
262
198
  const REFUSE = (why, extra) => ({ selected: null, refused: true, reason: why, ...(extra || {}) });
263
199
 
264
- /** PLAN_BREADTH_FIRST_NLU.md §4 — breadth-first ambiguity, read-only capabilities
265
- * ONLY (every registered capability is `readOnly:true` with an empty delete-list,
266
- * so dispatching the SAME tool once per tied candidate carries no double-write
267
- * risk). Runs the bound call for each candidate in `pool` (capped, matching the
268
- * reason string's own display cap) and returns `[{candidate, result}, ...]`, or
269
- * undefined when there is no dispatcher to run it with (`execute:false` or no
270
- * `ctx.dispatch` — e.g. a structural-only / planning caller). Never throws: a
271
- * per-candidate dispatch failure is just an honest miss for that one candidate. */
200
+ /** Breadth-first ambiguity: dispatches the SAME tool once per tied candidate (safe since
201
+ * every registered capability is read-only). Returns `[{candidate, result}, ...]`, or
202
+ * undefined when there is no dispatcher to run it with. */
272
203
  async function dispatchEachCandidate(pool, capName, arg, ctx, execute) {
273
204
  if (!execute || !ctx.dispatch) return undefined;
274
205
  const results = [];
@@ -280,38 +211,25 @@ async function dispatchEachCandidate(pool, capName, arg, ctx, execute) {
280
211
  }
281
212
 
282
213
  /** Select a capability for a request and BIND its arguments — the full resolver.
283
- * Order: command register -> NL parse -> imperative frame. On a bound selection
284
- * it delegates entity binding to ctx.resolve (resolveObject) and, unless
285
- * `execute:false`, grounds it via ctx.dispatch. Returns
214
+ * Order: command register -> NL parse -> imperative frame. Delegates entity binding to
215
+ * ctx.resolve and, unless `execute:false`, grounds it via ctx.dispatch. Returns
286
216
  * { selected:{name,input}, proof, why, resolved, observed? } — a grounded call
287
217
  * { selected:null, refused:true, reason, candidateResults? } — an honest refusal
288
- * An ambiguous-term refusal stays `refused:true` (never a guess at which
289
- * candidate is "the" one) but, when a dispatcher is available, ADDITIONALLY
290
- * carries `candidateResults`: the SAME capability dispatched once per tied
291
- * candidate, so a machine caller gets both the honest "still ambiguous" signal
292
- * and every candidate's real answer (PLAN_BREADTH_FIRST_NLU.md §4).
293
- * NEVER emits an ungrounded / ambiguous / undeclared call. */
218
+ * Never emits an ungrounded / ambiguous / undeclared call. */
294
219
  export async function resolveOne(request, declaredNames, ctx, { execute = true } = {}) {
295
220
  const declared = new Set(declaredNames);
296
221
 
297
- // 1. command register (exact) 2. NL parse 3. imperative frame. An NL-parse
298
- // REFUSAL is NOT terminal: a shape with no relational operator ("what is the
299
- // impact of X", "list what covers X") can still be an imperative FRAME hit,
300
- // so we fall through and only surface the NL reason if the frame misses too.
222
+ // An NL-parse refusal is not terminal: an imperative FRAME may still hit, so we fall
223
+ // through and only surface the NL reason if the frame misses too.
301
224
  let pick = commandCapability(request, declared);
302
225
  let nlRefuse = null;
303
226
  let nlUndeclared = null;
304
227
  if (!pick) {
305
228
  const mapped = mapParse(parseQuery(request));
306
229
  if (mapped && !mapped.refuse) {
307
- // An NL parse that selects a DECLARED capability is the Stage-1 answer. One
308
- // that selects an OUT-OF-SET capability is NOT terminal (Stage-2 widening):
309
- // an imperative FRAME may still reach a DECLARED capability for the SAME
310
- // request (e.g. keyword-spot mis-routes "outgoing calls of X" toward an
311
- // out-of-set callers/callees, but the calls-frame reaches the declared
312
- // tmct_calls). Hold the out-of-set name and fall through; surface it only if
313
- // the frame misses too. This can only turn a refuse into a grounded DECLARED
314
- // call — the declared/hallucination gates below still apply, never a guess.
230
+ // An out-of-set NL selection isn't terminal: an imperative FRAME may still reach a
231
+ // declared capability for the same request (e.g. the calls-frame reaches tmct_calls
232
+ // where keyword-spotting mis-routes toward out-of-set callers/callees).
315
233
  if (declared.has(mapped.name)) pick = mapped;
316
234
  else nlUndeclared = mapped.name;
317
235
  } else if (mapped && mapped.refuse) nlRefuse = mapped.reason;
@@ -325,14 +243,13 @@ export async function resolveOne(request, declaredNames, ctx, { execute = true }
325
243
  let why = pick.why ?? [];
326
244
  if (!declared.has(pick.name)) return REFUSE(`selected ${pick.name} but it is not in the declared toolset`);
327
245
 
328
- // build the bound input. A command pick already carries a bound input; an NL /
329
- // frame pick carries a raw term that we BIND via resolveObject (the oracle).
246
+ // A command pick already carries a bound input; an NL/frame pick carries a raw term
247
+ // we bind via resolveObject.
330
248
  let input = pick.input ? { ...pick.input } : {};
331
249
  let resolved = null;
332
250
  if (!pick.input && !pick.noArg) {
333
251
  const term = String(pick.term || "").trim();
334
252
  if (!term) return REFUSE(`the ${pick.topic} intent named no entity to bind`);
335
- // DELEGATE binding to resolveObject — the resolves(param,as) precondition.
336
253
  const r = ctx.resolve ? ctx.resolve(term) : { match: { label: term }, ambiguous: false };
337
254
  if (!r || !r.match) return REFUSE(`"${term}" does not resolve to any graph entity (honest miss)`);
338
255
  if (r.ambiguous) {
@@ -346,11 +263,9 @@ export async function resolveOne(request, declaredNames, ctx, { execute = true }
346
263
  }
347
264
 
348
265
  const call = { name: pick.name, input };
349
- // the same zero-hallucination gate the grader enforces — self-check before emit.
350
266
  const problems = hallucinationsIn(call, [...declared]);
351
267
  if (problems.length) return REFUSE(`bound call did not validate: ${problems.map((p) => p.reason).join(",")}`);
352
268
 
353
- // ground it: a ToolError (unresolvable entity) is an honest miss -> refuse.
354
269
  if (execute && ctx.dispatch) {
355
270
  const res = await ctx.dispatch(pick.name, input);
356
271
  if (!res.ok) return REFUSE(`unresolvable at dispatch: ${res.error}`);