@verevoir/recipes 0.13.0 → 0.14.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/CHANGELOG.md +4 -0
- package/dist/engine.d.ts +1 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -0
- package/dist/engine.js.map +1 -1
- package/dist/plan-execute.d.ts +110 -0
- package/dist/plan-execute.d.ts.map +1 -0
- package/dist/plan-execute.js +199 -0
- package/dist/plan-execute.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 — 2026-07-02
|
|
4
|
+
|
|
5
|
+
- **Plan-first execution engine** (`plan-execute.ts`), relocated from the mcp so it's a shared engine primitive alongside `buildPlanGraph`. Pure (no SDK/network — deps injected): `executePlanParallel` (layer the DAG, run each layer concurrently, thread upstream results, isolate failures), `layerPlan`, `buildExecutionPlan`, `gatePlan`, `parseEntrySelection`, `defaultBuildDirective`, and the `NodeRun` / `PlanExecDeps` / `PlanExecResult` / `GateVerdict` / `RecordedCall` types. Exported from the engine entry. The mcp keeps the LLM-specific bits (`selectEntryTypes`, `enactNode`, the coordinator harness) and consumes these. 101 tests.
|
|
6
|
+
|
|
3
7
|
## 0.13.0 — 2026-07-01
|
|
4
8
|
|
|
5
9
|
- **The capability join lives here now — `capabilityWithRun` / `capabilitiesWithRun`** (STDIO-515). Recipes already owned a capability's DATA half (`parseCapability` → `CapabilityDescriptor`); the CODE half (a consumer's `run` executor, keyed by type) was joined to it in each consumer (aigency-web's `capabilities.ts`). That join is engine logic in the wrong place — and it must be **universally available to aigency AND the MCP**, which can't import aigency, so a shared lib is the only home. Lifted here: `capabilityWithRun<Run>(corpus, type, executors)` (single lookup) and `capabilitiesWithRun<Run>(corpus, executors)` (bulk), plus the `CapabilityWithRun<Run> = CapabilityDescriptor & { run?: Run }` type. **Generic over the executor signature** — recipes owns the join, not the executor shape, so each consumer plugs in its own (`run: undefined` for a conversation-only capability with no executor; `undefined` when no descriptor of that type exists). Consumers refactor to call it and drop their local copies.
|
package/dist/engine.d.ts
CHANGED
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC"}
|
package/dist/engine.js
CHANGED
package/dist/engine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,gFAAgF;AAChF,oFAAoF;AACpF,kFAAkF;AAClF,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,gFAAgF;AAChF,oFAAoF;AACpF,kFAAkF;AAClF,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { CapabilityDescriptor } from './index.js';
|
|
2
|
+
import type { ExecutionPlan, PlanNode } from './plan.js';
|
|
3
|
+
/** One real tool call a node ran, with what it cost — the opaque accounting unit
|
|
4
|
+
* the executor threads through to a caller's cost aggregation. Recipes never
|
|
5
|
+
* interprets these; it only collects them per node and hands the union back. */
|
|
6
|
+
export interface RecordedCall {
|
|
7
|
+
/** The tool the call ran (`enact_capability`, `delegate`, …). */
|
|
8
|
+
tool: string;
|
|
9
|
+
/** The concrete model id that ran the work, or `'(none)'` for an inline call. */
|
|
10
|
+
model: string;
|
|
11
|
+
/** FRESH input tokens only — cache read/write are separate below so a caller
|
|
12
|
+
* can price them at their real (cheaper) rates. */
|
|
13
|
+
tokensIn: number;
|
|
14
|
+
tokensOut: number;
|
|
15
|
+
/** Cache-read / cache-write input tokens, SEPARATE from `tokensIn`. */
|
|
16
|
+
cacheRead?: number;
|
|
17
|
+
cacheWrite?: number;
|
|
18
|
+
/** Wall-clock for the call, ms. */
|
|
19
|
+
ms: number;
|
|
20
|
+
}
|
|
21
|
+
/** The outcome of running one plan node: its produced text, every model call it
|
|
22
|
+
* made (for the cost rollup), and whether it failed. A failed run's dependents
|
|
23
|
+
* can never satisfy their deps, so they are skipped. */
|
|
24
|
+
export interface NodeRun {
|
|
25
|
+
/** The node's produced output — threaded into its dependents' directives. */
|
|
26
|
+
text: string;
|
|
27
|
+
/** Every model call this node made, for the whole-run cost aggregation. */
|
|
28
|
+
calls: RecordedCall[];
|
|
29
|
+
/** True when the node's enact errored or its gate failed. */
|
|
30
|
+
failed?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface PlanExecDeps {
|
|
33
|
+
/** Run ONE node: enact its capability with a directive already threaded from
|
|
34
|
+
* upstream results. Injected so the executor is testable without a network;
|
|
35
|
+
* the coordinator provides the real one (a wrapper over `enactCapability`). */
|
|
36
|
+
enactNode: (node: PlanNode, directive: string) => Promise<NodeRun>;
|
|
37
|
+
/** Build a node's directive from the request and its upstream results (the
|
|
38
|
+
* produced text of each capability in `node.dependsOn`). Optional — a sensible
|
|
39
|
+
* default grounds the request with a labelled block per upstream. */
|
|
40
|
+
buildDirective?: (node: PlanNode, request: string, upstream: Map<string, string>) => string;
|
|
41
|
+
}
|
|
42
|
+
export interface PlanExecResult {
|
|
43
|
+
/** Capability type → produced text, for every node that ran (not the skipped). */
|
|
44
|
+
results: Map<string, string>;
|
|
45
|
+
/** All RecordedCalls across all nodes that ran, for the cost aggregation. */
|
|
46
|
+
calls: RecordedCall[];
|
|
47
|
+
/** The layers as executed; each is the capability types run CONCURRENTLY.
|
|
48
|
+
* `layers.length` is the critical-path depth; the total size is the node
|
|
49
|
+
* count. Order within a layer is stable (sorted by capability). */
|
|
50
|
+
layers: string[][];
|
|
51
|
+
/** Nodes that failed — their transitive dependents were skipped, so a failure
|
|
52
|
+
* leaves a legible partial result rather than throwing. */
|
|
53
|
+
failed: string[];
|
|
54
|
+
}
|
|
55
|
+
/** The default directive builder: the request, followed by one labelled grounding
|
|
56
|
+
* block per upstream result, so a node sees what its prerequisites produced. */
|
|
57
|
+
export declare function defaultBuildDirective(_node: PlanNode, request: string, upstream: Map<string, string>): string;
|
|
58
|
+
/**
|
|
59
|
+
* Layer the plan's DAG: layer 0 is the nodes with no in-plan dependency, and
|
|
60
|
+
* layer k is the nodes whose every dependency sits in an earlier layer. PURE and
|
|
61
|
+
* deterministic — membership follows only from `dependsOn`, and each layer is
|
|
62
|
+
* sorted by capability so the order is stable across runs. A dependency naming a
|
|
63
|
+
* capability not in the plan is treated as already-satisfied (it can't be waited
|
|
64
|
+
* on). A cycle (which a well-formed plan never contains) leaves its nodes
|
|
65
|
+
* unlayered rather than looping.
|
|
66
|
+
*/
|
|
67
|
+
export declare function layerPlan(plan: ExecutionPlan): PlanNode[][];
|
|
68
|
+
/**
|
|
69
|
+
* Execute a plan with its independent nodes running CONCURRENTLY. The DAG is
|
|
70
|
+
* layered (`layerPlan`); each layer runs via `Promise.all`, and a barrier between
|
|
71
|
+
* layers means a node never starts before its dependencies' results exist. Before
|
|
72
|
+
* a node runs, its upstream results are gathered and threaded into its directive
|
|
73
|
+
* (`deps.buildDirective`, or a labelled-grounding default).
|
|
74
|
+
*
|
|
75
|
+
* Failure is isolated: a node whose run reports `failed` — or whose `enactNode`
|
|
76
|
+
* throws (caught, treated as failed) — is recorded in `failed`, and its
|
|
77
|
+
* transitive dependents are SKIPPED (they can never satisfy their deps) while
|
|
78
|
+
* independent nodes and layers proceed. This never throws: a failure yields a
|
|
79
|
+
* legible partial result, not a dropped run.
|
|
80
|
+
*/
|
|
81
|
+
export declare function executePlanParallel(plan: ExecutionPlan, deps: PlanExecDeps): Promise<PlanExecResult>;
|
|
82
|
+
/** A gate verdict over a plan: whether it's safe to spend on, and the findings
|
|
83
|
+
* that failed it. `ok` false means the plan should be aborted, not executed. */
|
|
84
|
+
export interface GateVerdict {
|
|
85
|
+
ok: boolean;
|
|
86
|
+
findings: string[];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Gate a plan before any spend — the "inspect before you spend" control point.
|
|
90
|
+
* PURE. A plan passes only when:
|
|
91
|
+
* - it has at least one node (an empty plan produces nothing);
|
|
92
|
+
* - every entry capability resolves to a node in the plan;
|
|
93
|
+
* - no node depends on a capability the plan doesn't contain (a dangling edge);
|
|
94
|
+
* - the plan is acyclic — topologically orderable via its `dependsOn` edges.
|
|
95
|
+
*
|
|
96
|
+
* buildPlanGraph already topologically orders and drops unresolvable edges, so a
|
|
97
|
+
* gate failure here signals a plan built some other way, or a corpus/entry
|
|
98
|
+
* mismatch. Findings name what failed so the caller can abort legibly.
|
|
99
|
+
*/
|
|
100
|
+
export declare function gatePlan(plan: ExecutionPlan): GateVerdict;
|
|
101
|
+
/** Parse selected ids from a model reply, matching only against the supplied id
|
|
102
|
+
* set, order preserved by the corpus. Word-boundary anchored so a hyphenated id
|
|
103
|
+
* doesn't fire on its substrings (same rule as recipes' parseSelectedIds). PURE
|
|
104
|
+
* string parsing — no model call. */
|
|
105
|
+
export declare function parseEntrySelection(text: string, ids: string[]): string[];
|
|
106
|
+
/** Build an ExecutionPlan from buildPlanGraph's output. Practices are `[]` — this
|
|
107
|
+
* builds the DAG's cost + parallel structure, not per-node provisioning (that's
|
|
108
|
+
* `planExecution`'s job, which needs an embedder). PURE. */
|
|
109
|
+
export declare function buildExecutionPlan(request: string, entry: string[], corpus: CapabilityDescriptor[]): ExecutionPlan;
|
|
110
|
+
//# sourceMappingURL=plan-execute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-execute.d.ts","sourceRoot":"","sources":["../src/plan-execute.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEzD;;gFAEgF;AAChF,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd;uDACmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;wDAEwD;AACxD,MAAM,WAAW,OAAO;IACtB,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B;;mFAE+E;IAC/E,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnE;;yEAEqE;IACrE,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;CAC7F;AAED,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,6EAA6E;IAC7E,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB;;uEAEmE;IACnE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACnB;+DAC2D;IAC3D,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;gFACgF;AAChF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,MAAM,CAMR;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,EAAE,CAkB3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,cAAc,CAAC,CA4CzB;AAED;gFACgF;AAChF,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,WAAW,CA2BzD;AA2BD;;;qCAGqC;AACrC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAOzE;AAED;;4DAE4D;AAC5D,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,EAAE,oBAAoB,EAAE,GAC7B,aAAa,CAaf"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// PLAN EXECUTION — the PURE, plan-first execution engine, sibling to
|
|
2
|
+
// `buildPlanGraph` in plan.ts. Where plan.ts turns a request into a dependency
|
|
3
|
+
// DAG of capability nodes, this module GATES that plan (inspect before you
|
|
4
|
+
// spend), LAYERS it, and EXECUTES it with independent nodes running CONCURRENTLY.
|
|
5
|
+
//
|
|
6
|
+
// Everything here is a PURE function of the plan (its `dependsOn` edges) plus
|
|
7
|
+
// INJECTED enactment — no LLM, no network, no SDK. A real coordinator wraps
|
|
8
|
+
// `enactCapability`; tests pass a mock. That purity is why these live in recipes
|
|
9
|
+
// as a shared engine primitive (aigency, the MCP, or anyone can drive them),
|
|
10
|
+
// rather than in any one consumer.
|
|
11
|
+
//
|
|
12
|
+
// `RecordedCall` is the executor's opaque cost-accounting unit — the executor
|
|
13
|
+
// threads a node's recorded calls through to the aggregate without interpreting
|
|
14
|
+
// them, so recipes stays free of any measurement machinery.
|
|
15
|
+
import { buildPlanGraph } from './plan.js';
|
|
16
|
+
/** The default directive builder: the request, followed by one labelled grounding
|
|
17
|
+
* block per upstream result, so a node sees what its prerequisites produced. */
|
|
18
|
+
export function defaultBuildDirective(_node, request, upstream) {
|
|
19
|
+
if (upstream.size === 0)
|
|
20
|
+
return request;
|
|
21
|
+
const grounding = [...upstream.entries()]
|
|
22
|
+
.map(([capability, text]) => `grounding: ${capability}:\n${text}`)
|
|
23
|
+
.join('\n\n');
|
|
24
|
+
return `${request}\n\n${grounding}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Layer the plan's DAG: layer 0 is the nodes with no in-plan dependency, and
|
|
28
|
+
* layer k is the nodes whose every dependency sits in an earlier layer. PURE and
|
|
29
|
+
* deterministic — membership follows only from `dependsOn`, and each layer is
|
|
30
|
+
* sorted by capability so the order is stable across runs. A dependency naming a
|
|
31
|
+
* capability not in the plan is treated as already-satisfied (it can't be waited
|
|
32
|
+
* on). A cycle (which a well-formed plan never contains) leaves its nodes
|
|
33
|
+
* unlayered rather than looping.
|
|
34
|
+
*/
|
|
35
|
+
export function layerPlan(plan) {
|
|
36
|
+
const inPlan = new Set(plan.nodes.map((n) => n.capability));
|
|
37
|
+
const placed = new Set();
|
|
38
|
+
const layers = [];
|
|
39
|
+
while (placed.size < plan.nodes.length) {
|
|
40
|
+
const ready = plan.nodes.filter((n) => !placed.has(n.capability) && n.dependsOn.every((d) => !inPlan.has(d) || placed.has(d)));
|
|
41
|
+
// No node became ready but some remain unplaced → a dependency cycle. Stop
|
|
42
|
+
// rather than loop; the remaining nodes are left unlayered (never runnable).
|
|
43
|
+
if (ready.length === 0)
|
|
44
|
+
break;
|
|
45
|
+
ready.sort((a, b) => a.capability.localeCompare(b.capability));
|
|
46
|
+
for (const n of ready)
|
|
47
|
+
placed.add(n.capability);
|
|
48
|
+
layers.push(ready);
|
|
49
|
+
}
|
|
50
|
+
return layers;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Execute a plan with its independent nodes running CONCURRENTLY. The DAG is
|
|
54
|
+
* layered (`layerPlan`); each layer runs via `Promise.all`, and a barrier between
|
|
55
|
+
* layers means a node never starts before its dependencies' results exist. Before
|
|
56
|
+
* a node runs, its upstream results are gathered and threaded into its directive
|
|
57
|
+
* (`deps.buildDirective`, or a labelled-grounding default).
|
|
58
|
+
*
|
|
59
|
+
* Failure is isolated: a node whose run reports `failed` — or whose `enactNode`
|
|
60
|
+
* throws (caught, treated as failed) — is recorded in `failed`, and its
|
|
61
|
+
* transitive dependents are SKIPPED (they can never satisfy their deps) while
|
|
62
|
+
* independent nodes and layers proceed. This never throws: a failure yields a
|
|
63
|
+
* legible partial result, not a dropped run.
|
|
64
|
+
*/
|
|
65
|
+
export async function executePlanParallel(plan, deps) {
|
|
66
|
+
const buildDirective = deps.buildDirective ?? defaultBuildDirective;
|
|
67
|
+
const layers = layerPlan(plan);
|
|
68
|
+
const results = new Map();
|
|
69
|
+
const calls = [];
|
|
70
|
+
const failed = new Set();
|
|
71
|
+
for (const layer of layers) {
|
|
72
|
+
// A node is skipped when any dependency failed or was itself skipped — the
|
|
73
|
+
// failure propagates transitively down the DAG, layer by layer.
|
|
74
|
+
const runnable = layer.filter((n) => !n.dependsOn.some((d) => failed.has(d)));
|
|
75
|
+
for (const n of layer)
|
|
76
|
+
if (!runnable.includes(n))
|
|
77
|
+
failed.add(n.capability);
|
|
78
|
+
const runs = await Promise.all(runnable.map(async (node) => {
|
|
79
|
+
const upstream = new Map();
|
|
80
|
+
for (const dep of node.dependsOn) {
|
|
81
|
+
const text = results.get(dep);
|
|
82
|
+
if (text !== undefined)
|
|
83
|
+
upstream.set(dep, text);
|
|
84
|
+
}
|
|
85
|
+
const directive = buildDirective(node, plan.request, upstream);
|
|
86
|
+
try {
|
|
87
|
+
return [node, await deps.enactNode(node, directive)];
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
const message = `<enact ${node.capability} failed: ${String(err).slice(0, 200)}>`;
|
|
91
|
+
return [node, { text: message, calls: [], failed: true }];
|
|
92
|
+
}
|
|
93
|
+
}));
|
|
94
|
+
for (const [node, run] of runs) {
|
|
95
|
+
calls.push(...run.calls);
|
|
96
|
+
if (run.failed)
|
|
97
|
+
failed.add(node.capability);
|
|
98
|
+
else
|
|
99
|
+
results.set(node.capability, run.text);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
results,
|
|
104
|
+
calls,
|
|
105
|
+
layers: layers.map((layer) => layer.map((n) => n.capability)),
|
|
106
|
+
failed: [...failed],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Gate a plan before any spend — the "inspect before you spend" control point.
|
|
111
|
+
* PURE. A plan passes only when:
|
|
112
|
+
* - it has at least one node (an empty plan produces nothing);
|
|
113
|
+
* - every entry capability resolves to a node in the plan;
|
|
114
|
+
* - no node depends on a capability the plan doesn't contain (a dangling edge);
|
|
115
|
+
* - the plan is acyclic — topologically orderable via its `dependsOn` edges.
|
|
116
|
+
*
|
|
117
|
+
* buildPlanGraph already topologically orders and drops unresolvable edges, so a
|
|
118
|
+
* gate failure here signals a plan built some other way, or a corpus/entry
|
|
119
|
+
* mismatch. Findings name what failed so the caller can abort legibly.
|
|
120
|
+
*/
|
|
121
|
+
export function gatePlan(plan) {
|
|
122
|
+
const findings = [];
|
|
123
|
+
const present = new Set(plan.nodes.map((n) => n.capability));
|
|
124
|
+
if (plan.nodes.length === 0) {
|
|
125
|
+
findings.push('plan is empty — no capabilities to execute');
|
|
126
|
+
}
|
|
127
|
+
for (const entry of plan.entry) {
|
|
128
|
+
if (!present.has(entry)) {
|
|
129
|
+
findings.push(`entry capability "${entry}" has no node in the plan`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const node of plan.nodes) {
|
|
133
|
+
for (const dep of node.dependsOn) {
|
|
134
|
+
if (!present.has(dep)) {
|
|
135
|
+
findings.push(`node "${node.capability}" depends on missing capability "${dep}"`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!isAcyclic(plan.nodes)) {
|
|
140
|
+
findings.push('plan has a dependency cycle — it cannot be topologically ordered');
|
|
141
|
+
}
|
|
142
|
+
return { ok: findings.length === 0, findings };
|
|
143
|
+
}
|
|
144
|
+
/** Whether a node set's `dependsOn` edges form a DAG — a Kahn peel that consumes
|
|
145
|
+
* every node iff there is no cycle. Edges to absent capabilities are ignored here
|
|
146
|
+
* (the dangling-dep check reports those); this asks only about cyclicity. */
|
|
147
|
+
function isAcyclic(nodes) {
|
|
148
|
+
const present = new Set(nodes.map((n) => n.capability));
|
|
149
|
+
const indeg = new Map();
|
|
150
|
+
for (const n of nodes) {
|
|
151
|
+
indeg.set(n.capability, n.dependsOn.filter((d) => present.has(d)).length);
|
|
152
|
+
}
|
|
153
|
+
const ready = nodes.filter((n) => (indeg.get(n.capability) ?? 0) === 0).map((n) => n.capability);
|
|
154
|
+
let consumed = 0;
|
|
155
|
+
while (ready.length > 0) {
|
|
156
|
+
const t = ready.pop();
|
|
157
|
+
consumed++;
|
|
158
|
+
for (const n of nodes) {
|
|
159
|
+
if (n.dependsOn.includes(t)) {
|
|
160
|
+
const d = (indeg.get(n.capability) ?? 0) - 1;
|
|
161
|
+
indeg.set(n.capability, d);
|
|
162
|
+
if (d === 0)
|
|
163
|
+
ready.push(n.capability);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return consumed === nodes.length;
|
|
168
|
+
}
|
|
169
|
+
/** Parse selected ids from a model reply, matching only against the supplied id
|
|
170
|
+
* set, order preserved by the corpus. Word-boundary anchored so a hyphenated id
|
|
171
|
+
* doesn't fire on its substrings (same rule as recipes' parseSelectedIds). PURE
|
|
172
|
+
* string parsing — no model call. */
|
|
173
|
+
export function parseEntrySelection(text, ids) {
|
|
174
|
+
const out = [];
|
|
175
|
+
for (const id of ids) {
|
|
176
|
+
const re = new RegExp(`(?<![a-z-])${id}(?![a-z-])`);
|
|
177
|
+
if (re.test(text) && !out.includes(id))
|
|
178
|
+
out.push(id);
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
/** Build an ExecutionPlan from buildPlanGraph's output. Practices are `[]` — this
|
|
183
|
+
* builds the DAG's cost + parallel structure, not per-node provisioning (that's
|
|
184
|
+
* `planExecution`'s job, which needs an embedder). PURE. */
|
|
185
|
+
export function buildExecutionPlan(request, entry, corpus) {
|
|
186
|
+
const graph = buildPlanGraph(entry, corpus);
|
|
187
|
+
const nodes = graph.map((g) => ({
|
|
188
|
+
capability: g.capability,
|
|
189
|
+
practices: [],
|
|
190
|
+
dependsOn: g.dependsOn,
|
|
191
|
+
source: g.source,
|
|
192
|
+
}));
|
|
193
|
+
return {
|
|
194
|
+
request,
|
|
195
|
+
entry: graph.filter((g) => g.source === 'retrieved').map((g) => g.capability),
|
|
196
|
+
nodes,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=plan-execute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-execute.js","sourceRoot":"","sources":["../src/plan-execute.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verevoir/recipes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Recipe-descriptor format + zero-dependency parser: a flat-frontmatter .md describes a parameterised procedure (typed inputs → instructions → result) that a host compiles to a chat-time tool or an MCP prompt.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|