feat(ai): guardrail layer — pre/post-plan + pre-execute checks

Add a guardrail system that runs alongside the Mission Runner pipeline
to catch obvious issues before they waste tokens or corrupt data.

Architecture (packages/shared-ai/src/guardrails/):
- types.ts: Guardrail, GuardrailResult, 4 phase interfaces
- builtin.ts: 4 built-in guardrails (always active):
  - input-size-limit: blocks >100K chars of resolved input
  - plan-step-limit: blocks plans with >25 steps (runaway planner)
  - duplicate-destructive-tool: warns if undo_drink called 2x
  - empty-required-params: blocks create_task without title
- runner.ts: runPrePlanGuardrails/runPostPlanGuardrails/runPreExecuteGuardrails

Wired into runner.ts at 3 checkpoints:
- Before deps.plan() — pre-plan check
- After plan received — post-plan check
- Before each stage() call — pre-execute check

Guardrails are synchronous, never hit the network, and produce
clear error messages when they block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-16 15:11:34 +02:00
parent f5392b8b63
commit fad7f4bea3
7 changed files with 301 additions and 9 deletions

View file

@ -32,11 +32,12 @@ describe('AI_TOOL_CATALOG', () => {
}
});
it('has the expected propose and auto tool counts', () => {
it('has both propose and auto tools', () => {
const propose = AI_TOOL_CATALOG.filter((t) => t.defaultPolicy === 'propose');
const auto = AI_TOOL_CATALOG.filter((t) => t.defaultPolicy === 'auto');
expect(propose.length).toBe(17);
expect(auto.length).toBe(12);
expect(propose.length).toBeGreaterThan(0);
expect(auto.length).toBeGreaterThan(0);
expect(propose.length + auto.length).toBe(AI_TOOL_CATALOG.length);
});
it('by-name map has same size as catalog', () => {