@hiro-c/agent-gate 1.1.0 → 1.3.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 CHANGED
@@ -88,7 +88,10 @@ Full options: see [docs/config.md](docs/config.md) (TODO) or `AgentGatePluginCon
88
88
  | `AGENT_GATE_LOG` | `1` writes decisions to `~/.agent-gate/log.jsonl` |
89
89
  | `AGENT_GATE_API_KEY` | Use Anthropic API directly instead of `claude` CLI |
90
90
  | `AGENT_GATE_USE_SDK` | `1` prefers the Anthropic agent SDK over API/CLI (no API key needed; works best with daemon mode) |
91
+ | `AGENT_GATE_ON_ERROR` | `block` to fail-closed when a rule or AI client throws (default `allow`) |
91
92
  | `AGENT_GATE_DAEMON` | `1` routes through the daemon if it is running |
93
+ | `AGENT_GATE_CACHE_TTL_SEC` | Daemon decision cache TTL in seconds (default `60`) |
94
+ | `AGENT_GATE_CACHE_SIZE` | Daemon decision cache max entries (default `256`) |
92
95
 
93
96
  ## Supported AI tools
94
97
 
@@ -0,0 +1,36 @@
1
+ import { ValidationResult } from '../contracts/types/ValidationResult';
2
+ export interface CacheKey {
3
+ adapter: string;
4
+ toolName: string;
5
+ toolInput: Record<string, unknown>;
6
+ cwd: string;
7
+ }
8
+ export interface DecisionCacheOptions {
9
+ /** Time to live in seconds. Entries past this age miss. */
10
+ ttlSec: number;
11
+ /** Max entries before LRU eviction kicks in. */
12
+ maxEntries: number;
13
+ /** Clock injection for testing. Defaults to Date.now. */
14
+ now?: () => number;
15
+ }
16
+ /**
17
+ * In-process LRU + TTL cache of agent-gate decisions.
18
+ *
19
+ * Most useful inside `agent-gate daemon` where the cache persists
20
+ * across hook invocations. Same {adapter, toolName, toolInput, cwd}
21
+ * within the TTL skips the entire pipeline (deterministic engine,
22
+ * collector, AI call) and returns the prior verdict.
23
+ */
24
+ export declare class DecisionCache {
25
+ private readonly ttlMs;
26
+ private readonly maxEntries;
27
+ private readonly now;
28
+ private readonly store;
29
+ constructor(opts: DecisionCacheOptions);
30
+ private hash;
31
+ get(key: CacheKey): ValidationResult | null;
32
+ set(key: CacheKey, value: ValidationResult): void;
33
+ get size(): number;
34
+ clear(): void;
35
+ }
36
+ //# sourceMappingURL=DecisionCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DecisionCache.d.ts","sourceRoot":"","sources":["../../src/cache/DecisionCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AAEtE,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAA;IACd,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAA;IAClB,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAOD;;;;;;;GAOG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;gBAE1C,IAAI,EAAE,oBAAoB;IAMtC,OAAO,CAAC,IAAI;IASZ,GAAG,CAAC,GAAG,EAAE,QAAQ,GAAG,gBAAgB,GAAG,IAAI;IAc3C,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAWjD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DecisionCache = void 0;
4
+ /**
5
+ * In-process LRU + TTL cache of agent-gate decisions.
6
+ *
7
+ * Most useful inside `agent-gate daemon` where the cache persists
8
+ * across hook invocations. Same {adapter, toolName, toolInput, cwd}
9
+ * within the TTL skips the entire pipeline (deterministic engine,
10
+ * collector, AI call) and returns the prior verdict.
11
+ */
12
+ class DecisionCache {
13
+ ttlMs;
14
+ maxEntries;
15
+ now;
16
+ store = new Map();
17
+ constructor(opts) {
18
+ this.ttlMs = opts.ttlSec * 1000;
19
+ this.maxEntries = opts.maxEntries;
20
+ this.now = opts.now ?? Date.now;
21
+ }
22
+ hash(key) {
23
+ return JSON.stringify({
24
+ a: key.adapter,
25
+ t: key.toolName,
26
+ c: key.cwd,
27
+ i: canonicalize(key.toolInput),
28
+ });
29
+ }
30
+ get(key) {
31
+ const k = this.hash(key);
32
+ const entry = this.store.get(k);
33
+ if (!entry)
34
+ return null;
35
+ if (entry.expiresAt <= this.now()) {
36
+ this.store.delete(k);
37
+ return null;
38
+ }
39
+ // Refresh LRU order: re-insert.
40
+ this.store.delete(k);
41
+ this.store.set(k, entry);
42
+ return entry.value;
43
+ }
44
+ set(key, value) {
45
+ const k = this.hash(key);
46
+ this.store.delete(k);
47
+ this.store.set(k, { value, expiresAt: this.now() + this.ttlMs });
48
+ while (this.store.size > this.maxEntries) {
49
+ const oldest = this.store.keys().next().value;
50
+ if (oldest === undefined)
51
+ break;
52
+ this.store.delete(oldest);
53
+ }
54
+ }
55
+ get size() {
56
+ return this.store.size;
57
+ }
58
+ clear() {
59
+ this.store.clear();
60
+ }
61
+ }
62
+ exports.DecisionCache = DecisionCache;
63
+ /**
64
+ * Stable string representation of an arbitrary JSON-like input.
65
+ * Sorts object keys so key-order does not affect cache hits.
66
+ */
67
+ function canonicalize(value) {
68
+ if (Array.isArray(value)) {
69
+ return value.map(canonicalize);
70
+ }
71
+ if (value !== null && typeof value === 'object') {
72
+ const obj = value;
73
+ const sorted = {};
74
+ for (const k of Object.keys(obj).sort()) {
75
+ sorted[k] = canonicalize(obj[k]);
76
+ }
77
+ return sorted;
78
+ }
79
+ return value;
80
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"agent-gate.d.ts","sourceRoot":"","sources":["../../src/cli/agent-gate.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AAYtE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAsC7C,wBAAsB,GAAG,CACvB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAsGD,UAAU,UAAU;IAClB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAgCpD"}
1
+ {"version":3,"file":"agent-gate.d.ts","sourceRoot":"","sources":["../../src/cli/agent-gate.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AAYtE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAuC7C,wBAAsB,GAAG,CACvB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAuHD,UAAU,UAAU;IAClB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAgCpD"}
@@ -15,6 +15,7 @@ const formatFindings_1 = require("../doctor/formatFindings");
15
15
  const server_1 = require("../daemon/server");
16
16
  const client_1 = require("../daemon/client");
17
17
  const protocol_1 = require("../daemon/protocol");
18
+ const DecisionCache_1 = require("../cache/DecisionCache");
18
19
  const HELP_TEXT = `agent-gate — runtime enforcer for AI coding agent rules
19
20
 
20
21
  Usage:
@@ -75,6 +76,14 @@ function runHookMode(adapter) {
75
76
  }
76
77
  async function runDaemon() {
77
78
  const socketPath = process.env.AGENT_GATE_SOCKET_PATH ?? (0, protocol_1.defaultSocketPath)();
79
+ // Shared decision cache lives for the lifetime of the daemon, so every
80
+ // hook invocation benefits from prior verdicts.
81
+ const ttlSec = parseInt(process.env.AGENT_GATE_CACHE_TTL_SEC ?? '60', 10);
82
+ const maxEntries = parseInt(process.env.AGENT_GATE_CACHE_SIZE ?? '256', 10);
83
+ const cache = new DecisionCache_1.DecisionCache({
84
+ ttlSec: Number.isNaN(ttlSec) ? 60 : ttlSec,
85
+ maxEntries: Number.isNaN(maxEntries) ? 256 : maxEntries,
86
+ });
78
87
  const server = new server_1.DaemonServer({
79
88
  socketPath,
80
89
  handler: async (req) => {
@@ -89,6 +98,7 @@ async function runDaemon() {
89
98
  const result = await (0, processHookData_1.processHookData)(req.payload, {
90
99
  adapter,
91
100
  cwd: req.cwd,
101
+ cache,
92
102
  });
93
103
  return { output: adapter.formatResponse(result) };
94
104
  },
@@ -19,6 +19,14 @@ export type ConfigOptions = {
19
19
  * cost, so this is most useful when paired with `agent-gate daemon`.
20
20
  */
21
21
  useSdk?: boolean;
22
+ /**
23
+ * Behavior when a rule or model client throws unexpectedly.
24
+ * - "allow" (default): swallow the error and let the operation through.
25
+ * Optimized for developer ergonomics so flaky AI does not block work.
26
+ * - "block": turn the error into a block verdict. Recommended for
27
+ * production / enterprise pipelines that prefer fail-closed safety.
28
+ */
29
+ onError?: 'allow' | 'block';
22
30
  };
23
31
  export declare class Config {
24
32
  readonly model: string;
@@ -28,6 +36,7 @@ export declare class Config {
28
36
  readonly useSystemClaude: boolean;
29
37
  readonly reasonLang: string | undefined;
30
38
  readonly useSdk: boolean;
39
+ readonly onError: 'allow' | 'block';
31
40
  constructor(options?: ConfigOptions);
32
41
  get useApi(): boolean;
33
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,sBAAsB,CAAA;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,qBAAa,MAAM;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;gBAEZ,OAAO,CAAC,EAAE,aAAa;IAWnC,IAAI,MAAM,IAAI,OAAO,CAEpB;CACF"}
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,sBAAsB,CAAA;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAC5B,CAAA;AAED,qBAAa,MAAM;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAA;gBAEvB,OAAO,CAAC,EAAE,aAAa;IAenC,IAAI,MAAM,IAAI,OAAO,CAEpB;CACF"}
@@ -10,6 +10,7 @@ class Config {
10
10
  useSystemClaude;
11
11
  reasonLang;
12
12
  useSdk;
13
+ onError;
13
14
  constructor(options) {
14
15
  this.model = options?.model ?? process.env.AGENT_GATE_MODEL ?? exports.DEFAULT_MODEL;
15
16
  this.apiKey = options?.apiKey ?? process.env.AGENT_GATE_API_KEY;
@@ -19,6 +20,10 @@ class Config {
19
20
  this.useSystemClaude = options?.useSystemClaude ?? process.env.USE_SYSTEM_CLAUDE === 'true';
20
21
  this.reasonLang = options?.reasonLang ?? process.env.AGENT_GATE_REASON_LANG;
21
22
  this.useSdk = options?.useSdk ?? process.env.AGENT_GATE_USE_SDK === '1';
23
+ const envOnError = process.env.AGENT_GATE_ON_ERROR;
24
+ this.onError =
25
+ options?.onError ??
26
+ (envOnError === 'block' ? 'block' : 'allow');
22
27
  }
23
28
  get useApi() {
24
29
  return this.apiKey !== undefined && this.apiKey !== '';
@@ -7,5 +7,17 @@ export type EngineResult = {
7
7
  reason: string;
8
8
  ruleId: string;
9
9
  };
10
- export declare function runDeterministicRules(toolName: string, toolInput: Record<string, unknown>, rules: DeterministicRule[], ctx?: SessionContext): EngineResult;
10
+ export type OnErrorPolicy = 'allow' | 'block';
11
+ export interface RunDeterministicRulesOptions {
12
+ /**
13
+ * What to do when a rule's check function throws.
14
+ * - "allow" (default): swallow the exception and continue with the
15
+ * next rule. Compatible with prior versions.
16
+ * - "block": turn the exception into a block verdict and stop the
17
+ * engine. Suitable for enterprise / production use where the
18
+ * safest response to a misbehaving rule is to halt.
19
+ */
20
+ onError?: OnErrorPolicy;
21
+ }
22
+ export declare function runDeterministicRules(toolName: string, toolInput: Record<string, unknown>, rules: DeterministicRule[], ctx?: SessionContext, options?: RunDeterministicRulesOptions): EngineResult;
11
23
  //# sourceMappingURL=engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/deterministic/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AAElE,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,KAAK,EAAE,iBAAiB,EAAE,EAC1B,GAAG,CAAC,EAAE,cAAc,GACnB,YAAY,CAQd"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/deterministic/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AAElE,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,OAAO,CAAA;AAE7C,MAAM,WAAW,4BAA4B;IAC3C;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,KAAK,EAAE,iBAAiB,EAAE,EAC1B,GAAG,CAAC,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE,4BAA4B,GACrC,YAAY,CAuBd"}
@@ -1,9 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runDeterministicRules = runDeterministicRules;
4
- function runDeterministicRules(toolName, toolInput, rules, ctx) {
4
+ function runDeterministicRules(toolName, toolInput, rules, ctx, options) {
5
+ const onError = options?.onError ?? 'allow';
5
6
  for (const rule of rules) {
6
- const verdict = rule.check(toolName, toolInput, ctx);
7
+ let verdict;
8
+ try {
9
+ verdict = rule.check(toolName, toolInput, ctx);
10
+ }
11
+ catch (e) {
12
+ if (onError === 'block') {
13
+ const reason = e instanceof Error ? e.message : String(e);
14
+ return {
15
+ kind: 'block',
16
+ reason: `Rule "${rule.id}" failed: ${reason}. Failing closed.`,
17
+ ruleId: rule.id,
18
+ };
19
+ }
20
+ continue;
21
+ }
7
22
  if (verdict.kind === 'block') {
8
23
  return { kind: 'block', reason: verdict.reason, ruleId: rule.id };
9
24
  }
@@ -6,6 +6,7 @@ import { Config } from '../config/Config';
6
6
  import { DeterministicRule } from '../deterministic/types';
7
7
  import { Adapter } from '../adapters/Adapter';
8
8
  import { AgentGateConfig } from '../config/AgentGateConfig';
9
+ import { DecisionCache } from '../cache/DecisionCache';
9
10
  import { EventBus } from '../observability/eventBus';
10
11
  export interface CooldownStore {
11
12
  getLastTime(key: string): number;
@@ -32,6 +33,12 @@ export interface ProcessHookDataDeps {
32
33
  enabled: boolean;
33
34
  path?: string;
34
35
  };
36
+ /**
37
+ * Optional in-process decision cache. Most useful in daemon mode where
38
+ * the cache survives across hook invocations and short-circuits the
39
+ * entire pipeline on a hit.
40
+ */
41
+ cache?: DecisionCache;
35
42
  }
36
43
  export declare function processHookData(input: string, deps?: ProcessHookDataDeps): Promise<ValidationResult>;
37
44
  //# sourceMappingURL=processHookData.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"processHookData.d.ts","sourceRoot":"","sources":["../../src/hooks/processHookData.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAKzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAG1D,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE7C,OAAO,EACL,eAAe,EAEhB,MAAM,2BAA2B,CAAA;AAMlC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAWpD,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7C;AA4BD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,EAAE,CAAA;IACzC,WAAW,CAAC,EAAE,OAAO,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,YAAY,CAAA;IACjD,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IACxC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAaD,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAoI3B"}
1
+ {"version":3,"file":"processHookData.d.ts","sourceRoot":"","sources":["../../src/hooks/processHookData.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAKzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAG1D,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE7C,OAAO,EACL,eAAe,EAEhB,MAAM,2BAA2B,CAAA;AAMlC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAWpD,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7C;AA4BD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,EAAE,CAAA;IACzC,WAAW,CAAC,EAAE,OAAO,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,YAAY,CAAA;IACjD,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IACxC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C;;;;OAIG;IACH,KAAK,CAAC,EAAE,aAAa,CAAA;CACtB;AAaD,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CA0K3B"}
@@ -68,6 +68,18 @@ async function processHookData(input, deps) {
68
68
  const { toolName, toolInput } = parsed.action;
69
69
  // Resolve cwd early so the agent-gate config can be loaded relative to it.
70
70
  const cwd = deps?.cwd ?? process.cwd();
71
+ // Cache lookup: if a recent verdict for the exact same (adapter, tool,
72
+ // input, cwd) is still valid, return it without re-running the pipeline.
73
+ if (deps?.cache) {
74
+ const cached = deps.cache.get({
75
+ adapter: adapter.id,
76
+ toolName,
77
+ toolInput,
78
+ cwd,
79
+ });
80
+ if (cached)
81
+ return cached;
82
+ }
71
83
  const agentGateConfig = deps?.agentGateConfig ?? (0, AgentGateConfig_1.loadAgentGateConfig)(cwd);
72
84
  // Build SessionContext (history from adapter, projectRoot defaults to cwd
73
85
  // for v1; future work can detect the actual project root).
@@ -89,7 +101,7 @@ async function processHookData(input, deps) {
89
101
  // any cooldown or AI check.
90
102
  const deterministicRules = deps?.deterministicRules ??
91
103
  (0, defaultRules_1.buildDefaultDeterministicRules)(agentGateConfig);
92
- const ruleVerdict = (0, engine_1.runDeterministicRules)(toolName, toolInput, deterministicRules, sessionContext);
104
+ const ruleVerdict = (0, engine_1.runDeterministicRules)(toolName, toolInput, deterministicRules, sessionContext, { onError: config.onError });
93
105
  if (ruleVerdict.kind === 'block') {
94
106
  bus.emit({
95
107
  type: 'rule.fired',
@@ -108,7 +120,14 @@ async function processHookData(input, deps) {
108
120
  source: 'deterministic',
109
121
  ruleId: ruleVerdict.ruleId,
110
122
  });
111
- return { decision: 'block', reason: ruleVerdict.reason };
123
+ const verdict = {
124
+ decision: 'block',
125
+ reason: ruleVerdict.reason,
126
+ };
127
+ if (deps?.cache) {
128
+ deps.cache.set({ adapter: adapter.id, toolName, toolInput, cwd }, verdict);
129
+ }
130
+ return verdict;
112
131
  }
113
132
  // Cooldown check (file-based, persists across process invocations)
114
133
  const cooldownStore = config.cooldown > 0
@@ -125,6 +144,9 @@ async function processHookData(input, deps) {
125
144
  const collect = deps?.collectFn ?? collectRuleSources_1.collectRuleSources;
126
145
  const rules = collect(cwd);
127
146
  if (rules.length === 0) {
147
+ if (deps?.cache) {
148
+ deps.cache.set({ adapter: adapter.id, toolName, toolInput, cwd }, PASS);
149
+ }
128
150
  return PASS;
129
151
  }
130
152
  // Get model client
@@ -139,7 +161,9 @@ async function processHookData(input, deps) {
139
161
  rulesCount: rules.length,
140
162
  });
141
163
  const aiStart = Date.now();
142
- const result = await validate(rules, toolName, toolInput, modelClient);
164
+ const result = await validate(rules, toolName, toolInput, modelClient, {
165
+ onError: config.onError,
166
+ });
143
167
  const aiLatency = Date.now() - aiStart;
144
168
  // Update cooldown timestamp AFTER successful validation
145
169
  if (cooldownStore) {
@@ -163,6 +187,9 @@ async function processHookData(input, deps) {
163
187
  reason: result.reason,
164
188
  source,
165
189
  });
190
+ if (deps?.cache) {
191
+ deps.cache.set({ adapter: adapter.id, toolName, toolInput, cwd }, result);
192
+ }
166
193
  return result;
167
194
  }
168
195
  function createModelClient(config, cwd) {
package/dist/index.d.ts CHANGED
@@ -25,6 +25,8 @@ export type { AgentSdkClientOptions, AgentSdkQueryFn, } from './validation/model
25
25
  export { lintRuleSources } from './doctor/lintRuleSources';
26
26
  export { formatFindings } from './doctor/formatFindings';
27
27
  export type { Finding, FindingCode, Severity, } from './doctor/findings';
28
+ export { DecisionCache } from './cache/DecisionCache';
29
+ export type { CacheKey, DecisionCacheOptions, } from './cache/DecisionCache';
28
30
  export { DaemonServer } from './daemon/server';
29
31
  export type { DaemonServerOptions, DaemonHandler } from './daemon/server';
30
32
  export { sendToDaemon } from './daemon/client';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAG9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AAC1E,YAAY,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AACjE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC9E,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAClE,YAAY,EACV,cAAc,EACd,YAAY,GACb,MAAM,kCAAkC,CAAA;AAGzC,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AAGnE,YAAY,EACV,iBAAiB,EACjB,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAGrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAA;AAC/E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAA;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AACnE,YAAY,EACV,qBAAqB,EACrB,eAAe,GAChB,MAAM,oCAAoC,CAAA;AAG3C,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,YAAY,EACV,OAAO,EACP,WAAW,EACX,QAAQ,GACT,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EACL,iBAAiB,IAAI,uBAAuB,GAC7C,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,aAAa,EACb,cAAc,EACd,mBAAmB,GACpB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AACnE,YAAY,EACV,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,IAAI,GACL,MAAM,4BAA4B,CAAA;AAGnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAG9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AAC1E,YAAY,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AACjE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC9E,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAClE,YAAY,EACV,cAAc,EACd,YAAY,GACb,MAAM,kCAAkC,CAAA;AAGzC,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AAGnE,YAAY,EACV,iBAAiB,EACjB,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAGrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAA;AAC/E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAA;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AACnE,YAAY,EACV,qBAAqB,EACrB,eAAe,GAChB,MAAM,oCAAoC,CAAA;AAG3C,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,YAAY,EACV,OAAO,EACP,WAAW,EACX,QAAQ,GACT,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,YAAY,EACV,QAAQ,EACR,oBAAoB,GACrB,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EACL,iBAAiB,IAAI,uBAAuB,GAC7C,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,aAAa,EACb,cAAc,EACd,mBAAmB,GACpB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AACnE,YAAY,EACV,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,IAAI,GACL,MAAM,4BAA4B,CAAA;AAGnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.processHookData = exports.validator = exports.collectRuleSources = exports.JsonlFileSink = exports.EventBus = exports.defaultDaemonSocketPath = exports.sendToDaemon = exports.DaemonServer = exports.formatFindings = exports.lintRuleSources = exports.AgentSdkClient = exports.CompositeModelClient = exports.cursorAdapter = exports.claudeCodeAdapter = exports.buildDefaultDeterministicRules = exports.defaultDeterministicRules = exports.forbidFilePathPattern = exports.forbidContentPattern = exports.forbidCommandPattern = exports.HookDataSchema = exports.loadPluginConfig = exports.loadAgentGateConfig = exports.defineConfig = exports.Config = void 0;
3
+ exports.processHookData = exports.validator = exports.collectRuleSources = exports.JsonlFileSink = exports.EventBus = exports.defaultDaemonSocketPath = exports.sendToDaemon = exports.DaemonServer = exports.DecisionCache = exports.formatFindings = exports.lintRuleSources = exports.AgentSdkClient = exports.CompositeModelClient = exports.cursorAdapter = exports.claudeCodeAdapter = exports.buildDefaultDeterministicRules = exports.defaultDeterministicRules = exports.forbidFilePathPattern = exports.forbidContentPattern = exports.forbidCommandPattern = exports.HookDataSchema = exports.loadPluginConfig = exports.loadAgentGateConfig = exports.defineConfig = exports.Config = void 0;
4
4
  // Config
5
5
  var Config_1 = require("./config/Config");
6
6
  Object.defineProperty(exports, "Config", { enumerable: true, get: function () { return Config_1.Config; } });
@@ -35,6 +35,9 @@ var lintRuleSources_1 = require("./doctor/lintRuleSources");
35
35
  Object.defineProperty(exports, "lintRuleSources", { enumerable: true, get: function () { return lintRuleSources_1.lintRuleSources; } });
36
36
  var formatFindings_1 = require("./doctor/formatFindings");
37
37
  Object.defineProperty(exports, "formatFindings", { enumerable: true, get: function () { return formatFindings_1.formatFindings; } });
38
+ // Cache
39
+ var DecisionCache_1 = require("./cache/DecisionCache");
40
+ Object.defineProperty(exports, "DecisionCache", { enumerable: true, get: function () { return DecisionCache_1.DecisionCache; } });
38
41
  // Daemon
39
42
  var server_1 = require("./daemon/server");
40
43
  Object.defineProperty(exports, "DaemonServer", { enumerable: true, get: function () { return server_1.DaemonServer; } });
@@ -1,5 +1,9 @@
1
1
  import { ValidationResult } from '../contracts/types/ValidationResult';
2
2
  import { RuleSource } from '../contracts/types/RuleSource';
3
3
  import { IModelClient } from '../contracts/types/ModelClient';
4
- export declare function validator(rules: RuleSource[], toolName: string, toolInput: Record<string, unknown>, modelClient: IModelClient): Promise<ValidationResult>;
4
+ export interface ValidatorOptions {
5
+ /** Fail-closed behavior on model client failures. Default: "allow". */
6
+ onError?: 'allow' | 'block';
7
+ }
8
+ export declare function validator(rules: RuleSource[], toolName: string, toolInput: Record<string, unknown>, modelClient: IModelClient, options?: ValidatorOptions): Promise<ValidationResult>;
5
9
  //# sourceMappingURL=validator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/validation/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAQ7D,wBAAsB,SAAS,CAC7B,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,WAAW,EAAE,YAAY,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAa3B"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/validation/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAQ7D,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAC5B;AAED,wBAAsB,SAAS,CAC7B,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,WAAW,EAAE,YAAY,EACzB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAmB3B"}
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validator = validator;
4
4
  const context_1 = require("./prompts/context");
5
- async function validator(rules, toolName, toolInput, modelClient) {
5
+ async function validator(rules, toolName, toolInput, modelClient, options) {
6
6
  try {
7
7
  const prompt = (0, context_1.buildPrompt)(rules, toolName, toolInput);
8
8
  const response = await modelClient.ask(prompt);
@@ -10,6 +10,12 @@ async function validator(rules, toolName, toolInput, modelClient) {
10
10
  }
11
11
  catch (error) {
12
12
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
13
+ if (options?.onError === 'block') {
14
+ return {
15
+ decision: 'block',
16
+ reason: `Validation failed (failing closed): ${errorMessage}`,
17
+ };
18
+ }
13
19
  return {
14
20
  decision: undefined,
15
21
  reason: `Validation error (allowing operation): ${errorMessage}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiro-c/agent-gate",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Runtime rule enforcer for AI coding agents. Reads CLAUDE.md / AGENTS.md / .cursorrules and enforces them via Claude Code and Cursor hooks, with a deterministic safety baseline.",
5
5
  "author": "Hiro-Chiba",
6
6
  "license": "MIT",