@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.
Files changed (81) hide show
  1. package/README.md +441 -217
  2. package/bin/tmct.mjs +126 -1
  3. package/corpus/seon/README.md +1 -2
  4. package/package.json +4 -2
  5. package/src/answer-variants.mjs +8 -36
  6. package/src/ask-browser-entry.mjs +5 -23
  7. package/src/ask-browser.bundle.js +1 -2
  8. package/src/ask-nlp.mjs +9 -23
  9. package/src/ask-vocab.mjs +139 -589
  10. package/src/ask.mjs +627 -1729
  11. package/src/chat.mjs +1684 -2874
  12. package/src/cli-args.mjs +14 -28
  13. package/src/codegraph.mjs +236 -644
  14. package/src/completions/complete.mjs +18 -62
  15. package/src/completions/graph-adapter.mjs +14 -60
  16. package/src/completions/group.mjs +12 -68
  17. package/src/completions/infer.mjs +38 -126
  18. package/src/completions/prune.mjs +17 -70
  19. package/src/completions/rank.mjs +16 -69
  20. package/src/completions/search.mjs +8 -31
  21. package/src/concept.mjs +32 -88
  22. package/src/conformance.mjs +11 -15
  23. package/src/corpus/conceptnet.mjs +31 -89
  24. package/src/corpus/templates.mjs +19 -45
  25. package/src/corpus/unknown-ingest.mjs +31 -92
  26. package/src/embed.mjs +10 -22
  27. package/src/extensions.mjs +50 -154
  28. package/src/finish.mjs +35 -91
  29. package/src/grammar/ace.mjs +16 -40
  30. package/src/grammar/assert.mjs +1 -1
  31. package/src/grammar/lexicon-core.json +1 -1
  32. package/src/grammar/lexicon.mjs +9 -27
  33. package/src/graph-merge.mjs +2 -3
  34. package/src/hash.mjs +6 -14
  35. package/src/index.mjs +6 -10
  36. package/src/init.mjs +38 -125
  37. package/src/interpret/fuzzy.mjs +10 -29
  38. package/src/interpret/merge.mjs +9 -27
  39. package/src/interpret/normalize.mjs +137 -585
  40. package/src/interpret/pipeline.mjs +23 -71
  41. package/src/interpret/strategies/ace.mjs +7 -31
  42. package/src/interpret/strategies/constructions.mjs +14 -41
  43. package/src/interpret/strategies/grammar.mjs +21 -60
  44. package/src/interpret/strategies/keywords.mjs +42 -131
  45. package/src/interpret/strategies/noise-strip.mjs +18 -89
  46. package/src/memory/bias.mjs +11 -54
  47. package/src/memory/blocks.mjs +18 -69
  48. package/src/memory/core.mjs +171 -591
  49. package/src/memory/fold.mjs +0 -0
  50. package/src/memory/inspect.mjs +7 -25
  51. package/src/memory/shacl.mjs +10 -39
  52. package/src/memory/trust.mjs +26 -127
  53. package/src/memory-ask-browser-entry.mjs +7 -30
  54. package/src/memory-ask-browser.bundle.js +1 -1
  55. package/src/paraphrase.mjs +20 -53
  56. package/src/planning.mjs +15 -157
  57. package/src/prose-nlp.mjs +4 -17
  58. package/src/prose.mjs +19 -67
  59. package/src/providers/bootstrap.mjs +1 -2
  60. package/src/providers/fixture.mjs +1 -2
  61. package/src/providers/graph-service.mjs +28 -59
  62. package/src/repository-interface.mjs +6 -8
  63. package/src/router/drive.mjs +183 -0
  64. package/src/router/goal-reasoner.mjs +66 -231
  65. package/src/router/guardrail.mjs +20 -58
  66. package/src/router/planner.mjs +15 -46
  67. package/src/router/registry.mjs +13 -43
  68. package/src/router/resolver.mjs +46 -131
  69. package/src/router/results.mjs +231 -0
  70. package/src/schema-docs.mjs +10 -27
  71. package/src/server-http.mjs +10 -19
  72. package/src/server.mjs +22 -28
  73. package/src/sessions.mjs +15 -30
  74. package/src/source-slice.mjs +5 -7
  75. package/src/source.mjs +10 -20
  76. package/src/syllogise.mjs +187 -575
  77. package/src/telemetry.mjs +3 -3
  78. package/src/toml-config.mjs +4 -4
  79. package/src/tui/app.mjs +9 -19
  80. package/src/viz.mjs +66 -123
  81. 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
+ }