@hiro-c/agent-gate 1.0.0 → 1.1.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
@@ -1,204 +1,101 @@
1
1
  # agent-gate
2
2
 
3
3
  [![CI](https://github.com/Hiro-Chiba/agent-gate/actions/workflows/ci.yml/badge.svg)](https://github.com/Hiro-Chiba/agent-gate/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/@hiro-c/agent-gate)](https://www.npmjs.com/package/@hiro-c/agent-gate)
4
5
 
5
- One natural-language rule source, enforced at runtime across multiple AI coding tools.
6
+ **Runtime rule enforcer for AI coding agents.** Reads your existing instruction files (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`, ...) and enforces them at hook time in Claude Code and Cursor.
6
7
 
7
- agent-gate reads the instruction files you already have (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `.cursor/rules/*.mdc`, `.clinerules`, `.windsurf/rules`, `.github/copilot-instructions.md`, `CONVENTIONS.md`) as a single combined rule set, then enforces them at hook time in Claude Code and Cursor. A deterministic safety baseline catches catastrophic operations before any AI call.
8
+ ## What it does
8
9
 
9
- ## Why
10
+ - **Stops catastrophic operations** before AI ever sees them: `rm -rf /`, writes to `.env` / `.ssh/*`, force-push to `main`, edits to `/etc`.
11
+ - **Aggregates 8 instruction file formats** into one rule set the AI uses to decide on the remaining cases.
12
+ - **Returns guidance, not denials**: block reasons describe the next correct step the agent should take.
13
+ - **Audits your rule files** with `agent-gate lint` (detects vague rules and ambiguous modifiers like "適切に" / "where possible").
10
14
 
11
- The AI coding tool landscape in 2026 has fragmented into many tools, each with its own instruction file format. Existing tooling either syncs rule files across tools (rulesync, symlinks) or enforces a single rule format at runtime (probity, tdd-guard), but not both. agent-gate sits in the gap: it accepts whatever natural-language instruction files you already maintain and enforces them across multiple AI tools through a single hook.
15
+ ## Install
12
16
 
13
- Two pain points the project is built to address:
14
-
15
- - **Rule forgetting** in long agent sessions, where context compression drops the rules from the prompt and the agent quietly drifts off-spec.
16
- - **Destructive operations** like `rm -rf $HOME` or force-pushing main, which AI judgment is too unreliable to catch consistently.
17
-
18
- ## Features
17
+ ```bash
18
+ npm install -g @hiro-c/agent-gate
19
+ agent-gate install
20
+ ```
19
21
 
20
- - Multi-source rule collection across 8 instruction file formats, surfaced to AI with per-source attribution.
21
- - Adapter pattern: same binary works in Claude Code (`--agent claude-code`, default) and Cursor 1.7 (`--agent cursor`).
22
- - Deterministic safety baseline with five built-in rules that fire before any AI call.
23
- - AI validation against the combined rule set when the safety baseline passes.
24
- - Per-rule disable and per-project customization via `.agent-gate.json` and `AGENT_GATE_DISABLED_RULES`.
25
- - Optional decision log (`AGENT_GATE_LOG=1`) and `agent-gate stats` for auditing.
26
- - Block reasons are guidance, not denials: the AI is instructed to describe the next correct step alongside the violated rule.
22
+ `agent-gate install` registers the Claude Code PreToolUse hook in `~/.claude/settings.json`. Restart Claude Code to activate. Use `agent-gate uninstall` to remove.
27
23
 
28
- ## Requirements
24
+ For Cursor 1.7, point your hook config at `agent-gate --agent cursor`.
29
25
 
30
- - Node.js >= 22.0.0
31
- - Claude Code or Cursor 1.7
26
+ ## How it works
32
27
 
33
- ## Installation
28
+ Every `Edit` / `Write` / `Bash` call from the agent goes through:
34
29
 
35
- ```bash
36
- git clone https://github.com/Hiro-Chiba/agent-gate.git
37
- cd agent-gate
38
- ./install.sh
30
+ ```
31
+ hook payload → adapter → deterministic rules → AI validation → verdict
32
+ (5 built-ins) (your rules)
39
33
  ```
40
34
 
41
- The install script handles dependency install, build, and Claude Code hook registration. Restart Claude Code to activate.
42
-
43
- To register against Cursor instead of Claude Code, run the binary with `--agent cursor` from your Cursor hook config. To remove the Claude Code hook, run `./uninstall.sh`.
44
-
45
- ## Configuration
46
-
47
- ### Environment variables
35
+ If a deterministic rule fires, AI is skipped. Otherwise agent-gate reads all instruction files in the project tree and asks the AI to validate the operation against them.
48
36
 
49
- | Variable | Default | Description |
50
- |---|---|---|
51
- | `AGENT_GATE_MODEL` | `claude-sonnet-4-6` | Model used for AI validation |
52
- | `AGENT_GATE_API_KEY` | (none) | Anthropic API key. Uses the API directly when set; otherwise spawns the Claude CLI |
53
- | `AGENT_GATE_COOLDOWN` | `0` | Cooldown in seconds between AI validations (deterministic rules always fire) |
54
- | `AGENT_GATE_DISABLED` | `false` | Set to `true` to disable the whole tool |
55
- | `AGENT_GATE_DISABLED_RULES` | (none) | Comma-separated rule ids to disable, merged with the config file |
56
- | `AGENT_GATE_LOG` | (none) | Set to `1` to append decisions to `~/.agent-gate/log.jsonl` |
57
- | `AGENT_GATE_REASON_LANG` | `auto` | Language for AI-generated `reason` text. `auto` matches the instruction files (English fallback when mixed). Pass `en`, `ja`, `zh`, `ko`, etc. to force a specific language |
58
- | `USE_SYSTEM_CLAUDE` | `false` | `true` forces PATH `claude` (default: `~/.claude/local/claude` with PATH fallback) |
37
+ ## Built-in safety rules
59
38
 
60
- ### Project config file
39
+ Run by default, disable per-rule in `.agent-gate.json` if needed.
61
40
 
62
- agent-gate looks for either a TypeScript / JavaScript config or a legacy JSON config, walking upward from the cwd until it finds one. Precedence (highest wins): `.agent-gate.config.ts` > `.mts` > `.mjs` > `.cjs` > `.js` > `.agent-gate.json`.
41
+ | Rule | Blocks |
42
+ |---|---|
43
+ | `prevent-rm-rf-root` | `rm -rf` on `/`, `$HOME`, `~`, `/etc`, `/usr`, `/var`, etc. (handles `sudo`, flag variants). |
44
+ | `prevent-secret-file-write` | `Edit`/`Write` to `.env*`, `.ssh/*`, `.aws/credentials`, `*.pem`, `*.key`, `id_rsa`. |
45
+ | `prevent-bash-secret-write` | Shell redirects to the same paths (`echo > .env`, `tee .ssh/id_rsa`). |
46
+ | `prevent-force-push-main` | `git push --force` to `main`, `master`, `develop`, `release`, etc. Allows `--force-with-lease`. |
47
+ | `prevent-system-path-write` | `Edit`/`Write` to `/etc`, `/usr`, `/System`, `/Library`. |
63
48
 
64
- #### TypeScript / JavaScript config
49
+ ## Config
65
50
 
66
- The full API including custom rules:
51
+ Drop a `.agent-gate.config.ts` (or `.js` / `.json`) at the project root:
67
52
 
68
53
  ```ts
69
- // .agent-gate.config.ts
70
- import { defineConfig, forbidCommandPattern, forbidFilePathPattern } from 'agent-gate'
54
+ import { defineConfig, forbidCommandPattern } from '@hiro-c/agent-gate'
71
55
 
72
56
  export default defineConfig({
73
57
  disabledRules: ['prevent-force-push-main'],
74
58
  protectedBranches: ['main', 'release'],
75
- extraSecretPathPrefixes: ['vault/', 'secrets/'],
76
59
  customRules: [
77
60
  forbidCommandPattern({
78
61
  id: 'no-drop-table',
79
62
  match: /drop\s+table/i,
80
- reason: 'DROP TABLE is forbidden. Use a migration instead.',
81
- }),
82
- forbidFilePathPattern({
83
- id: 'no-prod-config',
84
- match: /production\.ya?ml$/,
85
- reason: 'Production config edits go through ops review.',
63
+ reason: 'DROP TABLE is forbidden. Use a migration.',
86
64
  }),
87
65
  ],
88
66
  })
89
67
  ```
90
68
 
91
- | Field | Effect |
92
- |---|---|
93
- | `disabledRules` | List of rule ids that will not run. Merged with `AGENT_GATE_DISABLED_RULES`. |
94
- | `protectedBranches` | Overrides the default list used by `prevent-force-push-main`. |
95
- | `extraSecretPathPrefixes` | Additional path substrings treated as secret targets by `prevent-secret-file-write`. |
96
- | `customRules` | User-defined `DeterministicRule[]` appended after the built-ins. Use the `forbidCommandPattern` / `forbidContentPattern` / `forbidFilePathPattern` factories or hand-write your own. |
97
-
98
- #### Legacy JSON config
99
-
100
- Still works for simple cases:
101
-
102
- ```json
103
- {
104
- "disabled_rules": ["prevent-force-push-main"],
105
- "protected_branches": ["main", "release"],
106
- "extra_secret_paths": ["vault/", "secrets/"]
107
- }
108
- ```
109
-
110
- JSON cannot express custom rules. Migrate to the TS/JS form when you need them.
69
+ Full options: see [docs/config.md](docs/config.md) (TODO) or `AgentGatePluginConfig` in the source.
111
70
 
112
- ## How It Works
71
+ ## CLI
113
72
 
114
- 1. The AI coding tool fires a pre-tool-use hook with its vendor-specific JSON payload.
115
- 2. The selected adapter parses that payload into a normalized `Action`.
116
- 3. Deterministic safety rules run first. Catastrophic patterns are blocked here without calling AI.
117
- 4. If the safety baseline passes, agent-gate collects all 8 instruction file formats present in the project tree.
118
- 5. The AI validates the operation against the combined rule set, with each source attributed by kind.
119
- 6. The verdict (block + guidance, or allow) is returned through the adapter's response formatter.
120
-
121
- ## Built-in Safety Rules
122
-
123
- | Rule | Blocks |
73
+ | Command | What it does |
124
74
  |---|---|
125
- | `prevent-rm-rf-root` | Recursive `rm` on `/`, `$HOME`, `~`, `/etc`, `/usr`, `/var`, and other catastrophic paths. Handles `sudo` prefix and flag variants (`-rf`, `-fr`, `-Rf`). |
126
- | `prevent-secret-file-write` | `Edit`/`Write` on `.env*` (non-template), `.ssh/*`, `.aws/credentials`, `*.pem`, `*.key`, `id_rsa`, `.netrc`. |
127
- | `prevent-bash-secret-write` | Shell redirects to the same secret paths via `>`, `>>`, or `tee`. |
128
- | `prevent-force-push-main` | `git push --force` or `-f` to `main`, `master`, `develop`, `production`, `release`, `stable`. Allows `--force-with-lease`. |
129
- | `prevent-system-path-write` | `Edit`/`Write` to `/etc`, `/usr`, `/var`, `/System`, `/Library`, `/opt`, and other system-owned paths. |
130
-
131
- Each rule can be disabled individually through `.agent-gate.json` or the env var.
132
-
133
- ## Supported Instruction File Formats
75
+ | `agent-gate` | Run as hook (reads stdin, used internally) |
76
+ | `agent-gate install` / `uninstall` | Register or remove the Claude Code hook |
77
+ | `agent-gate lint` | Audit instruction files for ambiguity, emptiness, missing rules |
78
+ | `agent-gate stats` | Summarize the decision log (after `AGENT_GATE_LOG=1`) |
79
+ | `agent-gate daemon` | Long-lived server on a Unix socket (opt-in speedup, set `AGENT_GATE_DAEMON=1`) |
134
80
 
135
- agent-gate aggregates rules from any combination of:
81
+ ## Environment
136
82
 
137
- - `CLAUDE.md` (Claude Code)
138
- - `AGENTS.md` (cross-tool spec backed by the Linux Foundation Agentic AI Foundation)
139
- - `.cursorrules` (Cursor legacy)
140
- - `.cursor/rules/*.mdc` (Cursor current)
141
- - `.clinerules/*.md` (Cline)
142
- - `.windsurf/rules/*.md` (Windsurf)
143
- - `.github/copilot-instructions.md` (GitHub Copilot)
144
- - `CONVENTIONS.md` (Aider)
145
-
146
- You do not need to choose. Maintain whichever file your team already uses; agent-gate reads them all.
147
-
148
- ## Supported AI Coding Tools
149
-
150
- agent-gate enforces in any tool that exposes a pre-tool-use hook. As of v1:
151
-
152
- - Claude Code (mature). `agent-gate --agent claude-code`, the default.
153
- - Cursor 1.7 (beta). `agent-gate --agent cursor`. Payload mapping is best-effort against public docs.
154
-
155
- Tools without a hook surface (Copilot, Cline, Aider, Codex web, Replit, Devin) can still benefit from agent-gate as a rule source linter or via downstream sync (rulesync, symlinks), but cannot be enforced at runtime.
156
-
157
- ## CLAUDE.md Doctor
158
-
159
- Run `agent-gate lint` from a project root to audit your instruction files for AI-friendliness. The doctor walks the same 8 file formats the runtime reads, then surfaces:
160
-
161
- - **Empty files** that would make the AI think no rules exist.
162
- - **Files with no concrete rules** (no imperatives, no bullets, no numbered items).
163
- - **Ambiguous modifiers** like "where possible", "as needed", "適切に", "なるべく", "可能な限り", "必要に応じて". AI judgment cannot enforce these reliably; the doctor suggests replacing them with a concrete condition or threshold.
164
-
165
- ```text
166
- $ agent-gate lint
167
- /p/CLAUDE.md
168
- [warning] no-concrete-rules: No imperatives ...
169
- [info] ambiguous-modifier (line 5): Ambiguous modifier "適切に" ...
170
- > - エラーは適切に扱う
171
-
172
- 1 finding.
173
- ```
174
-
175
- Exit code is 1 if any finding has severity `error`, otherwise 0, so the command can run in CI.
176
-
177
- ## Daemon mode
178
-
179
- Each hook invocation normally spawns a fresh Node process (cold start ~300ms). For users that fire hooks at high frequency, agent-gate can run as a long-lived daemon on a Unix socket and let hook invocations reuse the warm process.
180
-
181
- ```bash
182
- # Terminal 1: start the daemon (foreground; manage with systemd / launchctl / tmux in production).
183
- agent-gate daemon
184
-
185
- # Terminal 2 (or in your hook config):
186
- AGENT_GATE_DAEMON=1 agent-gate
187
- ```
188
-
189
- When `AGENT_GATE_DAEMON=1`, the hook tries the socket first and transparently falls back to direct mode if the daemon is unreachable. Set `AGENT_GATE_SOCKET_PATH` to override the default `$TMPDIR/agent-gate.sock`.
190
-
191
- The daemon is opt-in. Users not setting `AGENT_GATE_DAEMON=1` keep the existing one-shot behavior.
192
-
193
- ## Observability
194
-
195
- Set `AGENT_GATE_LOG=1` to append every decision to `~/.agent-gate/log.jsonl`. Each line is a JSON object with timestamp, adapter, tool, decision, reason, source (`deterministic` / `ai`), and `ruleId` when a deterministic rule fired.
83
+ | Var | Effect |
84
+ |---|---|
85
+ | `AGENT_GATE_DISABLED` | Skip all checks |
86
+ | `AGENT_GATE_DISABLED_RULES` | Comma-separated rule ids to skip |
87
+ | `AGENT_GATE_REASON_LANG` | AI reason language: `auto` (default) / `en` / `ja` / etc. |
88
+ | `AGENT_GATE_LOG` | `1` writes decisions to `~/.agent-gate/log.jsonl` |
89
+ | `AGENT_GATE_API_KEY` | Use Anthropic API directly instead of `claude` CLI |
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_DAEMON` | `1` routes through the daemon if it is running |
196
92
 
197
- Run `agent-gate stats` for a summary: total decisions, block percentage, breakdown by source, adapter, tool, and rule id.
93
+ ## Supported AI tools
198
94
 
199
- ## Network Access
95
+ - **Claude Code** (mature, default).
96
+ - **Cursor 1.7** (beta, `--agent cursor`; payload mapping is best-effort against public docs).
200
97
 
201
- agent-gate only communicates with Anthropic endpoints, either directly via the Anthropic API (when `AGENT_GATE_API_KEY` is set) or indirectly through the Claude CLI subprocess. It does not contact any other external services and does not send telemetry. Deterministic rules run entirely locally.
98
+ Other tools (Copilot, Cline, Aider, Codex web, Replit, Devin) lack a hook surface and cannot be enforced at runtime.
202
99
 
203
100
  ## License
204
101
 
@@ -12,6 +12,13 @@ export type ConfigOptions = {
12
12
  * - "en", "ja", "zh", "ko", etc.: write reasons in that language.
13
13
  */
14
14
  reasonLang?: string;
15
+ /**
16
+ * Prefer Anthropic's agent SDK (`@anthropic-ai/claude-agent-sdk`) as the
17
+ * first model client. Reuses the host process's Claude auth, removing
18
+ * the need for AGENT_GATE_API_KEY. The SDK has noticeable cold-start
19
+ * cost, so this is most useful when paired with `agent-gate daemon`.
20
+ */
21
+ useSdk?: boolean;
15
22
  };
16
23
  export declare class Config {
17
24
  readonly model: string;
@@ -20,6 +27,7 @@ export declare class Config {
20
27
  readonly disabled: boolean;
21
28
  readonly useSystemClaude: boolean;
22
29
  readonly reasonLang: string | undefined;
30
+ readonly useSdk: boolean;
23
31
  constructor(options?: ConfigOptions);
24
32
  get useApi(): boolean;
25
33
  }
@@ -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;CACpB,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;gBAE3B,OAAO,CAAC,EAAE,aAAa;IAUnC,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;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"}
@@ -9,6 +9,7 @@ class Config {
9
9
  disabled;
10
10
  useSystemClaude;
11
11
  reasonLang;
12
+ useSdk;
12
13
  constructor(options) {
13
14
  this.model = options?.model ?? process.env.AGENT_GATE_MODEL ?? exports.DEFAULT_MODEL;
14
15
  this.apiKey = options?.apiKey ?? process.env.AGENT_GATE_API_KEY;
@@ -17,6 +18,7 @@ class Config {
17
18
  this.disabled = options?.disabled ?? process.env.AGENT_GATE_DISABLED === 'true';
18
19
  this.useSystemClaude = options?.useSystemClaude ?? process.env.USE_SYSTEM_CLAUDE === 'true';
19
20
  this.reasonLang = options?.reasonLang ?? process.env.AGENT_GATE_REASON_LANG;
21
+ this.useSdk = options?.useSdk ?? process.env.AGENT_GATE_USE_SDK === '1';
20
22
  }
21
23
  get useApi() {
22
24
  return this.apiKey !== undefined && this.apiKey !== '';
@@ -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;AAIzC,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,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"}
@@ -11,6 +11,7 @@ const Config_1 = require("../config/Config");
11
11
  const ClaudeCli_1 = require("../validation/models/ClaudeCli");
12
12
  const AnthropicApi_1 = require("../validation/models/AnthropicApi");
13
13
  const CompositeModelClient_1 = require("../validation/models/CompositeModelClient");
14
+ const AgentSdkClient_1 = require("../validation/models/AgentSdkClient");
14
15
  const engine_1 = require("../deterministic/engine");
15
16
  const defaultRules_1 = require("../deterministic/defaultRules");
16
17
  const adapter_1 = require("../adapters/claude-code/adapter");
@@ -165,11 +166,20 @@ async function processHookData(input, deps) {
165
166
  return result;
166
167
  }
167
168
  function createModelClient(config, cwd) {
168
- // Build a fallback chain: the most-preferred client comes first, with
169
- // ClaudeCli as a secondary path. The CompositeModelClient tries each in
170
- // order; if the first fails, the next runs. The validator's outer
171
- // try/catch fail-opens when every client has failed.
169
+ // Build a fallback chain: the most-preferred client comes first.
170
+ // The CompositeModelClient tries each in order; if one fails, the next
171
+ // runs. The validator's outer try/catch fail-opens if every client has
172
+ // failed.
173
+ //
174
+ // Order: AgentSdkClient (when AGENT_GATE_USE_SDK=1) -> AnthropicApi
175
+ // (when an API key is set) -> ClaudeCli (always available as a fallback).
176
+ // AgentSdkClient is preferred when enabled because it reuses the host
177
+ // agent's existing Claude authentication, removing the need for a
178
+ // separate API key.
172
179
  const clients = [];
180
+ if (config.useSdk) {
181
+ clients.push(new AgentSdkClient_1.AgentSdkClient({ config }));
182
+ }
173
183
  if (config.useApi) {
174
184
  clients.push(new AnthropicApi_1.AnthropicApi(config));
175
185
  }
package/dist/index.d.ts CHANGED
@@ -20,6 +20,8 @@ export { cursorAdapter } from './adapters/cursor/adapter';
20
20
  export type { Adapter, ReadHistoryOptions } from './adapters/Adapter';
21
21
  export { CompositeModelClient } from './validation/models/CompositeModelClient';
22
22
  export type { CompositeModelClientOptions } from './validation/models/CompositeModelClient';
23
+ export { AgentSdkClient } from './validation/models/AgentSdkClient';
24
+ export type { AgentSdkClientOptions, AgentSdkQueryFn, } from './validation/models/AgentSdkClient';
23
25
  export { lintRuleSources } from './doctor/lintRuleSources';
24
26
  export { formatFindings } from './doctor/formatFindings';
25
27
  export type { Finding, FindingCode, Severity, } from './doctor/findings';
@@ -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;AAG3F,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,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.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.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; } });
@@ -28,6 +28,8 @@ Object.defineProperty(exports, "cursorAdapter", { enumerable: true, get: functio
28
28
  // Model clients (for users wiring up custom AI fallback chains)
29
29
  var CompositeModelClient_1 = require("./validation/models/CompositeModelClient");
30
30
  Object.defineProperty(exports, "CompositeModelClient", { enumerable: true, get: function () { return CompositeModelClient_1.CompositeModelClient; } });
31
+ var AgentSdkClient_1 = require("./validation/models/AgentSdkClient");
32
+ Object.defineProperty(exports, "AgentSdkClient", { enumerable: true, get: function () { return AgentSdkClient_1.AgentSdkClient; } });
31
33
  // Doctor (CLAUDE.md linter)
32
34
  var lintRuleSources_1 = require("./doctor/lintRuleSources");
33
35
  Object.defineProperty(exports, "lintRuleSources", { enumerable: true, get: function () { return lintRuleSources_1.lintRuleSources; } });
@@ -0,0 +1,40 @@
1
+ import { IModelClient } from '../../contracts/types/ModelClient';
2
+ import { Config } from '../../config/Config';
3
+ /**
4
+ * Shape of the `query` export from `@anthropic-ai/claude-agent-sdk`,
5
+ * as documented by Anthropic. The SDK returns an AsyncIterable of
6
+ * streamed messages and accepts a small set of options.
7
+ */
8
+ export type AgentSdkQueryFn = (args: {
9
+ prompt: string;
10
+ options?: {
11
+ model?: string;
12
+ allowedTools?: string[];
13
+ permissionMode?: 'default' | 'bypassPermissions' | 'dontAsk';
14
+ maxTurns?: number;
15
+ };
16
+ }) => AsyncIterable<unknown>;
17
+ export interface AgentSdkClientOptions {
18
+ config: Config;
19
+ /**
20
+ * Override the SDK loader. Production builds default to a dynamic
21
+ * import of `@anthropic-ai/claude-agent-sdk`; tests inject a fake.
22
+ */
23
+ loadQuery?: () => Promise<AgentSdkQueryFn>;
24
+ }
25
+ /**
26
+ * IModelClient that runs validation through Anthropic's agent SDK
27
+ * (`@anthropic-ai/claude-agent-sdk`). Reuses the host process's
28
+ * existing Claude authentication so no separate API key is required.
29
+ *
30
+ * Tradeoff: the SDK has noticeable cold-start cost on each call.
31
+ * Recommended paired with `agent-gate daemon` so the SDK initialization
32
+ * is amortized across hook invocations.
33
+ */
34
+ export declare class AgentSdkClient implements IModelClient {
35
+ private readonly config;
36
+ private readonly loadQuery;
37
+ constructor(opts: AgentSdkClientOptions);
38
+ ask(prompt: string): Promise<string>;
39
+ }
40
+ //# sourceMappingURL=AgentSdkClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentSdkClient.d.ts","sourceRoot":"","sources":["../../../src/validation/models/AgentSdkClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAA;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAG5C;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;QACvB,cAAc,CAAC,EAAE,SAAS,GAAG,mBAAmB,GAAG,SAAS,CAAA;QAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;CACF,KAAK,aAAa,CAAC,OAAO,CAAC,CAAA;AAE5B,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAA;CAC3C;AAED;;;;;;;;GAQG;AACH,qBAAa,cAAe,YAAW,YAAY;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAE9C,IAAI,EAAE,qBAAqB;IAKjC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAgC3C"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentSdkClient = void 0;
4
+ const system_prompt_1 = require("../prompts/system-prompt");
5
+ /**
6
+ * IModelClient that runs validation through Anthropic's agent SDK
7
+ * (`@anthropic-ai/claude-agent-sdk`). Reuses the host process's
8
+ * existing Claude authentication so no separate API key is required.
9
+ *
10
+ * Tradeoff: the SDK has noticeable cold-start cost on each call.
11
+ * Recommended paired with `agent-gate daemon` so the SDK initialization
12
+ * is amortized across hook invocations.
13
+ */
14
+ class AgentSdkClient {
15
+ config;
16
+ loadQuery;
17
+ constructor(opts) {
18
+ this.config = opts.config;
19
+ this.loadQuery = opts.loadQuery ?? defaultLoadQuery;
20
+ }
21
+ async ask(prompt) {
22
+ let query;
23
+ try {
24
+ query = await this.loadQuery();
25
+ }
26
+ catch (e) {
27
+ const msg = e instanceof Error ? e.message : String(e);
28
+ throw new Error(`Agent SDK loader failed; install @anthropic-ai/claude-agent-sdk or fall back to another client. Underlying: ${msg}`);
29
+ }
30
+ const fullPrompt = `${(0, system_prompt_1.getSystemPrompt)(this.config.reasonLang)}\n\n${prompt}`;
31
+ let lastText = '';
32
+ for await (const message of query({
33
+ prompt: fullPrompt,
34
+ options: {
35
+ model: this.config.model,
36
+ allowedTools: [],
37
+ permissionMode: 'bypassPermissions',
38
+ maxTurns: 1,
39
+ },
40
+ })) {
41
+ const t = extractText(message);
42
+ if (t)
43
+ lastText = t;
44
+ }
45
+ if (!lastText) {
46
+ throw new Error('No response from agent SDK stream');
47
+ }
48
+ return lastText;
49
+ }
50
+ }
51
+ exports.AgentSdkClient = AgentSdkClient;
52
+ async function defaultLoadQuery() {
53
+ const mod = (await import('@anthropic-ai/claude-agent-sdk'));
54
+ if (typeof mod.query !== 'function') {
55
+ throw new Error('@anthropic-ai/claude-agent-sdk did not export query()');
56
+ }
57
+ return mod.query;
58
+ }
59
+ /**
60
+ * Pulls assistant text out of one streamed message. The SDK emits a
61
+ * mix of system / assistant / result envelopes; we cover the common
62
+ * shapes and ignore the rest.
63
+ */
64
+ function extractText(message) {
65
+ if (typeof message !== 'object' || message === null)
66
+ return null;
67
+ const m = message;
68
+ if (typeof m.result === 'string' && m.result.length > 0)
69
+ return m.result;
70
+ const inner = m.message;
71
+ if (typeof inner === 'object' && inner !== null) {
72
+ const content = inner.content;
73
+ if (Array.isArray(content)) {
74
+ const text = content
75
+ .map((c) => {
76
+ if (typeof c === 'object' && c !== null) {
77
+ const block = c;
78
+ if (typeof block.text === 'string')
79
+ return block.text;
80
+ }
81
+ return null;
82
+ })
83
+ .filter((s) => Boolean(s))
84
+ .join('\n');
85
+ if (text.length > 0)
86
+ return text;
87
+ }
88
+ }
89
+ if (typeof m.text === 'string' && m.text.length > 0)
90
+ return m.text;
91
+ return null;
92
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiro-c/agent-gate",
3
- "version": "1.0.0",
3
+ "version": "1.1.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",
@@ -55,7 +55,7 @@
55
55
  "prepublishOnly": "npm run checks && npm run build"
56
56
  },
57
57
  "dependencies": {
58
- "@anthropic-ai/sdk": "^0.90.0",
58
+ "@anthropic-ai/sdk": "^0.93.0",
59
59
  "jiti": "^2.7.0",
60
60
  "zod": "^4.3.6"
61
61
  },
@@ -63,5 +63,8 @@
63
63
  "@types/node": "^25.6.0",
64
64
  "typescript": "^6.0.3",
65
65
  "vitest": "^4.1.4"
66
+ },
67
+ "optionalDependencies": {
68
+ "@anthropic-ai/claude-agent-sdk": "^0.3.0"
66
69
  }
67
70
  }
@@ -1,3 +0,0 @@
1
- import { ClaudeMdFile } from '../contracts/types/ClaudeMdFile';
2
- export declare function collectClaudeMd(cwd: string): ClaudeMdFile[];
3
- //# sourceMappingURL=collectClaudeMd.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"collectClaudeMd.d.ts","sourceRoot":"","sources":["../../src/collector/collectClaudeMd.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAe9D,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAY3D"}
@@ -1,87 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.collectClaudeMd = collectClaudeMd;
4
- const fs_1 = require("fs");
5
- const path_1 = require("path");
6
- const EXCLUDED_DIRS = new Set([
7
- 'node_modules',
8
- '.git',
9
- 'target',
10
- '.venv',
11
- 'vendor',
12
- '__pycache__',
13
- 'dist',
14
- 'build',
15
- ]);
16
- const MAX_DEPTH = 3;
17
- function collectClaudeMd(cwd) {
18
- const upward = collectUpward(cwd);
19
- const downward = collectDownward(cwd);
20
- // Deduplicate: upward already includes cwd/CLAUDE.md
21
- const upwardPaths = new Set(upward.map((f) => f.path));
22
- const combined = [
23
- ...upward,
24
- ...downward.filter((f) => !upwardPaths.has(f.path)),
25
- ];
26
- return combined;
27
- }
28
- function collectUpward(cwd) {
29
- const files = [];
30
- let dir = cwd;
31
- while (true) {
32
- const claudeMdPath = (0, path_1.join)(dir, 'CLAUDE.md');
33
- const content = readFileSafe(claudeMdPath);
34
- if (content !== null) {
35
- files.push({ path: claudeMdPath, content });
36
- }
37
- const parent = (0, path_1.dirname)(dir);
38
- if (parent === dir)
39
- break;
40
- dir = parent;
41
- }
42
- return files;
43
- }
44
- function collectDownward(cwd) {
45
- const files = [];
46
- walkDir(cwd, 0, files);
47
- return files;
48
- }
49
- function walkDir(dir, depth, files) {
50
- if (depth > MAX_DEPTH)
51
- return;
52
- let entries;
53
- try {
54
- entries = (0, fs_1.readdirSync)(dir);
55
- }
56
- catch {
57
- return;
58
- }
59
- for (const entry of entries) {
60
- if (EXCLUDED_DIRS.has(entry))
61
- continue;
62
- const fullPath = (0, path_1.join)(dir, entry);
63
- if (entry === 'CLAUDE.md') {
64
- const content = readFileSafe(fullPath);
65
- if (content !== null) {
66
- files.push({ path: fullPath, content });
67
- }
68
- continue;
69
- }
70
- try {
71
- if ((0, fs_1.statSync)(fullPath).isDirectory()) {
72
- walkDir(fullPath, depth + 1, files);
73
- }
74
- }
75
- catch {
76
- // Skip inaccessible entries
77
- }
78
- }
79
- }
80
- function readFileSafe(filePath) {
81
- try {
82
- return (0, fs_1.readFileSync)(filePath, 'utf-8');
83
- }
84
- catch {
85
- return null;
86
- }
87
- }
@@ -1,5 +0,0 @@
1
- export type ClaudeMdFile = {
2
- path: string;
3
- content: string;
4
- };
5
- //# sourceMappingURL=ClaudeMdFile.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ClaudeMdFile.d.ts","sourceRoot":"","sources":["../../../src/contracts/types/ClaudeMdFile.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA"}
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });