@hiro-c/agent-gate 1.0.0 → 1.0.2

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,100 @@
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_DAEMON` | `1` routes through the daemon if it is running |
196
91
 
197
- Run `agent-gate stats` for a summary: total decisions, block percentage, breakdown by source, adapter, tool, and rule id.
92
+ ## Supported AI tools
198
93
 
199
- ## Network Access
94
+ - **Claude Code** (mature, default).
95
+ - **Cursor 1.7** (beta, `--agent cursor`; payload mapping is best-effort against public docs).
200
96
 
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.
97
+ Other tools (Copilot, Cline, Aider, Codex web, Replit, Devin) lack a hook surface and cannot be enforced at runtime.
202
98
 
203
99
  ## License
204
100
 
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.0.2",
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",
@@ -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 });