@polycode-projects/the-mechanical-code-talker 1.9.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +441 -217
- package/bin/tmct.mjs +126 -1
- package/corpus/seon/README.md +1 -2
- 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 -2874
- 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
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// src/router/drive.mjs — the product-facing drive of the capability router: the
|
|
2
|
+
// piece that turns a real English request into a real, executed answer over a
|
|
3
|
+
// real repo graph. registry/resolver/planner/guardrail/goal-reasoner/
|
|
4
|
+
// call-validator are all pure, deterministic decision machinery — this module
|
|
5
|
+
// is the thin, stateful shell around them that a CLI or chat surface calls:
|
|
6
|
+
// build a { dispatch, resolve, graph } context against the repo's actual code
|
|
7
|
+
// graph, then run a request through resolver -> planner -> goal-reasoner.
|
|
8
|
+
//
|
|
9
|
+
// Single-shot -> the resolver. A compound "... then ..."/"if ..."/"of the ...,
|
|
10
|
+
// which are ..." request -> the planner, HTN-decomposed into an ordered call
|
|
11
|
+
// sequence with a POP causal-link proof chain, then folded into ONE composed
|
|
12
|
+
// answer via the same set-algebra the HTN method names (relative-filter ->
|
|
13
|
+
// intersect; conditional -> fallback/guard). A request neither stage can
|
|
14
|
+
// ground escalates to the closed-world goal-reasoner — a maintenance-invariant
|
|
15
|
+
// deduction (coverage-gap / cochange-risk), never a keyword guess. Anything
|
|
16
|
+
// none of the three grounds is an honest refuse, the same "grounded or an
|
|
17
|
+
// honest miss" contract as every other tmct answer path.
|
|
18
|
+
|
|
19
|
+
import { resolveOne } from "./resolver.mjs";
|
|
20
|
+
import { plan, isMultiStep, decompose, MAX_STEPS } from "./planner.mjs";
|
|
21
|
+
import { goalReason } from "./goal-reasoner.mjs";
|
|
22
|
+
import { capabilities } from "./registry.mjs";
|
|
23
|
+
import { intersect, fallbackIfEmpty, guardIfEmpty, memberIndividuals, membersReaching, resultSetOf } from "./results.mjs";
|
|
24
|
+
import { resolveObject } from "../ask.mjs";
|
|
25
|
+
import { parseEntities } from "../codegraph.mjs";
|
|
26
|
+
import { dispatchTool } from "../server.mjs";
|
|
27
|
+
import { ToolError } from "../config.mjs";
|
|
28
|
+
import * as defaultSource from "../source.mjs";
|
|
29
|
+
|
|
30
|
+
export const ROUTER_DRIVER = "resolver-0.8.0";
|
|
31
|
+
export const GOAL_DRIVER = "goal-0.8.1";
|
|
32
|
+
|
|
33
|
+
/** Every registered capability's name — the default declared toolset for a
|
|
34
|
+
* caller that doesn't want to hand-pick a subset. */
|
|
35
|
+
export function declaredCapabilityNames() {
|
|
36
|
+
return capabilities().map((c) => c.name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const CALLABLE_MEMBER_CLASSES = new Set(["Method", "Function"]);
|
|
40
|
+
const refuse = (why, driver) => ({ calls: [], refused: true, terminated: true, proof: [], driver, why });
|
|
41
|
+
|
|
42
|
+
/** Execute the MEMBER-FILTER HTN method ("which methods of X end up calling Y")
|
|
43
|
+
* — the per-member hop the single-shot resolver cannot emit on its own. Step 1
|
|
44
|
+
* grounds members(X) via resolveOne; then, per CALLABLE member (sorted, bounded
|
|
45
|
+
* by the planner's MAX_STEPS budget), one tmct_callees hop. The fold is
|
|
46
|
+
* membersReaching (the bounded transitive callsSymbol closure), computed over
|
|
47
|
+
* the graph, never parsed from text. Honest refuses: no tmct_callees in the
|
|
48
|
+
* declared toolset, an unbindable filter target, an over-budget member list. */
|
|
49
|
+
async function memberFilterDrive(request, tools, ctx, segments) {
|
|
50
|
+
const [setSeg, filterSeg] = segments;
|
|
51
|
+
if (!tools.includes("tmct_callees")) {
|
|
52
|
+
return refuse("the member-filter reachability hop needs tmct_callees, which is not in the declared toolset — refusing the hop rather than guessing the fold", ROUTER_DRIVER);
|
|
53
|
+
}
|
|
54
|
+
const t = ctx.resolve ? ctx.resolve(String(filterSeg.text)) : null;
|
|
55
|
+
if (!t || !t.match || t.ambiguous) {
|
|
56
|
+
return refuse(`the member-filter target "${filterSeg.text}" does not bind to one graph entity (honest miss)`, ROUTER_DRIVER);
|
|
57
|
+
}
|
|
58
|
+
const target = t.match;
|
|
59
|
+
|
|
60
|
+
const r1 = await resolveOne(setSeg.text, tools, ctx, { execute: true });
|
|
61
|
+
if (r1.refused) return refuse(`sub-goal 1 ("${setSeg.text}") did not resolve: ${r1.reason}`, ROUTER_DRIVER);
|
|
62
|
+
const classInd = r1.resolved;
|
|
63
|
+
if (!classInd) return refuse(`sub-goal 1 ("${setSeg.text}") grounded no class entity to enumerate`, ROUTER_DRIVER);
|
|
64
|
+
|
|
65
|
+
const calls = [r1.selected];
|
|
66
|
+
const proof = [{ step: "causal-link", producer: "graph", condition: classInd.label, consumer: `step-1:${r1.selected.name}`, role: "action", ok: true }];
|
|
67
|
+
for (const s of r1.proof) proof.push({ ...s, ofStep: 1 });
|
|
68
|
+
const why = [
|
|
69
|
+
`HTN method: member-filter — enumerate members(${classInd.label}), hop tmct_callees per callable member, fold by bounded transitive reach of ${target.label}`,
|
|
70
|
+
...(r1.why || []).map((w) => `[1] ${w}`),
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const members = memberIndividuals(ctx.graph, classInd)
|
|
74
|
+
.filter((m) => CALLABLE_MEMBER_CLASSES.has(m.class))
|
|
75
|
+
.sort((a, b) => String(a.label).localeCompare(String(b.label)));
|
|
76
|
+
if (1 + members.length > MAX_STEPS) {
|
|
77
|
+
return refuse(`member-filter needs ${1 + members.length} steps (> budget ${MAX_STEPS}) — escalate`, ROUTER_DRIVER);
|
|
78
|
+
}
|
|
79
|
+
for (let i = 0; i < members.length; i += 1) {
|
|
80
|
+
const m = members[i];
|
|
81
|
+
const res = await ctx.dispatch("tmct_callees", { symbol: m.label });
|
|
82
|
+
if (!res.ok) return refuse(`the callees hop for ${m.label} did not ground: ${res.error}`, ROUTER_DRIVER);
|
|
83
|
+
calls.push({ name: "tmct_callees", input: { symbol: m.label } });
|
|
84
|
+
proof.push({ step: "causal-link", producer: "step-1", condition: m.label, consumer: `step-${i + 2}:tmct_callees`, role: "member-filter", ok: true });
|
|
85
|
+
why.push(`[${i + 2}] callees hop over ${m.label} (a member step 1 produced)`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const composed = membersReaching(ctx.graph, classInd, target.label);
|
|
89
|
+
return {
|
|
90
|
+
calls, refused: false, terminated: true, proof, driver: ROUTER_DRIVER, why, composed,
|
|
91
|
+
observed: `plan(member-filter): ${calls.map((c) => c.name).join(" -> ")} => {${composed.join(", ")}}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Fold a multi-step plan's EXECUTED, threaded step results into one composed
|
|
96
|
+
* answer, choosing the operator from the router's HTN method:
|
|
97
|
+
* - relative-filter ("of the <set>, which are <Y>") -> set INTERSECTION
|
|
98
|
+
* - conditional "... <action> instead" -> FALLBACK-if-empty
|
|
99
|
+
* - conditional "if <check>, <action>" -> GUARD-if-empty
|
|
100
|
+
* - sequence (independent "... then ...") -> no single composed set
|
|
101
|
+
* Returns a label array, or null when the method composes nothing (sequence). */
|
|
102
|
+
async function composeResult(request, calls, ctx) {
|
|
103
|
+
const { method } = decompose(request);
|
|
104
|
+
if (method !== "relative-filter" && method !== "conditional") return null;
|
|
105
|
+
if (calls.length < 2) return null; // a relaxed/short plan cannot fold — result stays unmet
|
|
106
|
+
|
|
107
|
+
const sets = [];
|
|
108
|
+
for (const c of calls) {
|
|
109
|
+
const res = await ctx.dispatch(c.name, c.input || {});
|
|
110
|
+
sets.push(res.ok && Array.isArray(res.result) ? res.result : []);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (method === "relative-filter") return intersect(sets[0], sets[1]);
|
|
114
|
+
return /\binstead\b/i.test(request) ? fallbackIfEmpty(sets[0], sets[1]) : guardIfEmpty(sets[0], sets[1]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Single-shot via the resolver, compound via the planner (+ result
|
|
118
|
+
* composition). Never escalates to the goal-reasoner on its own — see
|
|
119
|
+
* runCapabilityPlan for the full resolver/planner -> goal-reasoner chain. */
|
|
120
|
+
export async function runResolverPlan(request, tools, ctx) {
|
|
121
|
+
const d = decompose(request);
|
|
122
|
+
if (d.method === "member-filter") return memberFilterDrive(request, tools, ctx, d.segments);
|
|
123
|
+
|
|
124
|
+
if (isMultiStep(request)) {
|
|
125
|
+
const loop = await plan(request, tools, ctx, { driver: ROUTER_DRIVER });
|
|
126
|
+
if (loop.refused) return loop;
|
|
127
|
+
const composed = await composeResult(request, loop.calls, ctx);
|
|
128
|
+
return composed === null ? loop : { ...loop, composed };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const r = await resolveOne(request, tools, ctx, { execute: true });
|
|
132
|
+
if (r.refused) {
|
|
133
|
+
return { calls: [], refused: true, terminated: true, proof: [], driver: ROUTER_DRIVER, why: r.reason };
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
calls: [r.selected], refused: false, terminated: true, proof: r.proof,
|
|
137
|
+
driver: ROUTER_DRIVER, why: r.why, observed: r.observed,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The full drive: resolver/planner first; a refusal there escalates to the
|
|
142
|
+
* closed-world goal-reasoner. Mirrors agentbench's driver-resolver.mjs +
|
|
143
|
+
* driver-goal.mjs composition, with no agentbench/ dependency (agentbench/
|
|
144
|
+
* is dev-only, never shipped). Returns a loopResult:
|
|
145
|
+
* `{ calls, refused, terminated, proof, why, driver, composed?, observed? }`. */
|
|
146
|
+
export async function runCapabilityPlan(request, tools, ctx) {
|
|
147
|
+
const c1 = await runResolverPlan(request, tools, ctx);
|
|
148
|
+
if (!c1.refused) return c1;
|
|
149
|
+
const c2 = await goalReason(request, tools, ctx, { driver: GOAL_DRIVER });
|
|
150
|
+
// Both stages refused: carry the resolver/planner's own reason alongside the
|
|
151
|
+
// goal-reasoner's so a caller can show why the direct route AND the
|
|
152
|
+
// maintenance-goal route both declined,
|
|
153
|
+
// rather than only the last (often less specific) of the two.
|
|
154
|
+
return c2.refused ? { ...c2, c1Why: c1.why } : c2;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Build a real `{ dispatch, resolve, graph, config }` context against a repo's
|
|
158
|
+
* actual code graph — the router's window onto the live tool layer. `dispatch`
|
|
159
|
+
* runs the SAME dispatchTool the chat/CLI/server surfaces call, and computes
|
|
160
|
+
* the structured `result` label-set (results.mjs's resultSetOf) a multi-step
|
|
161
|
+
* plan folds. `resolve` delegates to resolveObject over the SAME parsed graph,
|
|
162
|
+
* so resolve() and dispatch() always agree on what an entity resolves to.
|
|
163
|
+
* Pass an already-parsed `graph` (e.g. a chat session's own) to skip reloading
|
|
164
|
+
* it — mirrors the config -> source.fetchEntities -> parseEntities chain
|
|
165
|
+
* dispatchTool runs internally, so a passed-in graph must come from that same
|
|
166
|
+
* chain to stay consistent. */
|
|
167
|
+
export async function buildCapabilityPlanCtx({ config, source = defaultSource, tel = null, graph = null } = {}) {
|
|
168
|
+
const g = graph || parseEntities(await source.fetchEntities(config));
|
|
169
|
+
const resolve = (term) => resolveObject(g, term);
|
|
170
|
+
const dispatch = async (name, input) => {
|
|
171
|
+
try {
|
|
172
|
+
const text = await dispatchTool(name, input, { config, source, tel });
|
|
173
|
+
const primary = input && (input.symbol ?? input.module ?? input.class ?? input.query);
|
|
174
|
+
const resolved = primary ? resolve(String(primary)).match : null;
|
|
175
|
+
const result = resultSetOf(g, name, input, resolved);
|
|
176
|
+
return { ok: true, text, resolved, result };
|
|
177
|
+
} catch (e) {
|
|
178
|
+
if (e instanceof ToolError) return { ok: false, error: e.message };
|
|
179
|
+
throw e;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
return { dispatch, resolve, graph: g, config };
|
|
183
|
+
}
|