@polycode-projects/the-mechanical-code-talker 0.8.1 → 0.9.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 +14 -0
- package/ROADMAP.md +113 -38
- package/corpus/seon/concepts.jsonl +42 -0
- package/data/templates/responses.jsonl +1 -1
- package/package.json +2 -1
- package/src/ask.mjs +574 -31
- package/src/chat.mjs +864 -43
- package/src/codegraph.mjs +109 -1
- package/src/grammar/lexicon-core.json +7 -0
- package/src/interpret/merge.mjs +16 -1
- package/src/interpret/normalize.mjs +245 -4
- package/src/memory/core.mjs +8 -1
- package/src/memory/fold.mjs +0 -0
- package/src/memory/trust.mjs +8 -4
- package/src/router/call-validator.mjs +45 -0
- package/src/router/goal-reasoner.mjs +148 -50
- package/src/router/guardrail.mjs +1 -1
- package/src/router/planner.mjs +22 -1
- package/src/router/resolver.mjs +1 -1
- package/src/router/set-algebra.mjs +31 -0
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
|
|
44
44
|
import { backwardChain, extractEntity } from "./resolver.mjs";
|
|
45
45
|
import { capabilityByName, effectsOf } from "./registry.mjs";
|
|
46
|
-
import { hallucinationsIn } from "
|
|
47
|
-
import { intersect } from "
|
|
46
|
+
import { hallucinationsIn } from "./call-validator.mjs";
|
|
47
|
+
import { intersect } from "./set-algebra.mjs";
|
|
48
48
|
|
|
49
49
|
// Hard OUTER-tick budget — the meta-loop runs at most this many ticks, then
|
|
50
50
|
// REFUSES (escalate). Independent of BDI convergence and of the monotone
|
|
@@ -55,8 +55,23 @@ export const MAX_TICKS = 16;
|
|
|
55
55
|
// ---- the DECLARED goal model (data, mirroring registry.mjs's STRIPS operators)
|
|
56
56
|
// A goal-rule is a maintenance INVARIANT over the graph, plus the epistemic
|
|
57
57
|
// sub-goals whose facts decide whether it is violated and the DECLARED priority
|
|
58
|
-
// that
|
|
58
|
+
// that settles ties in first-step arbitration. Growing this set is the "long-chain
|
|
59
59
|
// deduction library" the RFC flags — same discipline as syllogise's rule set.
|
|
60
|
+
//
|
|
61
|
+
// Each rule declares (pure frozen data, no code):
|
|
62
|
+
// focusClass — the entity class the scoped reading binds its focus to.
|
|
63
|
+
// modes — which deduced scopes the rule covers ("scoped" = a bound focus;
|
|
64
|
+
// "global" = whole-graph keystone arbitration). A rule whose
|
|
65
|
+
// sub-goals are all entity-scoped has no global reading.
|
|
66
|
+
// subGoals — the epistemic facts to gather, IN DECLARED ORDER (each
|
|
67
|
+
// backward-chains to a capability, exactly like an NL intent).
|
|
68
|
+
// compose — the declarative fold of the gathered facts into the scoped
|
|
69
|
+
// answer: intersect(a, b), each side naming a gathered topic,
|
|
70
|
+
// optionally bound to the focus (`of:"focus"`), optionally with
|
|
71
|
+
// the focus itself unioned in (`withFocus` — the change footprint).
|
|
72
|
+
// priorityTopic / coverageTopic — the global keystone arbitration keys
|
|
73
|
+
// (argmax |priority(m)| over the coverage-violating set).
|
|
74
|
+
// achieves — the meta-goal topic the composed answer achieves.
|
|
60
75
|
export const GOAL_RULES = Object.freeze([
|
|
61
76
|
Object.freeze({
|
|
62
77
|
id: "coverage-invariant",
|
|
@@ -65,8 +80,8 @@ export const GOAL_RULES = Object.freeze([
|
|
|
65
80
|
// closure) MUST have direct test coverage. A Module that is untested AND
|
|
66
81
|
// impactful VIOLATES it — an active goal to close the coverage gap.
|
|
67
82
|
invariant: "an impactful module must be tested",
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
focusClass: "Module",
|
|
84
|
+
modes: Object.freeze(["scoped", "global"]),
|
|
70
85
|
subGoals: Object.freeze(["impact", "untested"]),
|
|
71
86
|
// the DECLARED priority key for first-step arbitration: a violation's
|
|
72
87
|
// priority is its blast radius |impact(module)| — the wider the reach, the
|
|
@@ -74,9 +89,41 @@ export const GOAL_RULES = Object.freeze([
|
|
|
74
89
|
priorityTopic: "impact",
|
|
75
90
|
// the coverage predicate the invariant screens on.
|
|
76
91
|
coverageTopic: "untested",
|
|
92
|
+
// scoped fold: untested ∩ ({focus} ∪ impact(focus)) — the change footprint.
|
|
93
|
+
compose: Object.freeze({
|
|
94
|
+
op: "intersect",
|
|
95
|
+
a: Object.freeze({ topic: "untested" }),
|
|
96
|
+
b: Object.freeze({ topic: "impact", of: "focus", withFocus: true }),
|
|
97
|
+
names: "the change's untested footprint",
|
|
98
|
+
empty: "no coverage gap",
|
|
99
|
+
}),
|
|
77
100
|
// the meta-goal topic the composed answer achieves (backward-chained below).
|
|
78
101
|
achieves: "coverage-gap",
|
|
79
102
|
}),
|
|
103
|
+
Object.freeze({
|
|
104
|
+
id: "cochange-risk-invariant",
|
|
105
|
+
kind: "maintenance",
|
|
106
|
+
// INVARIANT: a module CHANGE-COUPLED with the focus (they historically land
|
|
107
|
+
// in the same commits) MUST have direct test coverage. A coupled module that
|
|
108
|
+
// is untested VIOLATES it — an active goal over the focus's coupling set.
|
|
109
|
+
invariant: "a module change-coupled with the focus must be tested",
|
|
110
|
+
focusClass: "Module",
|
|
111
|
+
// scoped ONLY: both sub-goals are read relative to a bound focus; there is
|
|
112
|
+
// no whole-graph keystone reading declared for change-coupling.
|
|
113
|
+
modes: Object.freeze(["scoped"]),
|
|
114
|
+
subGoals: Object.freeze(["cochanges", "untested"]),
|
|
115
|
+
priorityTopic: "cochanges",
|
|
116
|
+
coverageTopic: "untested",
|
|
117
|
+
// scoped fold: cochanges(focus) ∩ untested — the coupled-but-untested set.
|
|
118
|
+
compose: Object.freeze({
|
|
119
|
+
op: "intersect",
|
|
120
|
+
a: Object.freeze({ topic: "cochanges", of: "focus" }),
|
|
121
|
+
b: Object.freeze({ topic: "untested" }),
|
|
122
|
+
names: "the change-coupled untested set",
|
|
123
|
+
empty: "every change-coupled module is tested",
|
|
124
|
+
}),
|
|
125
|
+
achieves: "cochange-risk",
|
|
126
|
+
}),
|
|
80
127
|
]);
|
|
81
128
|
|
|
82
129
|
/** Backward-chain a meta-goal topic to the declared goal-rule that achieves it —
|
|
@@ -85,6 +132,28 @@ export function backwardChainGoal(topic) {
|
|
|
85
132
|
return GOAL_RULES.find((r) => r.achieves === topic) || null;
|
|
86
133
|
}
|
|
87
134
|
|
|
135
|
+
/** THE RULE-SELECTION DEDUCTION (replaces the old single-rule hard-wiring): a
|
|
136
|
+
* declared goal-rule APPLIES to a request iff
|
|
137
|
+
* (1) every one of its epistemic sub-goal topics backward-chains to a
|
|
138
|
+
* capability IN the declared toolset (the closed-world groundability
|
|
139
|
+
* screen — the meta-level twin of "never select an out-of-set call"),
|
|
140
|
+
* (2) the deduced mode is one of the rule's declared modes, and
|
|
141
|
+
* (3) a scoped reading's bound focus is of the rule's declared focusClass.
|
|
142
|
+
* Pure over the goal model + registry: nothing here reads the request string.
|
|
143
|
+
* The caller REFUSES on zero matches (the open-world goal-generation seam) and
|
|
144
|
+
* on more than one (an ambiguous meta-goal — arbitration between meta-goals is
|
|
145
|
+
* undeclared, so guessing one would be an invented goal). */
|
|
146
|
+
export function applicableRules(declaredTools, focus, mode) {
|
|
147
|
+
const declared = Array.isArray(declaredTools) ? declaredTools : [];
|
|
148
|
+
return GOAL_RULES.filter((rule) =>
|
|
149
|
+
rule.modes.includes(mode)
|
|
150
|
+
&& (mode !== "scoped" || (focus != null && focus.class === rule.focusClass))
|
|
151
|
+
&& rule.subGoals.every((topic) => {
|
|
152
|
+
const cap = backwardChain(topic);
|
|
153
|
+
return Boolean(cap && declared.includes(cap.name));
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
|
|
88
157
|
const refuse = (why, driver) => ({ calls: [], refused: true, terminated: true, proof: [], composed: null, driver, why });
|
|
89
158
|
|
|
90
159
|
/** THREATS lifted to the meta-level: any pending intention whose needed condition
|
|
@@ -130,11 +199,12 @@ async function groundSubGoal(topic, entityLabel, tools, ctx) {
|
|
|
130
199
|
}
|
|
131
200
|
|
|
132
201
|
/** BDI DROP CONDITIONS (Rao & Georgeff): an intention persists until it is
|
|
133
|
-
* achieved / impossible / its goal lapses.
|
|
134
|
-
*
|
|
135
|
-
|
|
202
|
+
* achieved / impossible / its goal lapses. `focusClass` is the SELECTED rule's
|
|
203
|
+
* declared focus class (never a literal here — the rule is the authority).
|
|
204
|
+
* Returns the reason string, or null to KEEP committing to it. Pure. */
|
|
205
|
+
export function dropCondition(intention, observed, mode, focus, focusClass) {
|
|
136
206
|
if (observed.has(intention.key)) return "achieved"; // fact now gathered
|
|
137
|
-
if (mode === "scoped" && (!focus || focus.class !==
|
|
207
|
+
if (mode === "scoped" && (!focus || focus.class !== focusClass)) return "lapsed"; // focus moved
|
|
138
208
|
return null; // else keep the commitment
|
|
139
209
|
}
|
|
140
210
|
|
|
@@ -148,21 +218,32 @@ export function dropCondition(intention, observed, mode, focus) {
|
|
|
148
218
|
* ctx: { dispatch(name,input)->{ok,result}, resolve(term)->{match,ambiguous} }. */
|
|
149
219
|
export async function goalReason(request, tools, ctx, { driver = "goal-0.8.1" } = {}) {
|
|
150
220
|
const declared = Array.isArray(tools) ? tools : [];
|
|
151
|
-
const rule = backwardChainGoal("coverage-gap");
|
|
152
|
-
if (!rule) return refuse("no declared goal-rule achieves the meta-goal — escalate", driver);
|
|
153
221
|
|
|
154
|
-
// STEP 1 — deduce the goal scope from the DECLARED model + a bound focus
|
|
222
|
+
// STEP 1 — deduce the goal scope from the DECLARED model + a bound focus,
|
|
223
|
+
// then SELECT the goal-rule by pure applicability (no request keyword ever):
|
|
224
|
+
// a bound focus reads scoped, no focus reads global (keystone arbitration).
|
|
155
225
|
const focus = focusOf(request, ctx);
|
|
156
|
-
|
|
157
|
-
if (focus && focus.class === "Module") mode = "scoped"; // assess the focus module's change footprint
|
|
158
|
-
else if (focus) mode = "escalate"; // a non-Module focus: no declared rule covers it
|
|
159
|
-
else mode = "global"; // no focus => rank the whole codebase (keystone)
|
|
226
|
+
const mode = focus ? "scoped" : "global";
|
|
160
227
|
|
|
161
|
-
// The open-world goal-generation seam, named honestly: a resolved focus
|
|
162
|
-
// declared goal
|
|
163
|
-
if (
|
|
164
|
-
|
|
228
|
+
// The open-world goal-generation seam, named honestly: a resolved focus whose
|
|
229
|
+
// class NO declared goal-rule scopes is REFUSED, never given an invented goal.
|
|
230
|
+
if (focus && !GOAL_RULES.some((r) => r.focusClass === focus.class)) {
|
|
231
|
+
const covered = [...new Set(GOAL_RULES.map((r) => r.focusClass))].join("/");
|
|
232
|
+
return refuse(`open-world: no declared goal-rule covers a ${focus.class} focus (the declared goal-rules are ${covered}-scoped) — escalate`, driver);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Rule selection is a DEDUCTION over the goal model + the declared toolset:
|
|
236
|
+
// 0 applicable rules => the same open-world seam (nothing declared grounds the
|
|
237
|
+
// request's scope in this toolset); >1 => an AMBIGUOUS meta-goal (arbitration
|
|
238
|
+
// between meta-goals is undeclared) — both are honest refusals, never a guess.
|
|
239
|
+
const applicable = applicableRules(declared, focus, mode);
|
|
240
|
+
if (!applicable.length) {
|
|
241
|
+
return refuse(`open-world: no declared goal-rule is applicable in ${mode} mode (each needs a sub-goal capability outside the declared toolset, or a scope it does not declare) — escalate`, driver);
|
|
242
|
+
}
|
|
243
|
+
if (applicable.length > 1) {
|
|
244
|
+
return refuse(`ambiguous meta-goal: ${applicable.length} declared goal-rules apply (${applicable.map((r) => r.id).join(", ")}) — meta-goal arbitration is undeclared, refuse rather than guess — escalate`, driver);
|
|
165
245
|
}
|
|
246
|
+
const rule = applicable[0];
|
|
166
247
|
|
|
167
248
|
// the glass-box WHY, citing the declared goal-rule by backward-chain (the C2
|
|
168
249
|
// twin of resolver.mjs's "backward-chain => <capability>" provenance).
|
|
@@ -175,15 +256,22 @@ export async function goalReason(request, tools, ctx, { driver = "goal-0.8.1" }
|
|
|
175
256
|
const calls = [];
|
|
176
257
|
const observed = new Map(); // intention.key -> gathered result set
|
|
177
258
|
|
|
178
|
-
// STEP 2/3 — the pending INTENTIONS
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
259
|
+
// STEP 2/3 — the pending INTENTIONS: the rule's epistemic sub-goals IN
|
|
260
|
+
// DECLARED ORDER (arbitration is least-commitment: min order first, the
|
|
261
|
+
// keystone selection over the gathered facts happens at compose). A topic
|
|
262
|
+
// binds the focus iff its capability declares a REQUIRED parameter (read from
|
|
263
|
+
// the registry, never special-cased by topic name); in global mode there is
|
|
264
|
+
// no focus to bind, so entity-scoped topics are deferred to the GDA expansion
|
|
265
|
+
// (gather the coverage scan first, then EXPAND to priority-of-each violator).
|
|
266
|
+
const bindsEntity = (topic) => {
|
|
267
|
+
const cap = backwardChain(topic);
|
|
268
|
+
return Boolean(cap && cap.parameters.some((p) => p.required));
|
|
269
|
+
};
|
|
270
|
+
const pending = rule.subGoals
|
|
271
|
+
.filter((topic) => mode === "scoped" || !bindsEntity(topic))
|
|
272
|
+
.map((topic, i) => (mode === "scoped" && bindsEntity(topic)
|
|
273
|
+
? { topic, of: focus.label, key: `${topic}:${focus.label}`, order: i }
|
|
274
|
+
: { topic, of: null, key: topic, order: i }));
|
|
187
275
|
|
|
188
276
|
let committed = null; // the persisted BDI intention (not re-derived each tick)
|
|
189
277
|
let expanded = false; // one-shot guard: the single bounded GDA expansion
|
|
@@ -197,7 +285,7 @@ export async function goalReason(request, tools, ctx, { driver = "goal-0.8.1" }
|
|
|
197
285
|
// (3b) PERSISTENCE — keep the committed intention unless a BDI drop condition
|
|
198
286
|
// fires; only THEN re-arbitrate. This is the "commitment, not recomputed
|
|
199
287
|
// preference" that stops the loop thrashing.
|
|
200
|
-
if (committed && dropCondition(committed, observed, mode, focus)) committed = null;
|
|
288
|
+
if (committed && dropCondition(committed, observed, mode, focus, rule.focusClass)) committed = null;
|
|
201
289
|
if (!committed || !pending.includes(committed)) {
|
|
202
290
|
// (3a) FIRST-STEP ARBITRATION — least-commitment: the lowest declared order
|
|
203
291
|
// among pending. Threat-aware: skip a step that would clobber another
|
|
@@ -220,17 +308,17 @@ export async function goalReason(request, tools, ctx, { driver = "goal-0.8.1" }
|
|
|
220
308
|
pending.splice(pending.indexOf(committed), 1);
|
|
221
309
|
committed = null;
|
|
222
310
|
|
|
223
|
-
// GDA EXPANSION (monitor -> replan), ONCE: on observing the
|
|
224
|
-
// global mode
|
|
225
|
-
// module, so arbitration can rank
|
|
226
|
-
//
|
|
227
|
-
// converges.
|
|
311
|
+
// GDA EXPANSION (monitor -> replan), ONCE: on observing the coverage set in
|
|
312
|
+
// global mode (guarded on the rule DECLARING a global reading), expand to
|
|
313
|
+
// the priority sub-goal for each violating module, so arbitration can rank
|
|
314
|
+
// them. Bounded by the finite coverage set and fired at most once (the
|
|
315
|
+
// `expanded` guard) => the pending set still converges.
|
|
228
316
|
let expandedThisTick = false;
|
|
229
|
-
if (mode === "global" && achievedTopic === rule.coverageTopic && !expanded) {
|
|
317
|
+
if (mode === "global" && rule.modes.includes("global") && achievedTopic === rule.coverageTopic && !expanded) {
|
|
230
318
|
expanded = true;
|
|
231
319
|
expandedThisTick = true;
|
|
232
|
-
const
|
|
233
|
-
|
|
320
|
+
const violating = observed.get(rule.coverageTopic) || [];
|
|
321
|
+
violating.forEach((m, i) => pending.push({ topic: rule.priorityTopic, of: m, key: `${rule.priorityTopic}:${m}`, order: 100 + i }));
|
|
234
322
|
}
|
|
235
323
|
|
|
236
324
|
// the invariant, enforced mechanically: the pending set shrank by one this
|
|
@@ -245,21 +333,31 @@ export async function goalReason(request, tools, ctx, { driver = "goal-0.8.1" }
|
|
|
245
333
|
// facts (all INSIDE the driver's timeout guard; no unbounded post-work).
|
|
246
334
|
let composed;
|
|
247
335
|
if (mode === "scoped") {
|
|
248
|
-
// the
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
336
|
+
// interpret the rule's DECLARATIVE compose spec: intersect two gathered
|
|
337
|
+
// sides, each a topic (optionally focus-bound, optionally with the focus
|
|
338
|
+
// itself unioned in — the change-footprint shape). ∅ is a real answer.
|
|
339
|
+
const sideSet = (side) => {
|
|
340
|
+
const key = side.of === "focus" ? `${side.topic}:${focus.label}` : side.topic;
|
|
341
|
+
const set = observed.get(key) || [];
|
|
342
|
+
return side.withFocus ? [focus.label, ...set] : set;
|
|
343
|
+
};
|
|
344
|
+
const sideDesc = (side) => (side.withFocus
|
|
345
|
+
? `({${focus.label}} ∪ ${side.topic})`
|
|
346
|
+
: side.of === "focus" ? `${side.topic}(${focus.label})` : side.topic);
|
|
347
|
+
const spec = rule.compose;
|
|
348
|
+
composed = intersect(sideSet(spec.a), sideSet(spec.b));
|
|
349
|
+
why.push(`compose: ${sideDesc(spec.a)} ∩ ${sideDesc(spec.b)} = ${spec.names} (${composed.length ? composed.join(", ") : `∅ — ${spec.empty}`})`);
|
|
253
350
|
} else {
|
|
254
|
-
// KEYSTONE arbitration: among the coverage violations
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
351
|
+
// KEYSTONE arbitration: among the coverage violations, pick the highest
|
|
352
|
+
// declared priority — the widest |priority(m)| set — tie broken by label
|
|
353
|
+
// order. The single most-worth-covering module. Only a rule declaring a
|
|
354
|
+
// global mode ever reaches here (applicability screened on rule.modes).
|
|
355
|
+
const violating = observed.get(rule.coverageTopic) || [];
|
|
356
|
+
const ranked = violating
|
|
357
|
+
.map((m) => ({ m, weight: (observed.get(`${rule.priorityTopic}:${m}`) || []).length }))
|
|
260
358
|
.sort((a, b) => b.weight - a.weight || String(a.m).localeCompare(String(b.m)));
|
|
261
359
|
composed = ranked.length ? [ranked[0].m] : [];
|
|
262
|
-
why.push(`keystone: argmax |
|
|
360
|
+
why.push(`keystone: argmax |${rule.priorityTopic}| over ${violating.length} ${rule.coverageTopic} module(s) => ${composed.length ? `${composed[0]} (weight ${ranked[0].weight})` : "∅"}`);
|
|
263
361
|
}
|
|
264
362
|
|
|
265
363
|
return { calls, refused: false, terminated: true, proof, why, composed, driver, observed: `goal(${mode}): ${calls.map((c) => c.name).join(" -> ")}` };
|
package/src/router/guardrail.mjs
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// Pure over its inputs + ctx.resolve (the binding oracle). No network, no Date.now.
|
|
29
29
|
|
|
30
30
|
import { capabilityByName, preconditionsOf, PRECOND } from "./registry.mjs";
|
|
31
|
-
import { hallucinationsIn } from "
|
|
31
|
+
import { hallucinationsIn } from "./call-validator.mjs";
|
|
32
32
|
|
|
33
33
|
/** Validate a proposed tool_use. Returns a glass-box verdict:
|
|
34
34
|
* { ok, tool, denied:[{reason,detail}], steps:[{pred,ok,...}], provenance }
|
package/src/router/planner.mjs
CHANGED
|
@@ -74,7 +74,28 @@ export function decompose(request) {
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// METHOD 3 —
|
|
77
|
+
// METHOD 3 — the MEMBER-FILTER recipe: "which/what methods|members of X …
|
|
78
|
+
// (end up|eventually)? calling/reaching Y". A C1 surface-syntax recipe like the
|
|
79
|
+
// conditional and relative-filter methods above (the C1 discipline: a closed,
|
|
80
|
+
// authored shape — NOT the C2 goal-reasoner's deduction). Decomposes to
|
|
81
|
+
// [enumerate members(X), filter by bounded transitive call-reach of Y]. The
|
|
82
|
+
// second segment is the filter TARGET, role "member-filter": the DRIVER owns
|
|
83
|
+
// the per-member callees hop + the reachability fold (driver-resolver.mjs) —
|
|
84
|
+
// segment 2 is not a resolvable leaf sub-goal on its own.
|
|
85
|
+
const mem = raw.match(
|
|
86
|
+
/^(?:which|what)\s+(?:methods?|members?)\s+of\s+(.+?)\s+(?:(?:end\s+up|eventually)\s+)?(?:calls?|calling|reach(?:es|ing)?|invokes?|invoking)\s+(.+?)\s*\??$/i,
|
|
87
|
+
);
|
|
88
|
+
if (mem) {
|
|
89
|
+
return {
|
|
90
|
+
method: "member-filter",
|
|
91
|
+
segments: [
|
|
92
|
+
{ text: `members ${mem[1].trim()}`, role: "action", thread: false },
|
|
93
|
+
{ text: mem[2].trim(), role: "member-filter", thread: true },
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// METHOD 4 — SEQUENCING: split on the ordered connectives. Least commitment:
|
|
78
99
|
// we only split where a connective actually is.
|
|
79
100
|
const parts = raw.split(/\s*(?:,\s*then\s+|,\s+and\s+then\s+|\s+and\s+then\s+|\s+then\s+|,\s+|\s+and\s+)\s*/i)
|
|
80
101
|
.map((s) => s.replace(/^(?:then\s+|and\s+then\s+|and\s+|also\s+|check\s+|next\s+)/i, "").trim())
|
package/src/router/resolver.mjs
CHANGED
|
@@ -44,7 +44,7 @@ import { selectTool } from "../server-http.mjs";
|
|
|
44
44
|
import {
|
|
45
45
|
capabilities, capabilityByName, preconditionsOf, effectsOf, PRECOND,
|
|
46
46
|
} from "./registry.mjs";
|
|
47
|
-
import { hallucinationsIn } from "
|
|
47
|
+
import { hallucinationsIn } from "./call-validator.mjs";
|
|
48
48
|
|
|
49
49
|
// ---- the ask-kind -> epistemic-topic MAPPING (the Stage-1 core) --------------
|
|
50
50
|
// Keyed `${shape}:${kind}` off parseQuery's simple-clause output. The VALUE is
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/router/set-algebra.mjs — the COMPOSITION OPERATORS: pure set-algebra a
|
|
2
|
+
// multi-step plan needs to fold its threaded step result-sets into ONE composed
|
|
3
|
+
// answer. Shared by the product router (goal-reasoner's relative-filter fold)
|
|
4
|
+
// and the bench result-execution layer (agentbench/results.mjs re-exports
|
|
5
|
+
// these) — the bench imports the product, never the other way round. The
|
|
6
|
+
// resolver DRIVER picks the operator from the router's OWN HTN method
|
|
7
|
+
// (relative-filter -> intersect; conditional -> fallback/guard) and applies it;
|
|
8
|
+
// grade.mjs never imports these (it only value-compares the driver's composed
|
|
9
|
+
// answer to the STATIC expect.result literal — no circular re-derivation).
|
|
10
|
+
// No I/O, no Date.now, no LLM.
|
|
11
|
+
|
|
12
|
+
/** Dedupe + locale-stable sort — the canonical label-set normal form. */
|
|
13
|
+
export const uniqSort = (xs) => [...new Set(xs)].sort((a, b) => String(a).localeCompare(String(b)));
|
|
14
|
+
|
|
15
|
+
/** a ∩ b — the relative-filter fold ("of the <set>, which are <Y>"). */
|
|
16
|
+
export function intersect(a = [], b = []) {
|
|
17
|
+
const bs = new Set(b);
|
|
18
|
+
return uniqSort([...new Set(a)].filter((x) => bs.has(x)));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** if a is empty -> b, else a — the conditional "… <action> instead" recipe
|
|
22
|
+
* ("if X has no tests, list what covers Y instead"). */
|
|
23
|
+
export function fallbackIfEmpty(a = [], b = []) {
|
|
24
|
+
return uniqSort(a.length ? a : b);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** if a is empty -> b, else ∅ — the guarded conditional ("if X is untested,
|
|
28
|
+
* <action> it"): the action's result fires ONLY when the guard set is empty. */
|
|
29
|
+
export function guardIfEmpty(a = [], b = []) {
|
|
30
|
+
return uniqSort(a.length ? [] : b);
|
|
31
|
+
}
|