@crewhaus/ir-passes 0.5.8 → 0.6.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/dist/index.d.ts +10 -2
- package/dist/index.js +267 -2
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,12 @@
|
|
|
26
26
|
* - `memoryIntegrityPass` — v0.3.0 validating pass: memory/continuity
|
|
27
27
|
* block integrity (wiki.recallK bounds, ttl floor, session-scope only
|
|
28
28
|
* on session-routed shapes, fragment JSON-serializability).
|
|
29
|
+
* - `modelPlanIntegrity` — 0.6.0 validating pass: the `models:` registry
|
|
30
|
+
* and every `model_pool` (candidate `tools ⊆ block tools`, restricted
|
|
31
|
+
* profile permissions, `enabled` leaves a routable candidate, strategy /
|
|
32
|
+
* rule / floor role slots name declared tags or arms, classifier ⇔
|
|
33
|
+
* policy, pool blob JSON round-trip). Returns the IR untouched when no
|
|
34
|
+
* model keys are present.
|
|
29
35
|
* - `promptCachePrefixSort` — TODO: re-orders system-block segments so
|
|
30
36
|
* the cache prefix is maximised. v0 stub returns IR unchanged so the
|
|
31
37
|
* pipeline contract holds; v1 follow-up wires this once we land
|
|
@@ -34,11 +40,12 @@
|
|
|
34
40
|
* Pipeline order in `applyPasses` (the safe default):
|
|
35
41
|
* deadToolElimination → redundantMcpServerCollapse →
|
|
36
42
|
* permissionRuleCanonicalize → transactionPolicyEnforcement →
|
|
37
|
-
* wellFormednessCheck → memoryIntegrityPass →
|
|
43
|
+
* wellFormednessCheck → memoryIntegrityPass → modelPlanIntegrity →
|
|
44
|
+
* promptCachePrefixSort
|
|
38
45
|
*
|
|
39
46
|
* G45 (loop contract 0.4) — the passes split into two families:
|
|
40
47
|
* - VALIDATING (transactionPolicyEnforcement, wellFormednessCheck,
|
|
41
|
-
* memoryIntegrityPass): pure pass-throughs that throw `IrPassError` on
|
|
48
|
+
* memoryIntegrityPass, modelPlanIntegrity): pure pass-throughs that throw `IrPassError` on
|
|
42
49
|
* a violation. Exported as `VALIDATING_PASSES`; the compiler runs them
|
|
43
50
|
* UNCONDITIONALLY inside `compile()` (they cannot drift bundle bytes).
|
|
44
51
|
* - REWRITING (deadToolElimination, redundantMcpServerCollapse,
|
|
@@ -112,6 +119,7 @@ export declare function transactionPolicyEnforcement(ir: IrNode): IrNode;
|
|
|
112
119
|
*/
|
|
113
120
|
export declare function wellFormednessCheck(ir: IrNode): IrNode;
|
|
114
121
|
export declare function memoryIntegrityPass(ir: IrNode): IrNode;
|
|
122
|
+
export declare function modelPlanIntegrity(ir: IrNode): IrNode;
|
|
115
123
|
/**
|
|
116
124
|
* G45 (loop contract 0.4) — the VALIDATING passes: pure pass-throughs that
|
|
117
125
|
* throw `IrPassError` on a violation and never rewrite the IR. The compiler
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,12 @@
|
|
|
26
26
|
* - `memoryIntegrityPass` — v0.3.0 validating pass: memory/continuity
|
|
27
27
|
* block integrity (wiki.recallK bounds, ttl floor, session-scope only
|
|
28
28
|
* on session-routed shapes, fragment JSON-serializability).
|
|
29
|
+
* - `modelPlanIntegrity` — 0.6.0 validating pass: the `models:` registry
|
|
30
|
+
* and every `model_pool` (candidate `tools ⊆ block tools`, restricted
|
|
31
|
+
* profile permissions, `enabled` leaves a routable candidate, strategy /
|
|
32
|
+
* rule / floor role slots name declared tags or arms, classifier ⇔
|
|
33
|
+
* policy, pool blob JSON round-trip). Returns the IR untouched when no
|
|
34
|
+
* model keys are present.
|
|
29
35
|
* - `promptCachePrefixSort` — TODO: re-orders system-block segments so
|
|
30
36
|
* the cache prefix is maximised. v0 stub returns IR unchanged so the
|
|
31
37
|
* pipeline contract holds; v1 follow-up wires this once we land
|
|
@@ -34,11 +40,12 @@
|
|
|
34
40
|
* Pipeline order in `applyPasses` (the safe default):
|
|
35
41
|
* deadToolElimination → redundantMcpServerCollapse →
|
|
36
42
|
* permissionRuleCanonicalize → transactionPolicyEnforcement →
|
|
37
|
-
* wellFormednessCheck → memoryIntegrityPass →
|
|
43
|
+
* wellFormednessCheck → memoryIntegrityPass → modelPlanIntegrity →
|
|
44
|
+
* promptCachePrefixSort
|
|
38
45
|
*
|
|
39
46
|
* G45 (loop contract 0.4) — the passes split into two families:
|
|
40
47
|
* - VALIDATING (transactionPolicyEnforcement, wellFormednessCheck,
|
|
41
|
-
* memoryIntegrityPass): pure pass-throughs that throw `IrPassError` on
|
|
48
|
+
* memoryIntegrityPass, modelPlanIntegrity): pure pass-throughs that throw `IrPassError` on
|
|
42
49
|
* a violation. Exported as `VALIDATING_PASSES`; the compiler runs them
|
|
43
50
|
* UNCONDITIONALLY inside `compile()` (they cannot drift bundle bytes).
|
|
44
51
|
* - REWRITING (deadToolElimination, redundantMcpServerCollapse,
|
|
@@ -91,6 +98,17 @@ export function deadToolElimination(ir) {
|
|
|
91
98
|
for (const sa of subAgentRefs) {
|
|
92
99
|
for (const t of sa.tools)
|
|
93
100
|
used.add(t);
|
|
101
|
+
// 0.6.0 §4.3 — a sub-agent's own pool candidates may subset its tools.
|
|
102
|
+
for (const c of sa.modelPool?.candidates ?? [])
|
|
103
|
+
for (const t of c.tools ?? [])
|
|
104
|
+
used.add(t);
|
|
105
|
+
}
|
|
106
|
+
// 0.6.0 §4.3 — a pool candidate's `tools` subset is a reference too:
|
|
107
|
+
// dropping a tool only a candidate names would silently shrink that
|
|
108
|
+
// candidate's advertised toolset.
|
|
109
|
+
for (const c of cli.agent.modelPool?.candidates ?? []) {
|
|
110
|
+
for (const t of c.tools ?? [])
|
|
111
|
+
used.add(t);
|
|
94
112
|
}
|
|
95
113
|
// Always-allow defaults: if any rule references a tool by exact name we
|
|
96
114
|
// count it; otherwise the original tool list serves as the
|
|
@@ -531,6 +549,252 @@ export function memoryIntegrityPass(ir) {
|
|
|
531
549
|
}
|
|
532
550
|
return ir;
|
|
533
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* 0.6.0 §4.3 — model-plan integrity check. VALIDATION-ONLY (no structural
|
|
554
|
+
* rewrite), mirroring `memoryIntegrityPass`'s posture, appended LAST in
|
|
555
|
+
* `VALIDATING_PASSES` so it runs unconditionally through `compile()` and in
|
|
556
|
+
* `crewhaus lint` via `DEFAULT_PIPELINE`. It returns the IR untouched when
|
|
557
|
+
* no model keys are present, so it cannot drift bytes. For every routed
|
|
558
|
+
* block that carries a `modelPool` (the agent block, workflow steps, graph
|
|
559
|
+
* nodes, crew roles, and the sub-agents hanging off any of them) and for
|
|
560
|
+
* the `models:` registry:
|
|
561
|
+
*
|
|
562
|
+
* - every candidate `tools` entry is SUBSET-ONLY: a builtin key must be
|
|
563
|
+
* one of the block's own `tools` (case-insensitive — tool registration
|
|
564
|
+
* lowercases), an `mcp__<server>__<tool|*>` selector must name a server
|
|
565
|
+
* the shape's `mcp_servers` declares, and `Consult` / `Escalate` exist
|
|
566
|
+
* only when some pool declares `strategy.modelDirected`; a block that
|
|
567
|
+
* carries no tool catalog admits no candidate `tools` at all;
|
|
568
|
+
* - candidate / profile `permissions` carry ONLY `deny` / `ask` (the §5.4
|
|
569
|
+
* restricted schema — a profile can narrow the shape's decisions, never
|
|
570
|
+
* widen them; the runtime's `narrowRuleSet` implements the decision-level
|
|
571
|
+
* meet, this pass pins the shape it is fed);
|
|
572
|
+
* - `enabled: false` leaves at least one routable candidate;
|
|
573
|
+
* - every strategy / rule / floor ROLE slot (`cascade.draft`,
|
|
574
|
+
* `cascade.escalateTo`, `committee.members[]`,
|
|
575
|
+
* `committee.escalateOnDisagreement`, `rules[].use`, `reward.floor.arm`)
|
|
576
|
+
* names a declared candidate tag or a candidate's arm id (its profile
|
|
577
|
+
* name, else its model string); `classifier.labels` keys are candidate
|
|
578
|
+
* tags; `policy: "classifier"` and a `classifier` block go together;
|
|
579
|
+
* - profile names match the registry grammar and every profile / pool
|
|
580
|
+
* blob survives a JSON round-trip UNCHANGED (emitters `JSON.stringify`
|
|
581
|
+
* the pool into bundles — the `memoryIntegrityPass` template).
|
|
582
|
+
*
|
|
583
|
+
* The "judge ≠ serving arm" check is deliberately NOT here (it is a
|
|
584
|
+
* `compile()` warning scoped to specs that opt into `models:` or a pool
|
|
585
|
+
* `strategy`, silenced by `allow_self_judge`), so a 0.5.8-valid
|
|
586
|
+
* self-judging pooled spec still compiles.
|
|
587
|
+
*/
|
|
588
|
+
const MODEL_PLAN_PROFILE_NAME_RE = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
589
|
+
const MODEL_DIRECTED_TOOLS = new Set(["Consult", "Escalate"]);
|
|
590
|
+
const MCP_TOOL_SELECTOR_RE = /^mcp__([^_].*?)__(.+)$/;
|
|
591
|
+
function modelPlanBlocks(ir) {
|
|
592
|
+
const out = [];
|
|
593
|
+
const withSubAgents = (path, block) => {
|
|
594
|
+
out.push([path, block]);
|
|
595
|
+
for (const sa of block.subAgents ?? []) {
|
|
596
|
+
out.push([
|
|
597
|
+
`${path}.sub_agents.${sa.name}`,
|
|
598
|
+
{ tools: sa.tools, toolCatalog: true, modelPool: sa.modelPool },
|
|
599
|
+
]);
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
switch (ir.target) {
|
|
603
|
+
case "cli":
|
|
604
|
+
withSubAgents("agent", {
|
|
605
|
+
tools: ir.tools,
|
|
606
|
+
toolCatalog: true,
|
|
607
|
+
modelPool: ir.agent.modelPool,
|
|
608
|
+
subAgents: ir.subAgents,
|
|
609
|
+
});
|
|
610
|
+
break;
|
|
611
|
+
case "channel":
|
|
612
|
+
withSubAgents("agent", {
|
|
613
|
+
tools: ir.tools,
|
|
614
|
+
toolCatalog: true,
|
|
615
|
+
modelPool: ir.agent.modelPool,
|
|
616
|
+
subAgents: ir.subAgents,
|
|
617
|
+
});
|
|
618
|
+
break;
|
|
619
|
+
case "managed":
|
|
620
|
+
withSubAgents("agent", {
|
|
621
|
+
tools: ir.tools ?? [],
|
|
622
|
+
toolCatalog: true,
|
|
623
|
+
modelPool: ir.agent.modelPool,
|
|
624
|
+
});
|
|
625
|
+
break;
|
|
626
|
+
case "pipeline":
|
|
627
|
+
withSubAgents("agent", {
|
|
628
|
+
tools: undefined,
|
|
629
|
+
toolCatalog: false,
|
|
630
|
+
modelPool: ir.agent.modelPool,
|
|
631
|
+
});
|
|
632
|
+
break;
|
|
633
|
+
case "research":
|
|
634
|
+
case "batch":
|
|
635
|
+
case "browser":
|
|
636
|
+
withSubAgents("agent", { tools: ir.tools, toolCatalog: true, modelPool: ir.agent.modelPool });
|
|
637
|
+
break;
|
|
638
|
+
case "workflow":
|
|
639
|
+
ir.steps.forEach((s, i) => withSubAgents(`steps[${i}]`, { tools: s.tools, toolCatalog: true, modelPool: s.modelPool }));
|
|
640
|
+
break;
|
|
641
|
+
case "graph":
|
|
642
|
+
for (const n of ir.nodes) {
|
|
643
|
+
withSubAgents(`nodes.${n.name}`, {
|
|
644
|
+
tools: n.tools,
|
|
645
|
+
toolCatalog: true,
|
|
646
|
+
modelPool: n.modelPool,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
break;
|
|
650
|
+
case "crew":
|
|
651
|
+
for (const r of ir.roles) {
|
|
652
|
+
withSubAgents(`roles.${r.name}`, {
|
|
653
|
+
tools: r.tools,
|
|
654
|
+
toolCatalog: true,
|
|
655
|
+
modelPool: r.modelPool,
|
|
656
|
+
subAgents: r.subAgents,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
break;
|
|
660
|
+
default:
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
return out;
|
|
664
|
+
}
|
|
665
|
+
/** A candidate's arm identity: its profile name, else its model string (§7.9). */
|
|
666
|
+
function armIdOf(c) {
|
|
667
|
+
return c.profile ?? c.model;
|
|
668
|
+
}
|
|
669
|
+
function checkProfilePermissions(path, permissions) {
|
|
670
|
+
if (permissions === undefined)
|
|
671
|
+
return;
|
|
672
|
+
for (const key of Object.keys(permissions)) {
|
|
673
|
+
if (key !== "deny" && key !== "ask") {
|
|
674
|
+
throw new IrPassError(`${path}.permissions.${key}: a model profile's permissions may only NARROW the shape's — deny and ask lists only (a profile can never widen what the shape allows)`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function checkCandidateTools(path, tools, block, mcpServers, modelDirected) {
|
|
679
|
+
if (tools === undefined)
|
|
680
|
+
return;
|
|
681
|
+
if (!block.toolCatalog) {
|
|
682
|
+
throw new IrPassError(`${path}.tools: this block registers no tool catalog, so a per-model tools list has nothing to narrow`);
|
|
683
|
+
}
|
|
684
|
+
const blockTools = new Set((block.tools ?? []).map((t) => t.toLowerCase()));
|
|
685
|
+
for (const [i, tool] of tools.entries()) {
|
|
686
|
+
if (tool.startsWith("mcp__")) {
|
|
687
|
+
const server = tool.match(MCP_TOOL_SELECTOR_RE)?.[1];
|
|
688
|
+
if (server === undefined) {
|
|
689
|
+
throw new IrPassError(`${path}.tools[${i}]: "${tool}" is not a valid MCP tool selector — use mcp__<server>__<tool> or mcp__<server>__*`);
|
|
690
|
+
}
|
|
691
|
+
if (!mcpServers.has(server)) {
|
|
692
|
+
throw new IrPassError(`${path}.tools[${i}]: "${tool}" names MCP server "${server}", which mcp_servers does not declare`);
|
|
693
|
+
}
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (MODEL_DIRECTED_TOOLS.has(tool)) {
|
|
697
|
+
if (!modelDirected) {
|
|
698
|
+
throw new IrPassError(`${path}.tools[${i}]: "${tool}" is registered only when a model_pool declares strategy.model_directed: true — no pool on this IR does`);
|
|
699
|
+
}
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (!blockTools.has(tool.toLowerCase())) {
|
|
703
|
+
throw new IrPassError(`${path}.tools[${i}]: "${tool}" is not one of the block's tools (${(block.tools ?? []).join(", ") || "none"}) — a per-model tools list can only narrow the block's toolset, never add to it`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function checkRoleSlot(path, value, tags, arms) {
|
|
708
|
+
if (tags.has(value) || arms.has(value))
|
|
709
|
+
return;
|
|
710
|
+
throw new IrPassError(`${path}: "${value}" is neither a candidate tag (${[...tags].join(", ") || "none"}) nor a candidate arm id (${[...arms].join(", ")}) of this model_pool`);
|
|
711
|
+
}
|
|
712
|
+
function checkModelPool(path, pool, block, mcpServers, modelDirected) {
|
|
713
|
+
const poolPath = `${path}.model_pool`;
|
|
714
|
+
if (!pool.candidates.some((c) => c.enabled !== false)) {
|
|
715
|
+
throw new IrPassError(`${poolPath}.candidates: every candidate is enabled: false — at least one must stay routable`);
|
|
716
|
+
}
|
|
717
|
+
const tags = new Set();
|
|
718
|
+
const arms = new Set();
|
|
719
|
+
pool.candidates.forEach((c, i) => {
|
|
720
|
+
const cpath = `${poolPath}.candidates[${i}]`;
|
|
721
|
+
for (const t of c.tags)
|
|
722
|
+
tags.add(t);
|
|
723
|
+
arms.add(armIdOf(c));
|
|
724
|
+
checkCandidateTools(cpath, c.tools, block, mcpServers, modelDirected);
|
|
725
|
+
checkProfilePermissions(cpath, c.permissions);
|
|
726
|
+
if (c.model.startsWith("$")) {
|
|
727
|
+
throw new IrPassError(`${cpath}.model: "${c.model}" is an unresolved profile reference — the compiler resolves every $ref at lower time; a direct-IR builder must carry the concrete model`);
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
if (pool.policy === "classifier" && pool.classifier === undefined) {
|
|
731
|
+
throw new IrPassError(`${poolPath}.policy: "classifier" requires a classifier block (model + labels) on the same model_pool`);
|
|
732
|
+
}
|
|
733
|
+
if (pool.classifier !== undefined) {
|
|
734
|
+
if (pool.policy !== "classifier") {
|
|
735
|
+
throw new IrPassError(`${poolPath}.classifier is declared but policy is "${pool.policy}" — the classifier runs only under policy: "classifier"`);
|
|
736
|
+
}
|
|
737
|
+
for (const label of Object.keys(pool.classifier.labels)) {
|
|
738
|
+
if (!tags.has(label)) {
|
|
739
|
+
throw new IrPassError(`${poolPath}.classifier.labels["${label}"]: every label must be a candidate tag (${[...tags].join(", ") || "none declared"})`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
for (const [i, rule] of (pool.rules ?? []).entries()) {
|
|
744
|
+
if (typeof rule.use === "string") {
|
|
745
|
+
checkRoleSlot(`${poolPath}.rules[${i}].use`, rule.use, tags, arms);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const st = pool.strategy;
|
|
749
|
+
if (st !== undefined) {
|
|
750
|
+
const sp = `${poolPath}.strategy`;
|
|
751
|
+
if (st.cascade !== undefined) {
|
|
752
|
+
checkRoleSlot(`${sp}.cascade.draft`, st.cascade.draft, tags, arms);
|
|
753
|
+
checkRoleSlot(`${sp}.cascade.escalateTo`, st.cascade.escalateTo, tags, arms);
|
|
754
|
+
}
|
|
755
|
+
if (st.committee !== undefined) {
|
|
756
|
+
st.committee.members.forEach((m, i) => checkRoleSlot(`${sp}.committee.members[${i}]`, m, tags, arms));
|
|
757
|
+
if (st.committee.escalateOnDisagreement !== undefined) {
|
|
758
|
+
checkRoleSlot(`${sp}.committee.escalateOnDisagreement`, st.committee.escalateOnDisagreement, tags, arms);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (pool.reward?.floor?.arm !== undefined) {
|
|
763
|
+
checkRoleSlot(`${poolPath}.reward.floor.arm`, pool.reward.floor.arm, tags, arms);
|
|
764
|
+
}
|
|
765
|
+
if (!jsonRoundTrips(pool)) {
|
|
766
|
+
throw new IrPassError(`${poolPath} is not JSON-serializable — emitters stringify the pool into bundles, so it must survive a JSON round-trip unchanged`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
export function modelPlanIntegrity(ir) {
|
|
770
|
+
const registry = ir.models;
|
|
771
|
+
const blocks = modelPlanBlocks(ir);
|
|
772
|
+
const pooled = blocks.filter(([, b]) => b.modelPool !== undefined);
|
|
773
|
+
if (registry === undefined && pooled.length === 0)
|
|
774
|
+
return ir;
|
|
775
|
+
if (registry !== undefined) {
|
|
776
|
+
for (const [name, profile] of Object.entries(registry)) {
|
|
777
|
+
if (!MODEL_PLAN_PROFILE_NAME_RE.test(name)) {
|
|
778
|
+
throw new IrPassError(`models.${name}: profile names must match /^[a-z][a-z0-9_-]{0,63}$/`);
|
|
779
|
+
}
|
|
780
|
+
checkProfilePermissions(`models.${name}`, profile.permissions);
|
|
781
|
+
if (profile.model.startsWith("$")) {
|
|
782
|
+
throw new IrPassError(`models.${name}.model: "${profile.model}" is an unresolved profile reference — profiles do not inherit from profiles`);
|
|
783
|
+
}
|
|
784
|
+
if (!jsonRoundTrips(profile)) {
|
|
785
|
+
throw new IrPassError(`models.${name} is not JSON-serializable — it must survive a JSON round-trip unchanged`);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const mcpServers = new Set(Object.keys(ir.mcp_servers ?? {}));
|
|
790
|
+
const modelDirected = pooled.some(([, b]) => b.modelPool?.strategy?.modelDirected === true);
|
|
791
|
+
for (const [path, block] of pooled) {
|
|
792
|
+
if (block.modelPool !== undefined) {
|
|
793
|
+
checkModelPool(path, block.modelPool, block, mcpServers, modelDirected);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return ir;
|
|
797
|
+
}
|
|
534
798
|
/**
|
|
535
799
|
* G45 (loop contract 0.4) — the VALIDATING passes: pure pass-throughs that
|
|
536
800
|
* throw `IrPassError` on a violation and never rewrite the IR. The compiler
|
|
@@ -544,6 +808,7 @@ export const VALIDATING_PASSES = Object.freeze([
|
|
|
544
808
|
transactionPolicyEnforcement,
|
|
545
809
|
wellFormednessCheck,
|
|
546
810
|
memoryIntegrityPass,
|
|
811
|
+
modelPlanIntegrity,
|
|
547
812
|
]);
|
|
548
813
|
export const DEFAULT_PIPELINE = Object.freeze([
|
|
549
814
|
deadToolElimination,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/ir-passes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Idempotent IR optimization passes (dead-tool-elimination, prompt-cache-prefix-sort, redundant-mcp-server-collapse, permission-rule-canonicalize)",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"test": "bun test src"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@crewhaus/errors": "0.
|
|
19
|
-
"@crewhaus/ir": "0.
|
|
18
|
+
"@crewhaus/errors": "0.6.0",
|
|
19
|
+
"@crewhaus/ir": "0.6.0"
|
|
20
20
|
},
|
|
21
21
|
"license": "Apache-2.0",
|
|
22
22
|
"author": {
|