@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.
- package/README.md +441 -202
- package/bin/tmct.mjs +126 -1
- package/package.json +4 -2
- package/src/answer-variants.mjs +8 -36
- package/src/ask-browser-entry.mjs +5 -23
- package/src/ask-browser.bundle.js +1 -2
- package/src/ask-nlp.mjs +9 -23
- package/src/ask-vocab.mjs +139 -589
- package/src/ask.mjs +627 -1729
- package/src/chat.mjs +1684 -2872
- package/src/cli-args.mjs +14 -28
- package/src/codegraph.mjs +236 -644
- package/src/completions/complete.mjs +18 -62
- package/src/completions/graph-adapter.mjs +14 -60
- package/src/completions/group.mjs +12 -68
- package/src/completions/infer.mjs +38 -126
- package/src/completions/prune.mjs +17 -70
- package/src/completions/rank.mjs +16 -69
- package/src/completions/search.mjs +8 -31
- package/src/concept.mjs +32 -88
- package/src/conformance.mjs +11 -15
- package/src/corpus/conceptnet.mjs +31 -89
- package/src/corpus/templates.mjs +19 -45
- package/src/corpus/unknown-ingest.mjs +31 -92
- package/src/embed.mjs +10 -22
- package/src/extensions.mjs +50 -154
- package/src/finish.mjs +35 -91
- package/src/grammar/ace.mjs +16 -40
- package/src/grammar/assert.mjs +1 -1
- package/src/grammar/lexicon-core.json +1 -1
- package/src/grammar/lexicon.mjs +9 -27
- package/src/graph-merge.mjs +2 -3
- package/src/hash.mjs +6 -14
- package/src/index.mjs +6 -10
- package/src/init.mjs +38 -125
- package/src/interpret/fuzzy.mjs +10 -29
- package/src/interpret/merge.mjs +9 -27
- package/src/interpret/normalize.mjs +137 -585
- package/src/interpret/pipeline.mjs +23 -71
- package/src/interpret/strategies/ace.mjs +7 -31
- package/src/interpret/strategies/constructions.mjs +14 -41
- package/src/interpret/strategies/grammar.mjs +21 -60
- package/src/interpret/strategies/keywords.mjs +42 -131
- package/src/interpret/strategies/noise-strip.mjs +18 -89
- package/src/memory/bias.mjs +11 -54
- package/src/memory/blocks.mjs +18 -69
- package/src/memory/core.mjs +171 -591
- package/src/memory/fold.mjs +0 -0
- package/src/memory/inspect.mjs +7 -25
- package/src/memory/shacl.mjs +10 -39
- package/src/memory/trust.mjs +26 -127
- package/src/memory-ask-browser-entry.mjs +7 -30
- package/src/memory-ask-browser.bundle.js +1 -1
- package/src/paraphrase.mjs +20 -53
- package/src/planning.mjs +15 -157
- package/src/prose-nlp.mjs +4 -17
- package/src/prose.mjs +19 -67
- package/src/providers/bootstrap.mjs +1 -2
- package/src/providers/fixture.mjs +1 -2
- package/src/providers/graph-service.mjs +28 -59
- package/src/repository-interface.mjs +6 -8
- package/src/router/drive.mjs +183 -0
- package/src/router/goal-reasoner.mjs +66 -231
- package/src/router/guardrail.mjs +20 -58
- package/src/router/planner.mjs +15 -46
- package/src/router/registry.mjs +13 -43
- package/src/router/resolver.mjs +46 -131
- package/src/router/results.mjs +231 -0
- package/src/schema-docs.mjs +10 -27
- package/src/server-http.mjs +10 -19
- package/src/server.mjs +22 -28
- package/src/sessions.mjs +15 -30
- package/src/source-slice.mjs +5 -7
- package/src/source.mjs +10 -20
- package/src/syllogise.mjs +187 -575
- package/src/telemetry.mjs +3 -3
- package/src/toml-config.mjs +4 -4
- package/src/tui/app.mjs +9 -19
- package/src/viz.mjs +66 -123
- package/src/wink-model.mjs +10 -24
package/src/router/planner.mjs
CHANGED
|
@@ -1,36 +1,13 @@
|
|
|
1
|
-
// src/router/planner.mjs —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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 —
|
|
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
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
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.
|
|
133
|
-
*
|
|
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
|
-
//
|
|
165
|
-
//
|
|
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 });
|
package/src/router/registry.mjs
CHANGED
|
@@ -1,34 +1,14 @@
|
|
|
1
|
-
// src/router/registry.mjs —
|
|
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
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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
|
|
212
|
-
//
|
|
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
|
-
//
|
|
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",
|
package/src/router/resolver.mjs
CHANGED
|
@@ -1,43 +1,17 @@
|
|
|
1
|
-
// src/router/resolver.mjs —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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
|
-
//
|
|
14
|
-
//
|
|
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", …)
|
|
51
|
-
//
|
|
52
|
-
//
|
|
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
|
|
63
|
-
//
|
|
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
|
-
//
|
|
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
|
|
94
|
-
//
|
|
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 (
|
|
110
|
-
//
|
|
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:
|
|
117
|
-
//
|
|
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
|
|
125
|
-
//
|
|
126
|
-
//
|
|
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
|
|
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
|
-
//
|
|
158
|
-
//
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
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.
|
|
284
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
298
|
-
//
|
|
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
|
|
308
|
-
//
|
|
309
|
-
//
|
|
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
|
-
//
|
|
329
|
-
//
|
|
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}`);
|