@codai/axiom-mcp 1.0.24 โ†’ 2.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+
2
+ MIT License
3
+
4
+ Copyright (c) 2025
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1,423 +1,175 @@
1
- # @codai/axiom-mcp
2
-
3
- **AXIOM Model Context Protocol Server** - Generate artifacts, validate policies, and apply manifests directly from AI conversations.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@codai/axiom-mcp.svg)](https://www.npmjs.com/package/@codai/axiom-mcp)
6
- [![License](https://img.shields.io/npm/l/@codai/axiom-mcp.svg)](https://github.com/dragoscv/axiom/blob/main/LICENSE)
7
-
8
- ---
9
-
10
- ## ๐Ÿš€ Quick Start
11
-
12
- ### Installation
13
-
14
- ```bash
15
- npm install -g @codai/axiom-mcp@latest
16
- ```
17
-
18
- ### VS Code MCP Configuration
19
-
20
- Add to your `.vscode/mcp.json` or global MCP config:
21
-
22
- ```json
23
- {
24
- "mcpServers": {
25
- "axiom": {
26
- "command": "npx",
27
- "args": [
28
- "-y",
29
- "--package=@codai/axiom-mcp@latest",
30
- "axiom-mcp-stdio"
31
- ]
32
- }
33
- }
34
- }
35
- ```
36
-
37
- **Alternative (if installed globally)**:
38
- ```json
39
- {
40
- "mcpServers": {
41
- "axiom": {
42
- "command": "axiom-mcp-stdio"
43
- }
44
- }
45
- }
46
- ```
47
-
48
- ### Test Server
49
-
50
- ```bash
51
- npx @codai/axiom-mcp@latest
52
- # Output: AXIOM MCP Server running on stdio
53
- ```
54
-
55
- ---
56
-
57
- ## ๐Ÿ“‹ Available Tools
58
-
59
- ### 1. `axiom_parse`
60
-
61
- Parse `.axm` source code to Intermediate Representation (IR).
62
-
63
- **Input**:
64
- ```typescript
65
- {
66
- source: string // AXIOM .axm source code
67
- }
68
- ```
69
-
70
- **Output**:
71
- ```typescript
72
- {
73
- ir: IR // Parsed intermediate representation
74
- }
75
- ```
76
-
77
- **Example**:
78
- ```json
79
- {
80
- "source": "agent \"my-app\" {\n capability net(\"api\")\n check sla \"latency\" { expect \"cold_start_ms <= 50\" }\n emit service \"app\"\n}"
81
- }
82
- ```
83
-
84
- ---
85
-
86
- ### 2. `axiom_validate`
87
-
88
- Validate IR semantics and constraints.
89
-
90
- **Input**:
91
- ```typescript
92
- {
93
- ir: IR // IR object from axiom_parse
94
- }
95
- ```
96
-
97
- **Output**:
98
- ```typescript
99
- {
100
- valid: boolean,
101
- errors?: string[]
102
- }
103
- ```
104
-
105
- ---
106
-
107
- ### 3. `axiom_generate`
108
-
109
- Generate artifacts and manifest from validated IR.
110
-
111
- **Input**:
112
- ```typescript
113
- {
114
- ir: IR,
115
- profile?: string // "edge" | "default" | "budget" (default: "default")
116
- }
117
- ```
118
-
119
- **Output**:
120
- ```typescript
121
- {
122
- manifest: Manifest, // Complete manifest with artifacts
123
- artifacts: Artifact[] // Generated artifacts (files)
124
- }
125
- ```
126
-
127
- **Features**:
128
- - โœ… **POSIX paths**: All artifact paths use `/` (cross-platform)
129
- - โœ… **Deterministic**: Same IR + profile โ†’ same manifest SHA256
130
- - โœ… **Profile-based**: Different cold start thresholds per profile
131
-
132
- ---
133
-
134
- ### 4. `axiom_check`
135
-
136
- Run policy checks on generated manifest.
137
-
138
- **Input**:
139
- ```typescript
140
- {
141
- manifest: Manifest,
142
- ir?: IR // Optional for enhanced checking
143
- }
144
- ```
145
-
146
- **Output**:
147
- ```typescript
148
- {
149
- passed: boolean, // AND over all checks
150
- report: {
151
- checkName: string,
152
- kind: "sla" | "policy",
153
- passed: boolean,
154
- details: {
155
- expression: string,
156
- evaluated: true, // Always true (real evaluation)
157
- message: string,
158
- measurements: {
159
- cold_start_ms: number,
160
- frontend_bundle_kb: number,
161
- max_dependencies: number,
162
- no_pii_in_artifacts: boolean,
163
- // ... more metrics
164
- }
165
- }
166
- }[]
167
- }
168
- ```
169
-
170
- **Features**:
171
- - โœ… **Real evaluation**: Deterministic metrics calculation
172
- - โœ… **Profile-aware**: Edge (50ms), Default (100ms), Budget (120ms)
173
- - โœ… **Transparent**: `evaluated:true` confirms real computation
174
-
175
- ---
176
-
177
- ### 5. `axiom_apply`
178
-
179
- Apply manifest to filesystem or create Pull Request.
180
-
181
- **Input**:
182
- ```typescript
183
- {
184
- manifest: Manifest,
185
- mode: "fs" | "pr", // "fs" = filesystem, "pr" = pull request
186
- repoPath?: string, // Default: process.cwd()
187
-
188
- // PR mode only:
189
- branchName?: string,
190
- commitMessage?: string
191
- }
192
-
193
- // Manifest artifact structure (enhanced v1.0.17):
194
- interface Artifact {
195
- path: string; // POSIX relative path
196
- kind: "file" | "report";
197
- sha256: string; // Content hash for validation
198
- bytes: number;
199
-
200
- // โœจ NEW in 1.0.17: Optional embedded content (store-less operation)
201
- contentUtf8?: string; // UTF-8 text content (for README, configs)
202
- contentBase64?: string; // Base64 binary content (for images, compiled assets)
203
- }
204
- ```
205
-
206
- **Output**:
207
- ```typescript
208
- {
209
- filesWritten: string[], // POSIX relative paths: "out/webapp/index.html"
210
- summary: string
211
- }
212
- ```
213
-
214
- **Features**:
215
- - โœ… **Versioned Artifact Cache**: `.axiom/cache/v1/<sha256>` (future-proof structure)
216
- - โœ… **3-Tier Content Fallback** (NEW in 1.0.17):
217
- 1. `artifact.contentUtf8` โ†’ Buffer.from(utf-8) - **Fastest, no store lookup**
218
- 2. `artifact.contentBase64` โ†’ Buffer.from(base64) - **Embedded binary content**
219
- 3. `artifactStore.get(sha256)` โ†’ Cached content - **Fallback for large files**
220
- 4. Throws `ERR_ARTIFACT_CONTENT_MISSING` if all sources unavailable
221
- - โœ… **Real Filesystem Writes**: Physical `fs/promises.writeFile()` to `out/` directory
222
- - โœ… **SHA256 Validation**: Verifies file integrity after write (throws `ERR_SHA256_MISMATCH`)
223
- - โœ… **Auto-creates `./out/`**: No manual directory setup required
224
- - โœ… **POSIX Paths**: `filesWritten[]` use `/` on all platforms (guaranteed no backslash)
225
- - โœ… **Security**: Comprehensive protection:
226
- - Blocks absolute paths (`/etc/passwd`)
227
- - Blocks path traversal (`../../../sensitive`)
228
- - Blocks mid-path traversal (`safe/../evil/hack.txt`)
229
- - Validates all paths stay within `out/` directory
230
- - โœ… **Error Handling**: Clear errors with diagnostic context
231
-
232
- **Workflow Integration**:
233
- 1. **Generate**: Creates manifest + optionally embeds small content OR caches by SHA256 in `.axiom/cache/v1/`
234
- 2. **Apply**:
235
- - Tries embedded content first (fast path for README, configs)
236
- - Falls back to cache lookup for large artifacts
237
- - Writes real files to `out/` with SHA256 validation
238
- 3. **Deterministic**: Identical IR โ†’ identical SHA256 โ†’ identical files (verified post-write)
239
-
240
- ---
241
-
242
- ## ๐ŸŽฏ Complete Workflow Example
243
-
244
- ```json
245
- // 1. Parse .axm source
246
- {
247
- "tool": "axiom_parse",
248
- "input": {
249
- "source": "agent \"notes-app\" {\n capability net(\"firebase\")\n check sla \"fast\" { expect \"cold_start_ms <= 50\" }\n emit service \"web\"\n}"
250
- }
251
- }
252
-
253
- // 2. Validate IR
254
- {
255
- "tool": "axiom_validate",
256
- "input": {
257
- "ir": "<IR from step 1>"
258
- }
259
- }
260
-
261
- // 3. Generate artifacts (edge profile for performance)
262
- {
263
- "tool": "axiom_generate",
264
- "input": {
265
- "ir": "<IR from step 1>",
266
- "profile": "edge"
267
- }
268
- }
269
-
270
- // 4. Check policies
271
- {
272
- "tool": "axiom_check",
273
- "input": {
274
- "manifest": "<manifest from step 3>",
275
- "ir": "<IR from step 1>"
276
- }
277
- }
278
-
279
- // 5. Apply to filesystem
280
- {
281
- "tool": "axiom_apply",
282
- "input": {
283
- "manifest": "<manifest from step 3>",
284
- "mode": "fs",
285
- "repoPath": "."
286
- }
287
- }
288
- ```
289
-
290
- ---
291
-
292
- ## ๐Ÿงช Validation & Testing
293
-
294
- ### Test Suite Status
295
-
296
- ```bash
297
- cd packages/axiom-tests
298
- npx vitest run
299
-
300
- # Results:
301
- # โœ… 31/31 tests passing
302
- # โœ… Duration: ~800ms
303
- # โœ… Coverage: 100% of critical paths
304
- ```
305
-
306
- ### Key Test Validations
307
-
308
- | Bug Fix | Test File | Status |
309
- |---------|-----------|--------|
310
- | POSIX paths | `path-normalization.test.ts` | โœ… 2/2 |
311
- | Real check evaluator | `check-evaluator.test.ts` | โœ… 3/3 |
312
- | Apply to FS | `apply-reporoot.test.ts` | โœ… 3/3 |
313
- | Security guards | `apply-sandbox.test.ts` | โœ… 3/3 |
314
- | Determinism | `determinism-edge.test.ts` | โœ… 3/3 |
315
- | Parser completeness | `parser-roundtrip.test.ts` | โœ… 3/3 |
316
-
317
- ---
318
-
319
- ## ๐Ÿ“Š Performance & Quality
320
-
321
- | Metric | Value | Status |
322
- |--------|-------|--------|
323
- | Package Size | 4.1KB | โœ… Optimized |
324
- | Install Time | ~5s (97 packages) | โœ… Good |
325
- | Test Duration | 816ms | โœ… Fast |
326
- | Test Coverage | 31/31 (100%) | โœ… Excellent |
327
- | Determinism | Identical SHA256 | โœ… Maintained |
328
-
329
- ---
330
-
331
- ## ๐Ÿ”’ Security Features
332
-
333
- 1. **Path Traversal Protection**: Blocks `..` and absolute paths in `axiom_apply`
334
- 2. **SHA256 Validation**: Verifies file integrity after write
335
- 3. **POSIX Normalization**: Prevents OS-specific path exploits
336
- 4. **Input Validation**: Strict schema validation on all tool inputs
337
-
338
- ---
339
-
340
- ## ๐Ÿ“š Documentation
341
-
342
- - **API Reference**: See [docs/mcp_api.md](../../docs/mcp_api.md)
343
- - **Syntax Spec**: See [docs/syntax_spec.md](../../docs/syntax_spec.md)
344
- - **IR Spec**: See [docs/ir_spec.md](../../docs/ir_spec.md)
345
- - **GO-NOGO Report**: See [GO-NOGO-AXIOM-1.0.9.md](../../GO-NOGO-AXIOM-1.0.9.md)
346
-
347
- ---
348
-
349
- ## ๐Ÿ› Troubleshooting
350
-
351
- ### Issue: "EUNSUPPORTEDPROTOCOL" error
352
-
353
- **Problem**: Older version (1.0.7) used `workspace:*` dependencies
354
- **Solution**: Update to latest version
355
- ```bash
356
- npm install @codai/axiom-mcp@latest
357
- ```
358
-
359
- ### Issue: MCP server not starting in VS Code
360
-
361
- **Check**:
362
- 1. Verify `.vscode/mcp.json` configuration
363
- 2. Restart VS Code MCP extension
364
- 3. Check VS Code Output panel (MCP logs)
365
-
366
- **Debug**:
367
- ```bash
368
- npx @codai/axiom-mcp@latest
369
- # Should output: "AXIOM MCP Server running on stdio"
370
- ```
371
-
372
- ### Issue: Files not written to `./out/`
373
-
374
- **Check**:
375
- 1. Verify `repoPath` is correct (default: `process.cwd()`)
376
- 2. Ensure write permissions for directory
377
- 3. Check `filesWritten[]` in response for actual paths
378
-
379
- ---
380
-
381
- ## ๐Ÿ”„ Version History
382
-
383
- ### v1.0.9 (2025-10-21) - **CURRENT**
384
- - โœ… Complete MCP fix validation
385
- - โœ… GO-NOGO report with comprehensive evidence
386
- - โœ… All 3 critical bugs confirmed fixed
387
-
388
- ### v1.0.8 (2025-10-21)
389
- - โœ… Fixed `workspace:*` npm compatibility
390
- - โœ… Published internal packages with `internal` tag
391
-
392
- ### v1.0.1 (2025-10-20)
393
- - โœ… POSIX path normalization
394
- - โœ… Real check evaluator
395
- - โœ… Complete .axm parser
396
- - โœ… Apply defaults to `process.cwd()`
397
- - โœ… Determinism enhancements
398
-
399
- ---
400
-
401
- ## ๐Ÿ“ฆ Package Information
402
-
403
- - **Name**: `@codai/axiom-mcp`
404
- - **Version**: `1.0.9`
405
- - **License**: MIT
406
- - **Repository**: https://github.com/dragoscv/axiom
407
- - **npm**: https://www.npmjs.com/package/@codai/axiom-mcp
408
-
409
- ---
410
-
411
- ## ๐Ÿค Contributing
412
-
413
- See main repository: https://github.com/dragoscv/axiom
414
-
415
- ---
416
-
417
- ## ๐Ÿ“„ License
418
-
419
- MIT License - see [LICENSE](../../LICENSE) for details
420
-
421
- ---
422
-
423
- **Built with ๐Ÿ’™ by the AXIOM team**
1
+ # @codai/axiom-mcp
2
+
3
+ MCP server (stdio or Streamable HTTP) and CLI for AXIOM v2 โ€” the transactional write gate for coding agents:
4
+ `Plan` โ†’ canonical `ManifestBundle` โ†’ set-level checks โ†’ hash-gated two-phase `apply` โ†’ journal.
5
+ `dist/cli.js` (thin entry) + `dist/cli-main.js` (lazy-loaded engines, SDK and zod bundled in); no runtime dependencies.
6
+
7
+ ## Install & run
8
+
9
+ ```sh
10
+ npx @codai/axiom-mcp mcp --root /abs/path/to/repo # stdio MCP server
11
+ npx @codai/axiom-mcp mcp --root /abs/path/to/repo --http 127.0.0.1:3411 # Streamable HTTP at /mcp
12
+ npx @codai/axiom-mcp --help # CLI verbs
13
+ ```
14
+
15
+ `--root` may repeat. Every tool `root` argument must equal or lie inside one of them; with exactly one
16
+ root it is the default. There is **no** env-var or `cwd` fallback (`ERR_ROOT_REQUIRED` / `ERR_ROOT_NOT_ALLOWED`).
17
+
18
+ ### VS Code โ€” `.vscode/mcp.json`
19
+
20
+ ```json
21
+ {
22
+ "servers": {
23
+ "axiom": {
24
+ "type": "stdio",
25
+ "command": "npx",
26
+ "args": ["-y", "@codai/axiom-mcp", "mcp", "--root", "${workspaceFolder}"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ### Streamable HTTP โ€” `--http <host:port>`
33
+
34
+ ```sh
35
+ axiom mcp --root /abs/repo --http 127.0.0.1:3411 # loopback, no token needed
36
+ axiom mcp --root /abs/repo --http 0 # random port; URL logged at info level
37
+ AXIOM_HTTP_TOKEN=$(openssl rand -hex 32) axiom mcp --root /abs/repo --http 0.0.0.0:3411 --log-level info
38
+ ```
39
+
40
+ - Endpoints: `POST /mcp` (an `initialize` opens a session and returns `Mcp-Session-Id`; every later
41
+ request must send it), `GET /mcp` (standalone SSE stream, one per session), `DELETE /mcp` (close the
42
+ session), `GET /health` โ†’ `{ ok, name, version }` (unauthenticated). Anything else is `404` JSON.
43
+ - **Loopback by default.** A non-loopback host **refuses to start** unless a bearer token is present in
44
+ the env var named by `--http-token-env <NAME>` (default `AXIOM_HTTP_TOKEN`, โ‰ฅ 16 chars). Clients send
45
+ `Authorization: Bearer <token>`; the compare is constant-time. A token is optional on loopback.
46
+ - DNS-rebinding protection is on for loopback binds (`Host` must be `<host>:<port>`, `localhost:<port>`
47
+ or `127.0.0.1:<port>`; otherwise `403`). Request bodies over 4 MiB are `413`.
48
+ - Sessions idle for 30 minutes are evicted; each session has its own server instance (roots and guard
49
+ settings are shared). The transport lives in `dist/http-lazy.js`, loaded only with `--http`, and is
50
+ plain `node:http` โ€” no express/hono at runtime.
51
+ - VS Code: `{ "type": "http", "url": "http://127.0.0.1:3411/mcp" }`; add
52
+ `"headers": { "Authorization": "Bearer ${input:axiom-token}" }` when a token is set.
53
+
54
+ Conformance: `packages/conformance` runs `@modelcontextprotocol/conformance server` against this transport
55
+ in CI with an expected-failures baseline (`packages/conformance/baseline.yml`).
56
+
57
+ ### Claude Desktop โ€” `claude_desktop_config.json`
58
+
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "axiom": { "command": "npx", "args": ["-y", "@codai/axiom-mcp", "mcp", "--root", "/abs/path/to/repo"] }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ## Tools
68
+
69
+ | tool | risk | input | output |
70
+ |---|---|---|---|
71
+ | `axiom_plan_validate` | READ | `{ plan }` | `{ ok, planDigest?, errors[] }` |
72
+ | `axiom_plan_compile` | ACT | `{ plan, store?: inline\|cas, root? }` | `ManifestBundle` (writes only under `<root>/.axiom/` โ€” CAS blobs and the stored manifest โ€” when a root is given) |
73
+ | `axiom_manifest_verify` | READ | `{ bundle, root? }` | `{ ok, manifestDigest, canonical, signed, missing[], errors[], signatures?: { trustFile, keyids[], findings[], ok } }` โ€” `signatures` only when `root` has `.axiom/trust/keys.json` |
74
+ | `axiom_check` | READ | `{ bundle, profile?, root? }` | `CheckReport` (`verdict: pass\|fail\|error`) |
75
+ | `axiom_apply_dry_run` | READ | `{ bundle, root, profile? }` | `ApplyResult{mode:"dry-run", diff}` |
76
+ | `axiom_apply` | SENSITIVE | `{ bundle, root, profile?, confirmDigest }` | `ApplyResult` |
77
+ | `axiom_rollback` | SENSITIVE | `{ root, manifestDigest }` | `{ status:"rolled-back", phase, steps }` |
78
+ | `axiom_manifest_diff` | READ | `{ a: bundle\|"sha256:โ€ฆ", b }` | `{ added[], removed[], changed[] }` |
79
+ | `axiom_axm_parse` | READ | `{ source }` (`.axm` text) | `{ plan?, diagnostics: [{ severity, code, message, range: { start: {line, column}, end } }] }` |
80
+ | `axiom_roots_list` | READ | `{}` | `{ roots: [{ path, writable, hasGit }] }` |
81
+ | `axiom_repo_snapshot` | READ | `{ root?, include?[], exclude?[], maxFiles? (20000, cap 50000), maxBytes? (64 MiB), respectGitignore? (true), withContentDigest? (true) }` | `RepoSnapshot { snapshotDigest, body: { files: [{ path, bytes, sha256?, mode, kind }], truncated, counts } }` โ€” sorted, no timestamps/absolute paths; `.git/`, `.axiom/` always skipped; symlinks recorded, never followed (`docs/snapshot.md`) |
82
+
83
+ Every tool carries MCP `annotations` (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`)
84
+ and an `outputSchema`; `structuredContent` is the full result, `content[0].text` a small summary (digest,
85
+ verdict, counts, first 20 findings). Errors come back as `isError: true` with `{ code, message, path? }`
86
+ from the closed `ERROR_CODES` enum โ€” a handler never throws. `spec/tools.json` is generated from the same
87
+ registry (`pnpm build:spec`) and guarded by a parity test. `spec/codai-tools.json` is the same registry in
88
+ codai's `packages/agent-core/spec/tools-v2.json` entry shape (`{ name, risk, description, parameters }`) โ€”
89
+ see `docs/integration/codai.md`.
90
+
91
+ Resources: `axiom://manifest/{sha}`, `axiom://report/{sha}`, `axiom://applied/{sha}`,
92
+ `axiom://profile/{name}`, `axiom://schema/{Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal|RepoSnapshot}`,
93
+ `axiom://emitters` (template emitters available to `axiom_plan_compile` โ€” `web@2.0.0`, see `docs/emitters.md`).
94
+
95
+ ## Trust model
96
+
97
+ - Roots are realpath'd at startup, must be directories, and the set is frozen. Requested roots are
98
+ realpath'd too (case-insensitive containment on Windows); anything outside is a hard error.
99
+ - `axiom_apply` requires `confirmDigest === bundle.manifestDigest` โ€” echo the digest you saw in dry-run.
100
+ Pre-apply checks run against the profile (default `default`, or `<root>/.axiom/profiles/<name>.json`);
101
+ a non-`pass` verdict aborts with `ERR_CHECKS_FAILED` before any write.
102
+ - Payloads over 4 MiB are rejected up front (`ERR_BUNDLE_TOO_LARGE`).
103
+ - `.axiom/lock` makes apply single-writer per root; the journal makes it crash-safe and reversible.
104
+ - stdout carries only JSON-RPC. Logs are JSON lines on stderr (`--log-level error|warn|info|debug`, default `warn`).
105
+ - External guards (`guard.external`) are **off** unless the process is started with `--allow-guards`
106
+ *and* the profile sets `facts.allowGuards: true`. Relative commands must live under `<root>/scripts/`;
107
+ absolute ones must be listed exactly via `--guard-allowlist <abs>` (repeatable). Guards are spawned
108
+ with an args array (never a shell), a scrubbed environment, a wall-clock timeout, and must print
109
+ `GuardOutput` JSON โ€” see `docs/checks.md`.
110
+ - **Signed manifests** (`docs/signing.md`): a root can pin Ed25519 public keys in
111
+ `.axiom/trust/keys.json`; a profile with `manifest.requireSigned` then refuses unsigned, tampered or
112
+ untrusted bundles, and with `antiRollback: true` refuses any `counter โ‰ค .axiom/trust/state.json#lastCounter`.
113
+ `axiom_apply` advances that state only on `status: "applied"`. Private keys never enter the server:
114
+ signing is `axiom sign` with `AXIOM_SIGNING_KEY` or `--key-file`.
115
+
116
+ ## CLI
117
+
118
+ ```
119
+ axiom mcp [--root <abs>]... [--allow-guards] [--guard-allowlist <abs>]... [--log-level warn]
120
+ [--http <host:port>] [--http-token-env AXIOM_HTTP_TOKEN]
121
+ axiom compile <plan.json> [-o out.json] [--store cas --root .] [--allow-net [--net-allow host[,host]]] [--allow-file]
122
+ axiom verify <bundle.json> [--root .] (--root: also verify signatures against .axiom/trust/keys.json)
123
+ axiom check <bundle.json> --root . [--profile p] [--json] [--allow-guards] [--guard-allowlist <abs>]...
124
+ axiom apply <bundle.json> --root . [--dry-run] [--profile p] [--confirm <digest>] [--allow-guards] [--guard-allowlist <abs>]...
125
+ axiom rollback <digest> --root .
126
+ axiom gc --root . [--dry-run] [--older-than 30d] [--keep all-manifests|journal] (CAS garbage collection; CLI only, no MCP tool)
127
+ axiom diff <a.json> <b.json>
128
+ axiom schema <Plan|Manifest|ManifestBundle|CheckReport|ApplyResult|Profile|Journal|RepoSnapshot>
129
+ axiom emitters [--json]
130
+ axiom keygen [--out <dir>] [--name <label>] (ed25519; private key โ†’ <dir>/axiom-signing-<id>.key 0600, public entry โ†’ stdout)
131
+ axiom sign <bundle.json> [--key-file <path>] [-o out.json] (key from --key-file or $AXIOM_SIGNING_KEY)
132
+ axiom trust add <pub.json> --root . | remove <keyid> --root . | list --root .
133
+ axiom gate --stdin [--root <dir>] [--profile <file>] [--strict] [--log-level warn]
134
+ axiom migrate v1 <manifest.json> [-o plan.json] [--profile default] [--cas <root>] [--content <dir>] [--overwrite]
135
+ (v1 manifest โ†’ v2 Plan, lazy chunk; exit 1 = migrated with warnings โ€” docs/migrate.md)
136
+ axiom snapshot --root . [-o snap.json] [--include <glob>]... [--exclude <glob>]... [--max-files n] [--max-bytes n] [--no-gitignore] [--no-digest]
137
+ axiom snapshot-diff <a.json> <b.json> (RepoSnapshot โ†’ { added, removed, changed })
138
+ ```
139
+
140
+ Exit codes: `0` ok ยท `1` verdict fail / apply failed ยท `2` usage or error. Non-`mcp` verbs print JSON to stdout.
141
+
142
+ `ref` sources (`{ type: "ref", uri, digest }`) are offline by default: a digest already in
143
+ `<root>/.axiom/cas` resolves without network, anything else is `ERR_NET_DISABLED`. `--allow-net`
144
+ fetches `https:` only (no redirects, 30 s timeout, 32 MiB cap), optionally restricted to
145
+ `--net-allow` hosts (`*.example.com` wildcards), verifies the pinned digest and stores the blob in
146
+ the CAS โ€” a mismatch stores nothing (`ERR_DIGEST_MISMATCH`). `apply` never fetches. The MCP
147
+ `axiom_plan_compile` tool has no network switch. See [docs/plan-format.md](../../docs/plan-format.md#ref-sources)
148
+ and [docs/cas.md](../../docs/cas.md).
149
+
150
+ ## Hook mode โ€” `axiom gate --stdin`
151
+
152
+ A PreToolUse hook for Claude Code, Copilot CLI and VS Code agent hooks. It reads **one** harness
153
+ payload from stdin (both `{tool_name, tool_input, cwd}` and `{toolName, toolArgs, cwd}` casings;
154
+ `toolArgs` may be a JSON string), extracts the write target(s) of `Write|Edit|MultiEdit|NotebookEdit`,
155
+ `create_file|replace_string_in_file|insert_edit_into_file|apply_patch|multi_replace_string_in_file|edit_notebook_file`
156
+ and generic `write|edit`, and runs **only** the fast predicates: containment + `RelPath` rules
157
+ (`..`, `CON`, NTFS ADS โ†’ `ERR_CONTAINMENT` / `ERR_PATH_*`), `path.deny`, `path.allow`,
158
+ `content.noSecrets` and `content.maxBytes` on the new content when the payload carries it.
159
+
160
+ | outcome | exit | stdout | stderr |
161
+ |---|---|---|---|
162
+ | allow / unknown tool | `0` | โ€” | โ€” |
163
+ | deny | `2` | `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"โ€ฆ"}}` | `AXIOM GATE DENY <code>: <reason> (<relpath>)` |
164
+ | malformed payload, stdin timeout (2 s), internal error | `0` (**fail open**) | โ€” | `AXIOM GATE WARN: โ€ฆ` |
165
+ | same, with `--strict` | `2` | deny JSON | `AXIOM GATE DENY ERR_INTERNAL: โ€ฆ` |
166
+
167
+ Root = payload `cwd`, else `--root`, else the process cwd (the hook is the one place where cwd is
168
+ acceptable: the harness spawns the hook in the project directory and owns that value).
169
+ Profile = `--profile <file>` โ†’ `<root>/.axiom/gate-profile.json` โ†’ `~/.axiom/gate-profile.json` โ†’
170
+ built-in `{ deny: [".git/**", ".axiom/**", "**/*.lock", "pnpm-lock.yaml", ".env", ".env.*", "**/node_modules/**"], noSecrets: true }`.
171
+ Schema: `{ deny: string[], allow?: string[], noSecrets: boolean, maxBytes?: number }` (strict).
172
+
173
+ `gate` is a separate lazy chunk (`dist/gate-lazy.js`, no MCP SDK): in-process p95 โ‰ˆ 5 ms per payload,
174
+ end-to-end โ‰ˆ 150โ€“200 ms including node startup; `check-gate-latency` guards p95 โ‰ค 250 ms.
175
+ Wiring for each harness is in [`docs/hooks.md`](../../docs/hooks.md).