@bigknoxy/hashpilot 4.6.3

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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +777 -0
  3. package/docs/ADAPTER-CONTRACT.md +1260 -0
  4. package/docs/ARCHITECTURE.md +846 -0
  5. package/docs/CLI-QUICKREF.md +827 -0
  6. package/docs/COMPETITIVE-ANALYSIS.md +307 -0
  7. package/docs/INSTALL.md +403 -0
  8. package/docs/INTEGRATION-CLAUDE.md +126 -0
  9. package/docs/INTEGRATION-MCP.md +196 -0
  10. package/docs/INTEGRATION-OPENCODE.md +136 -0
  11. package/docs/INTEGRATION-PI.md +195 -0
  12. package/package.json +77 -0
  13. package/scripts/build-site.sh +39 -0
  14. package/scripts/doctor.sh +218 -0
  15. package/scripts/gen-cli-quickref.ts +232 -0
  16. package/scripts/install-cli.sh +60 -0
  17. package/scripts/install.sh +466 -0
  18. package/scripts/roadmap-lint.ts +200 -0
  19. package/scripts/uninstall.sh +202 -0
  20. package/src/cli-node.cjs +51 -0
  21. package/src/cli.ts +209 -0
  22. package/src/commands/ast.ts +255 -0
  23. package/src/commands/diff.ts +98 -0
  24. package/src/commands/edit.ts +93 -0
  25. package/src/commands/hash.ts +64 -0
  26. package/src/commands/intent.ts +68 -0
  27. package/src/commands/maintenance.ts +191 -0
  28. package/src/commands/mcp.ts +28 -0
  29. package/src/commands/provenance.ts +111 -0
  30. package/src/commands/read.ts +117 -0
  31. package/src/commands/route.ts +42 -0
  32. package/src/commands/shared.ts +65 -0
  33. package/src/commands/telemetry.ts +126 -0
  34. package/src/commands/verify.ts +61 -0
  35. package/src/core/ast-edit.ts +2357 -0
  36. package/src/core/batch-edit.ts +185 -0
  37. package/src/core/config.ts +189 -0
  38. package/src/core/diff-engine.ts +474 -0
  39. package/src/core/doctor.ts +303 -0
  40. package/src/core/encoding.ts +116 -0
  41. package/src/core/envelope.ts +163 -0
  42. package/src/core/exit-codes.ts +198 -0
  43. package/src/core/format.ts +339 -0
  44. package/src/core/grep.ts +180 -0
  45. package/src/core/hash-edit.ts +416 -0
  46. package/src/core/index.ts +155 -0
  47. package/src/core/intent.ts +584 -0
  48. package/src/core/locking.ts +292 -0
  49. package/src/core/module-system.ts +142 -0
  50. package/src/core/operations.ts +557 -0
  51. package/src/core/output.ts +122 -0
  52. package/src/core/path-normalize.ts +61 -0
  53. package/src/core/paths.ts +326 -0
  54. package/src/core/plan-executor.ts +437 -0
  55. package/src/core/platform.ts +132 -0
  56. package/src/core/provenance.ts +214 -0
  57. package/src/core/read.ts +111 -0
  58. package/src/core/redact.ts +98 -0
  59. package/src/core/resolve-content.ts +12 -0
  60. package/src/core/router.ts +463 -0
  61. package/src/core/snapshot.ts +346 -0
  62. package/src/core/telemetry.ts +838 -0
  63. package/src/core/utils.ts +7 -0
  64. package/src/core/verify-baseline.ts +186 -0
  65. package/src/core/verify-scope.ts +282 -0
  66. package/src/core/verify.ts +753 -0
  67. package/src/mcp/server.ts +325 -0
  68. package/templates/claude-section.md +12 -0
  69. package/templates/opencode-agent.md +106 -0
  70. package/templates/opencode-skill.md +241 -0
  71. package/templates/pi-extension.ts +288 -0
  72. package/templates/pi-skill.md +123 -0
  73. package/tsconfig.json +19 -0
package/README.md ADDED
@@ -0,0 +1,777 @@
1
+ # HashPilot — Deterministic Structured Editing for AI Coding Agents
2
+
3
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ [![Bun](https://img.shields.io/badge/runtime-Bun_1.2%2B-black)](https://bun.sh)
5
+ [![Tree-sitter](https://img.shields.io/badge/ast-tree--sitter-green)](https://tree-sitter.github.io)
6
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)](tests/)
7
+ [![Live Site](https://img.shields.io/badge/site-gh--pages-blue)](https://bigknoxy.github.io/HashPilot/)
8
+
9
+ **AI agents edit code blind. HashPilot gives them cryptographic certainty.**
10
+
11
+ > **HashPilot is the *safe, verifiable, observable* file-editing primitive for coding agents.**
12
+ > While other tools match text or rewrite whole files, HashPilot anchors every edit to a
13
+ > cryptographic digest — **if the hash doesn't match, nothing changes.** With AST-native editing,
14
+ > provenance trails, and a verify pipeline, it gives agents deterministic control over how code
15
+ > changes land on disk. It occupies the one position no competitor does: hash-anchored +
16
+ > AST-aware + provenance-tracked. See [`docs/COMPETITIVE-ANALYSIS.md`](docs/COMPETITIVE-ANALYSIS.md).
17
+
18
+ Landing page: **[https://bigknoxy.github.io/HashPilot/](https://bigknoxy.github.io/HashPilot/)**
19
+ Architecture: **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** · MCP setup: **[docs/INTEGRATION-MCP.md](docs/INTEGRATION-MCP.md)** · CLI reference: **[docs/CLI-QUICKREF.md](docs/CLI-QUICKREF.md)** · Roadmap & backlog: **[ROADMAP.md](ROADMAP.md)**
20
+
21
+ Every edit is anchored by a SHA-256 hash — not a fragile line number or a fuzzy text match. If the hash matches, you're editing the right content. No guessing, no retries, no silent corruption.
22
+
23
+ ---
24
+
25
+ ## What This Is
26
+
27
+ HashPilot is a CLI (`hashpilot`) and editing protocol that replaces fuzzy text editing with precision operations:
28
+
29
+ - **Hash-anchored replacement** — target content by its cryptographic fingerprint
30
+ - **AST-aware refactoring** — rename symbols, replace function bodies, manage imports (TypeScript, JS, Python, Go, Rust)
31
+ - **Stale-anchor detection** — catch race conditions before they corrupt files
32
+ - **Plan-and-execute intents** — describe a multi-file change, HashPilot discovers call sites and executes every step
33
+ - **Provenance tracking** — every edit records who, what, when, and why (like `git blame` for agents)
34
+
35
+ It's a global, tool-agnostic core. Claude Code, OpenCode, Pi, Codex CLI, Cursor — any agent that edits files.
36
+
37
+ ---
38
+
39
+ ## Why This Exists
40
+
41
+ ### The Pain (If You're an Agent Editing Code)
42
+
43
+ You're editing files fundamentally differently than a human. You don't have an IDE. You can't visually locate the right line. You don't keep a working memory of the file structure. Yet most tools expect you to edit the same way a human would:
44
+
45
+ | Problem | What Happens | Cost |
46
+ |---------|-------------|------|
47
+ | **Line counting** | `"Replace line 42"` breaks when one import shifts every line | +3 retries, corrupted file |
48
+ | **Fuzzy find-replace** | Guess the right text to match — or match the wrong occurrence | Silent corruption, wasted tokens |
49
+ | **Race conditions** | Two edits to the same file interleave | Invalid syntax, agent confusion |
50
+ | **No verification** | Edit, move on. No check that it compiles | Bug ships to production |
51
+
52
+ ### The Remedy
53
+
54
+ | HashPilot Feature | What It Does | For Whom |
55
+ |------------------|-------------|----------|
56
+ | SHA-256 anchors | Target content by its fingerprint — not a line number | **Agents** — never edit the wrong content |
57
+ | 3-tier routing | AST → Hash → Diff. Auto-selects the best strategy per file+op | **Agents** — one command, right tool |
58
+ | Stale recovery | Detects out-of-date edits and re-applies transparently | **Agents** — survive race conditions |
59
+ | Intents (M5) | `"Add a parameter to processData"` → discovers call sites, plans, executes | **Agents** — multi-file refactors in one shot |
60
+ | Verification bundling | Run formatter + linter + typechecker + tests in one command | **Developers** — know it works |
61
+ | Provenance | `provenance query src/utils.ts` shows edit history per line | **Teams** — audit trail for AI changes |
62
+ | Telemetry | Structured JSONL with health reports and trend analysis | **Operators** — monitor agent edit reliability |
63
+
64
+ ---
65
+
66
+ ## Who It's For
67
+
68
+ ### 👤 You, the Developer
69
+
70
+ You use AI coding agents to ship faster. When your agent uses HashPilot:
71
+
72
+ - **Edits work on the first try.** No more "oops, that was the wrong line."
73
+ - **Every change is auditable.** Provenance tells you who changed what and why.
74
+ - **You can trust AI refactors.** Multi-file renames execute correctly across your whole codebase.
75
+ - **It's invisible.** The agent uses HashPilot automatically — you just see better results.
76
+
77
+ ### 🤖 You, the AI Agent
78
+
79
+ You edit source files every time a user asks for a change. With HashPilot:
80
+
81
+ - **You know the hash is correct.** `replace-hash` targets content by its SHA-256 fingerprint. No ambiguity.
82
+ - **You don't need to re-read the file.** The hash from `read-many` is valid until the file changes. That's one less API round-trip.
83
+ - **AST edits are syntax-safe.** `rename-symbol`, `replace-body`, `add-import` — tree-sitter guarantees the edit is structurally valid. `rename-symbol` is **file-scoped and binding-aware**: it renames a symbol and its references within the target file, but refuses with `AMBIGUOUS_SYMBOL` when the same name binds more than one symbol in that file — a shadowed local, a foreign `import`, or a duplicate top-level declaration — so a file-wide rename never clobbers an unintended binding. Disambiguate by scoping the rename or renaming each declaration separately.
84
+ - **Intents handle the blast radius.** One `intent` command handles definition + all call sites + verification.
85
+ - **Telemetry tells you when something's wrong.** Stale-anchor rates, per-language failure rates, verify pass rates — all queryable.
86
+
87
+ ### 🏢 You, the Engineering Team
88
+
89
+ You need reproducible, auditable AI workflows:
90
+
91
+ - **Provenance = compliance.** Every edit logged with actor, task ID, and reason.
92
+ - **Config = consistency.** Team-wide route policies in `.hashpilot.json`.
93
+ - **Batch = scale.** Same edit applied across 100+ files, parallel or serial.
94
+ - **Verify = confidence.** Auto-detect project tools, run checks, revert on failure.
95
+
96
+ ---
97
+
98
+ ## Quick Start
99
+
100
+ ### One-Line Install (auto-installs Bun if missing)
101
+
102
+ ```bash
103
+ curl -fsSL https://raw.githubusercontent.com/bigknoxy/HashPilot/main/scripts/install.sh | bash
104
+ ```
105
+
106
+ **What it does:** Downloads HashPilot, auto-installs Bun 1.2+ if not present, installs all dependencies, configures adapters for **Claude Code**, **OpenCode**, and **Pi** automatically.
107
+
108
+ <details>
109
+ <summary><strong>📋 Copy-paste for your agent's install method</strong></summary>
110
+
111
+ **For any agent that runs shell commands:**
112
+ ```bash
113
+ curl -fsSL https://raw.githubusercontent.com/bigknoxy/HashPilot/main/scripts/install.sh | bash
114
+ ```
115
+
116
+ **For agents with a "run command" or "execute" tool:**
117
+ ```json
118
+ {
119
+ "command": "curl -fsSL https://raw.githubusercontent.com/bigknoxy/HashPilot/main/scripts/install.sh | bash",
120
+ "description": "Install HashPilot structured editing CLI"
121
+ }
122
+ ```
123
+
124
+ **Claude Code:** The installer adds HashPilot commands to `~/.claude/CLAUDE.md` automatically.
125
+
126
+ **OpenCode:** The installer adds the skill at `~/.config/opencode/skills/hashpilot/` and subagent at `~/.config/opencode/agent/hashpilot.md`.
127
+
128
+ **Pi:** The installer adds the extension at `~/.pi/agent/extensions/hashpilot.ts` with `/hp` slash command.
129
+
130
+ </details>
131
+
132
+ ### Install from npm
133
+
134
+ ```bash
135
+ npm i -g @bigknoxy/hashpilot # or: npx @bigknoxy/hashpilot doctor
136
+ ```
137
+
138
+ The package name is scoped because the bare `hashpilot` on npm belongs to an
139
+ unrelated project. The **binary is still `hashpilot`** — only the package name is
140
+ scoped. Bun ≥ 1.2 must be on PATH; the npm package does not install it (see the
141
+ runtime support matrix below).
142
+
143
+ **What you get, and what you don't.** The npm install is the whole CLI, including
144
+ the MCP server — every command works, `doctor` reports `installMode: "package"`,
145
+ and the `~/.agentic-tools` layout checks are skipped rather than failed. What it
146
+ does *not* do is write into your agents' config files: the adapter integrations
147
+ for **Claude Code**, **OpenCode**, and **Pi** are injected by the installer, not
148
+ by npm.
149
+
150
+ | | `curl … install.sh` | `npm i -g @bigknoxy/hashpilot` |
151
+ |---|---|---|
152
+ | CLI + MCP server | ✅ | ✅ |
153
+ | Auto-installs Bun | ✅ | ❌ (must already be on PATH) |
154
+ | Claude Code / OpenCode / Pi adapters | ✅ | ❌ — run `bun run install-cli` from a checkout, or configure the MCP server directly |
155
+ | Version pinning / `npx` | ❌ | ✅ |
156
+
157
+ For MCP clients, point at the binary and skip the adapters entirely — see
158
+ [docs/INTEGRATION-MCP.md](docs/INTEGRATION-MCP.md).
159
+
160
+ ### Install from a checkout (development)
161
+
162
+ ```bash
163
+ bun install
164
+ bun run install-cli # symlinks the launcher AND adds ~/.agentic-tools/bin to your shell rc
165
+ exec $SHELL -l # or: export PATH="$HOME/.agentic-tools/bin:$PATH"
166
+ hashpilot doctor
167
+ ```
168
+
169
+ `install-cli` writes a marked block (`# >>> hashpilot path >>>`) into the rc file
170
+ for your current `$SHELL`; `hashpilot uninstall` removes it. `doctor`'s
171
+ `bin-on-path` check reports when the directory is missing from `PATH`, so a
172
+ launcher that exists but cannot be resolved is a visible failure rather than a
173
+ silent one.
174
+
175
+ ### Upgrade
176
+
177
+ ```bash
178
+ hashpilot upgrade # upgrade to latest from main
179
+ hashpilot upgrade --dry-run # preview what would happen
180
+ ```
181
+
182
+ ### Uninstall
183
+
184
+ ```bash
185
+ hashpilot uninstall # remove everything (prompts for confirmation)
186
+ hashpilot uninstall --keep-config # remove binaries, keep config + telemetry
187
+ hashpilot uninstall --dry-run # preview what would be removed
188
+ hashpilot uninstall --force # skip confirmation prompt
189
+ ```
190
+
191
+ ### Runtime support matrix
192
+
193
+ HashPilot is Bun-only today. The core uses Bun APIs and ships as TypeScript source, so
194
+ there is no Node-compatible build yet.
195
+
196
+ | Runtime | Supported | Notes |
197
+ |---------|-----------|-------|
198
+ | Bun ≥ 1.2 | ✅ | The only supported runtime. Enforced by `engines.bun`. |
199
+ | Bun < 1.2 | ❌ | `npm`/`bun` warn at install time via `engines`. |
200
+ | Node.js (any version) | ❌ | `hashpilot` exits **127** with an install message pointing at https://bun.sh. |
201
+
202
+ Installing from npm on a platform with no prebuilt `tree-sitter` binding (notably
203
+ **linux-arm64**) requires a C++20-capable toolchain to build it from source, and
204
+ fails at install time without one. linux-x64, darwin-x64, darwin-arm64, and
205
+ win32-x64 ship prebuilds and need no toolchain.
206
+
207
+ The `hashpilot` binary is a small CommonJS shim (`src/cli-node.cjs`) that any Node can
208
+ parse. It hands off to Bun and forwards Bun's exit status unchanged, so a Node-only machine
209
+ gets one actionable line instead of a syntax-error stack trace.
210
+
211
+ ```bash
212
+ # Verify it works — human-readable by default in a TTY, JSON when piped or in CI
213
+ hashpilot doctor # TTY: human-readable summary
214
+ hashpilot doctor --format json
215
+ # --json still works but is deprecated (emits a warning on stderr)
216
+ hashpilot doctor --json
217
+
218
+ # See your merged config
219
+ hashpilot config
220
+ ```
221
+
222
+ ### Your First Edit
223
+
224
+ ```bash
225
+ # 1. Read a file — get its content hash
226
+ hashpilot read-many src/main.ts
227
+
228
+ # 2. Edit by hash — target the exact content
229
+ HASH="abc123..." # from read-many output
230
+ hashpilot replace-hash src/main.ts "$HASH" " port: 8080" --range 5:5
231
+
232
+ # 3. Verify nothing broke
233
+ hashpilot verify-changes src/main.ts --auto-detect
234
+ ```
235
+
236
+ Editing the same region twice needs no second read: a successful `replace-hash`
237
+ returns `newHash` — the hash of the content it just wrote — plus `newRange`,
238
+ where that content now lives. Feed the pair back as the next call's `oldHash`
239
+ and `--range`. (`fileHash` is the whole file after the edit; it is reported for
240
+ information and is not an anchor.)
241
+
242
+ A dry run previews rather than dumps. `--dry-run` (MCP `dryRun: true`) returns a
243
+ unified `diff` of the changed hunks plus `sourceOmitted: true` — not the whole
244
+ post-edit file, which used to make previewing an edit cost more context than
245
+ making it. Pass `--include-source` (`includeSource: true`) when you really want
246
+ the full text back.
247
+
248
+ Telemetry stays a log, not an object store. Every record is capped at `telemetry.maxRecordBytes` (4 KB by default); a captured diff larger than that is written to a content-addressed store under `~/.agentic-tools/logs/payloads/` and referenced from the record by hash, which took one real edit's record from 27,526 bytes to 185. Reads rehydrate the diff, so `provenance query` and `telemetry health` see exactly what they saw before.
249
+
250
+ Every check is opt-in, so `--auto-detect` (or an explicit `--formatter` /
251
+ `--linter` / `--typecheck` / `--test-runner`) is what makes the command verify
252
+ anything. A run with no check reports `overall: "skipped"` and exits 4 rather
253
+ than the vacuous `"pass"` it used to return over an empty check set.
254
+
255
+ ---
256
+
257
+ ## How It Works
258
+
259
+ ### The 3-Tier Routing Model
260
+
261
+ ```
262
+ ┌─────────────┐
263
+ │ Your Edit │
264
+ └──────┬──────┘
265
+
266
+
267
+ ┌──────────────────────┐
268
+ │ 1. AST Route │ ◄── tree-sitter syntax-aware edits
269
+ │ (TS/TSX/JS/Python/ │ rename-symbol, replace-body,
270
+ │ Go/Rust) │ add-import, remove-import,
271
+ │ │ insert-before/after
272
+ └──────────┬───────────┘
273
+ │ unsupported
274
+
275
+ ┌──────────────────────┐
276
+ │ 2. Hash Route │ ◄── SHA-256 anchored replacement
277
+ │ (any file) │ replace-hash with stale-anchor
278
+ │ │ detection + auto-recovery
279
+ └──────────┬───────────┘
280
+ │ no hash provided
281
+
282
+ ┌──────────────────────┐
283
+ │ 3. Diff Route │ ◄── LCS-based search-and-replace
284
+ │ (fallback) │ with duplicate detection and
285
+ │ │ fuzzy matching
286
+ └──────────────────────┘
287
+ ```
288
+
289
+ The router auto-selects. A single `route-edit` command tries AST first, falls back to Hash, then Diff. Every route records telemetry and provenance.
290
+
291
+ ### The Canonical Flow
292
+
293
+ ```
294
+ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
295
+ │ read- │ │ replace- │ │ verify- │ │ done. │
296
+ │ many │───▶│ hash │───▶│ changes │───▶│ │
297
+ │ │ │ │ │ │ │ │
298
+ │ hash: │ │ content │ │ lint │ │ audited, │
299
+ │ abc123 │ │ matched │ │ typecheck│ │ verified │
300
+ └─────────┘ │ by hash │ │ tests │ └──────────┘
301
+ └──────────┘ │ revert? │
302
+ └──────────┘
303
+ ```
304
+
305
+ **Read → Edit → Verify.** Every step outputs structured JSON for agent consumption.
306
+
307
+ ---
308
+
309
+ ## Measured Behavior
310
+
311
+ HashPilot ships a benchmark harness so its safety claims are checkable rather
312
+ than asserted. `bun run bench` replays every case in [`bench/`](bench/) through
313
+ the real routing entry point and classifies the result.
314
+
315
+ **Baseline — harness v1, HashPilot v4.3.2, 23 cases:**
316
+
317
+ | Metric | Value |
318
+ |--------|-------|
319
+ | Correct | 15 |
320
+ | Correct refusals (ambiguous / stale / unparseable input, refused untouched) | 6 |
321
+ | False refusals | 0 |
322
+ | **Silent corruption** — reported success, wrong bytes on disk | **2 (8.7%)** |
323
+ | Correctness rate (excludes correct refusals) | 88.2% |
324
+
325
+ Both silent-corruption cases are grouped-import handling on the AST tier
326
+ ([#102](https://github.com/bigknoxy/HashPilot/issues/102),
327
+ [#103](https://github.com/bigknoxy/HashPilot/issues/103)) and are committed as
328
+ red regression guards. The hash and diff tiers are at 100% across their cases,
329
+ including stale-anchor refusal, anchor relocation, ambiguous-content refusal, and
330
+ unified-diff reserved tokens (`---`, `+++`, `@@`) appearing as ordinary file
331
+ content.
332
+
333
+ **Silent corruption is the number that matters.** An apply-success rate counts an
334
+ edit that "succeeded" while quietly deleting three imports as a win; this harness
335
+ counts it as corruption. A refusal costs a retry — a confident wrong answer gets
336
+ committed.
337
+
338
+ Methodology, case format, and the honest scope limits are in
339
+ [`bench/README.md`](bench/README.md). Results are committed as JSON
340
+ ([`bench/results/latest.json`](bench/results/latest.json)) so trends are
341
+ diffable, and CI fails on any case that regresses from green.
342
+
343
+ ---
344
+
345
+ ## Commands
346
+
347
+ ### Read & Search
348
+
349
+ | Command | What It Does |
350
+ |---------|-------------|
351
+ | `read-many <files...>` | Batch read files with SHA-256 content hashes |
352
+ | `read-hash <file> <line>` | Read a specific line with context hash |
353
+ | `grep-many <pattern> <paths...>` | Regex search across files. Both forms work: `hashpilot grep-many "safeWrite" src/` or `hashpilot grep-many --pattern "safeWrite" --path src/` (`--path` repeatable); supplying both is a usage error. Any parse error — unknown flag, missing argument, unknown subcommand — returns the JSON usage envelope with `INVALID_ARGUMENT` and exit 1 rather than a bare Commander line on stderr |
354
+ | `symbol-lookup-many <paths...> --names n1,n2` | Find symbol definitions by name |
355
+
356
+ ### Upgrade
357
+
358
+ | Command | What It Does |
359
+ |---------|-------------|
360
+ | `upgrade [--dry-run] [--channel <branch>] [--target <dir>] [--keep-telemetry] [--force]` | Upgrade HashPilot from GitHub to latest version |
361
+
362
+ ### Edit — Hash Route
363
+
364
+ | Command | What It Does |
365
+ |---------|-------------|
366
+ | `replace-hash <file> <hash> <content>` | Replace content identified by SHA-256 hash (auto-recovers on stale anchor) |
367
+
368
+ ### Undo
369
+
370
+ | Command | What It Does |
371
+ |---------|-------------|
372
+ | `changesets [--limit N]` | List undoable changeSets, newest first |
373
+ | `undo <changeSetId>` | Restore every file in a changeSet to its pre-edit contents |
374
+ | `undo --last` | Undo the most recent changeSet |
375
+
376
+ Every write goes to a sibling temp file, is `fsync`ed, and is renamed over the
377
+ target, so an interrupted write can never leave a truncated source file — a reader
378
+ sees either the whole old file or the whole new one, and the target's permissions
379
+ are preserved. Before the write, the file's original bytes are stored in a
380
+ content-addressed snapshot store under `~/.agentic-tools/snapshots/`, keyed by the
381
+ changeSet the invocation belongs to. `undo` refuses any file that changed after the
382
+ edit was applied unless `--force` is passed, and `--dry-run` reports without writing.
383
+ Retention defaults to 200 changeSets / 7 days, configurable under `snapshots` in
384
+ `.hashpilot.json`.
385
+
386
+ ### Edit — AST Route
387
+
388
+ | Command | What It Does |
389
+ |---------|-------------|
390
+ | `ast capabilities` | Show supported languages, operations, and limitations |
391
+ | `ast find-symbols <file>` | List all symbols (functions, classes, variables), with 1-indexed `startLine`/`endLine`/`startColumn`/`endColumn` (matching the hash tier's `range`) alongside the raw 0-indexed tree-sitter `startRow`/`endRow`/`startCol`/`endCol`. Returns `{ symbols, truncated }`; the walk is bounded at depth 200, and `truncated: true` (plus a `SEARCH_TRUNCATED` warning) means symbols nested deeper were not visited |
392
+ | `ast rename-symbol <file> <old> <new>` | Rename a symbol and all its references within **one file**, binding-aware — refuses with `AMBIGUOUS_SYMBOL` (exit `2`) when the name binds more than one symbol (a shadowed local, a foreign import, or a duplicate declaration) |
393
+ | `ast replace-body <file> <symbol> <body>` | Replace a function/method body. The body is **statements only** — no surrounding braces, no leading indentation; the command owns both and re-indents each line to the symbol. Passing either back is silent: the file still parses (#108) |
394
+ | `ast add-import <file> <spec>` | Add an import with grouped-import merging. The spec is parsed as source, so the module path is quoted: `'{ Foo } from "./bar"'`; unquoted is a `PARSE_ERROR` (#109) |
395
+ | `ast remove-import <file> <spec>` | Remove an import statement |
396
+ | `ast insert-before <file> <symbol> <content>` | Insert content before a named symbol. Only declarations anchor an insertion — a name that is only a parameter or import specifier is refused, and an ambiguous name lists every candidate. Content is indented to match the anchor |
397
+ | `ast insert-after <file> <symbol> <content>` | Insert content after a named symbol (same anchor rules and indentation as `insert-before`) |
398
+
399
+ AST edits are guarded at both ends. A file that does not already parse is refused
400
+ (`PARSE_ERROR`, exit 2, with the line and column of the break) rather than edited
401
+ against a tree tree-sitter had to error-recover; and every edit is reparsed before
402
+ anything reaches disk, so an edit that would corrupt a file that parsed cleanly is
403
+ discarded instead of written. The same post-edit check applies to hash and diff
404
+ edits whenever a parser exists for the language. `--allow-parse-errors` waives the
405
+ pre-check for deliberately editing a broken file; the post-check always stands.
406
+
407
+ There is no file-size ceiling. Through v3.0.0 every AST operation failed on any
408
+ source over 32KB — the binding's string-marshalling limit — which silently
409
+ demoted large files to the diff route.
410
+
411
+ ### Edit — Diff Route (Fallback)
412
+
413
+ | Command | What It Does |
414
+ |---------|-------------|
415
+ | `diff generate <file> <old> <new>` | Generate a unified diff |
416
+ | `diff apply <file> --patch <patch>` | Apply a patch with fuzzy matching (`--fuzzy 0` = strict: exact offset and content). An ambiguous window — context matching more than once within `--fuzzy` lines — is refused with every candidate line named, and each applied hunk reports `expectedAt` / `appliedAt` / `offset` so a hunk that slid is visible |
417
+
418
+ ### Multi-File & Intents
419
+
420
+ | Command | What It Does |
421
+ |---------|-------------|
422
+ | `route-edit <file> <operation>` | Auto-routed edit through AST → Hash → Diff |
423
+ | `batch <operation> <files...>` | Same edit on many files in parallel or serial |
424
+ | `intent <json>` | Declarative multi-file edit — plan, discover references, execute |
425
+ | `route <file> <operation>` | Preview which route would be chosen |
426
+
427
+ `intent` never invents source text. If part of the intent cannot be computed —
428
+ `add-parameter` with no `param.default` leaves nothing to pass at the call
429
+ sites — it lists the gap under `plan.unresolved` (`file`, `operation`, `reason`,
430
+ `resolution`) and refuses the whole plan with `UNSUPPORTED_OPERATION` rather
431
+ than applying it halfway. Give the parameter a default, or pass `--yes` to
432
+ apply only the steps it could compute.
433
+
434
+ ### Verification
435
+
436
+ | Command | What It Does |
437
+ |---------|-------------|
438
+ | `verify-changes <files...>` | Run formatter + linter + typechecker + tests with auto-detection and revert-on-failure |
439
+
440
+ ### Telemetry & Provenance
441
+
442
+ | Command | What It Does |
443
+ |---------|-------------|
444
+ | `telemetry summary` | Operation counts and timing |
445
+ | `telemetry health [-w <days>] [--trend]` | Health report with per-language stats and threshold warnings |
446
+ | `telemetry sessions` | List session summaries |
447
+ | `provenance query <file> [line]` | Edit history for a file (like `git blame` for agent edits) |
448
+ | `provenance changeset <id>` | All edits in a changeSet |
449
+
450
+ > All commands accept `--actor`, `--task-id`, and `--reason` for provenance tracking. Every command outputs structured JSON.
451
+
452
+ ---
453
+
454
+ ## Output Control
455
+
456
+ Global flags, valid on every command:
457
+
458
+ | Flag | Effect |
459
+ |------|--------|
460
+ | `--format json\|text` | JSON if piped or in CI, text on a TTY (default) |
461
+ | `-q, --quiet` | Drop the text-mode success line. **Never** suppresses the JSON envelope |
462
+ | `-v, --verbose` | Routing and timing diagnostics, written to **stderr** |
463
+ | `--no-color` | Disable ANSI color (`NO_COLOR` and `TERM=dumb` are honored too) |
464
+
465
+ Color is veto-only — there is no flag that forces it on. It appears only with
466
+ `--format text` on a TTY, so **JSON output is never colorized** and a piped
467
+ stdout stays byte-clean. `--quiet` wins over `--verbose` when both are passed.
468
+
469
+ Because verbose output goes to stderr, `hashpilot --verbose ... | jq` still works:
470
+
471
+ ```bash
472
+ hashpilot --verbose route-edit src/app.ts rename-symbol \
473
+ --old-name greet --new-name hello --dry-run | jq .data
474
+ # stderr: [verbose] route: ast for rename-symbol on src/app.ts (Language 'typescript' supports AST operations)
475
+ # stderr: [verbose] result: ok via ast in 3ms
476
+ ```
477
+
478
+ ---
479
+
480
+ ## Output Envelope
481
+
482
+ Every command writes the same JSON shape to stdout, so an adapter has one parse path:
483
+
484
+ ```json
485
+ {
486
+ "apiVersion": "1",
487
+ "ok": true,
488
+ "command": "read-many",
489
+ "data": [{ "path": "src/api.ts", "hash": "a1b2c3d4e5f6", "content": "...", "lines": 42 }],
490
+ "error": null,
491
+ "warnings": []
492
+ }
493
+ ```
494
+
495
+ - `data` — the per-command payload (what used to sit at the top level).
496
+ - `error` — `null` when `ok`, else `{ code, message, recovery? }`. Branch on `code`, never on `message`.
497
+ - `warnings` — non-fatal notices: `ROUTE_FALLBACK` (the edit was downgraded to a less safe
498
+ route), `ANCHOR_RELOCATED` (the anchor moved and the edit landed elsewhere),
499
+ `TELEMETRY_LOG_CORRUPT`, `VERIFY_NO_CHECKS` (`verify-changes` ran no check at all — see below).
500
+ - `ok` is derived from the exit code below, so the two never disagree.
501
+
502
+ Schema: [`schema/hashpilot-envelope.schema.json`](schema/hashpilot-envelope.schema.json).
503
+ Raw modes for piping: `diff generate --raw`, `telemetry export --ndjson`.
504
+
505
+ **Breaking in v3.0.0** (from v2.x, which returned a different shape per command) — see
506
+ [`docs/ADAPTER-CONTRACT.md`](docs/ADAPTER-CONTRACT.md) for migration.
507
+
508
+ ---
509
+
510
+ ## Exit Codes
511
+
512
+ Every command exits with a stable code so agents and CI can branch on the result
513
+ without parsing text.
514
+
515
+ | Code | Meaning | What to do |
516
+ |------|---------|-----------|
517
+ | `0` | Success | Continue |
518
+ | `1` | Usage error — bad arguments, denied path, unsupported operation | Fix the invocation |
519
+ | `2` | Edit failed | Try another route or report |
520
+ | `3` | Stale anchor / precondition failed | **Retryable:** re-read and retry with the fresh hash |
521
+ | `4` | Verification failed (format/lint/test) | Inspect the verify output |
522
+ | `5` | I/O error | Check the path and permissions |
523
+ | `70` | Internal error | File a bug |
524
+
525
+ Batch commands return the worst code across all items.
526
+
527
+ ### `doctor` reuses codes 1 and 2 with its own meaning
528
+
529
+ `doctor` is a health report, not an edit, so it maps its result onto the same
530
+ numbers: `0` healthy, `1` warnings only (degraded, still usable), `2` one or
531
+ more checks failed. The overlap with `1` (usage) and `2` (edit failed) is
532
+ deliberate — "non-zero means do not proceed" is the property scripts actually
533
+ branch on, and `scripts/install.sh` fails the install on `2`.
534
+
535
+ Doctor's checks are scoped to how HashPilot is running (`installMode` in the
536
+ report): `installed`, `source` (a working checkout), or `package` (npm). The
537
+ `~/.agentic-tools` layout checks report `skip` outside an installed copy, and a
538
+ missing agent-host integration (OpenCode, Pi, Claude) is a `skip` too — nobody
539
+ has every host on one machine. **Only a `fail` makes an install unhealthy.**
540
+ Every `fail` carries a `remediation` string: the exact command that fixes it.
541
+
542
+ ```console
543
+ $ hashpilot --format text doctor
544
+ HashPilot 4.4.18 — ✓ healthy (installed install)
545
+ pass 18 fail 0 warn 0 skip 0
546
+ ✓ ast-parsers: All 6 tree-sitter parsers load
547
+ ...
548
+ ```
549
+
550
+ The `ast-parsers` check is the one that catches a class of silent failure:
551
+ `getParser()` swallows tree-sitter load errors and the router then quietly
552
+ downgrades AST edits to diff, so a broken native build shows up only as
553
+ mysteriously worse edits. `doctor` probes every language and reports the real
554
+ error, with `bun install` as the remediation.
555
+
556
+ ---
557
+
558
+ ## Where HashPilot Will Write
559
+
560
+ By default HashPilot only writes inside the project root (the nearest ancestor
561
+ containing `.git`). Anything else fails with `PATH_DENIED` and exit code `1`.
562
+
563
+ ```bash
564
+ hashpilot --allowed-root /srv/generated ast rename-symbol ... # widen for one run
565
+ hashpilot --allow-outside-root ... # disable containment
566
+ ```
567
+
568
+ ```json
569
+ { "allowedRoots": ["/srv/generated"] }
570
+ ```
571
+
572
+ Some locations are **never** writable, and neither `allowedRoots` nor
573
+ `--allow-outside-root` re-enables them: `~/.ssh`, `~/.aws`, `~/.gnupg`, `/etc`,
574
+ shell startup files (`~/.zshrc`, `~/.bashrc`, `~/.profile`), and HashPilot's own
575
+ telemetry log. Symlinks are resolved before the check, so a link inside the
576
+ project that points outside it is still refused.
577
+
578
+ ---
579
+
580
+ ## Telemetry and Privacy
581
+
582
+ HashPilot writes a local JSONL event log to `~/.agentic-tools/logs/`. Nothing is
583
+ ever sent off the machine.
584
+
585
+ **Turning it off** — highest priority first:
586
+
587
+ ```bash
588
+ hashpilot --no-telemetry ast rename-symbol ... # one invocation
589
+ export HASHPILOT_TELEMETRY=0 # whole shell (also: false, off, no)
590
+ ```
591
+
592
+ ```json
593
+ { "telemetry": { "enabled": false } }
594
+ ```
595
+
596
+ **What is in the log.** Operation name, route, file path, language, success,
597
+ elapsed time, and any `--actor` / `--task-id` / `--reason` you pass. Source code
598
+ is *not* recorded by default: the log holds content hashes, not content.
599
+
600
+ **Diff capture is opt-in.** Setting `provenance.captureDiffs` records a unified
601
+ diff of each edit, which puts real source lines on disk in plaintext:
602
+
603
+ ```json
604
+ { "provenance": { "captureDiffs": true } }
605
+ ```
606
+
607
+ Even then, files that are secret by definition are never diffed — `.env*`,
608
+ `*.pem`, `*.key`, `*.p12`, `*.pfx`, `id_rsa`/`id_ed25519`, `credentials`,
609
+ `.npmrc`, `.netrc`, `secrets.{yaml,json,toml}`. Their hashes still record *that*
610
+ the file changed.
611
+
612
+ **Redaction.** Everything written to the log is scrubbed for credential shapes
613
+ first — AWS keys, OpenAI/Anthropic/GitHub/Slack/Google tokens, JWTs, private-key
614
+ blocks, `Authorization` headers, passwords in connection strings, and any
615
+ `secret`/`token`/`password`/`api_key`-named assignment. Matches are replaced with
616
+ `[REDACTED]`. The log directory is created `0700` and the log file `0600`;
617
+ pre-existing logs from older versions are tightened on the next write.
618
+
619
+ **Retention is enforced automatically.** The log rotates at
620
+ `telemetry.maxFileSize` (10 MB) keeping `telemetry.maxRotatedFiles` (10), and
621
+ rotated logs past `telemetry.retentionDays` (30) are deleted without anyone
622
+ running `hashpilot telemetry prune` — the sweep is gated by a marker file to at
623
+ most once a day, so the cost on every other invocation is a single `stat`.
624
+ Orphaned diff payloads are collected in the same pass. `telemetry health`
625
+ reports `diskBytes` and `hashpilot doctor` reports the store size, both warning
626
+ past 100 MB.
627
+
628
+ ---
629
+
630
+ ## Integrations
631
+
632
+ HashPilot installs adapters for the three major coding agent platforms:
633
+
634
+ | Platform | What Gets Installed |
635
+ |----------|-------------------|
636
+ | **Claude Code** | HashPilot section injected into `~/.claude/CLAUDE.md` teaching Claude to use `hashpilot` commands |
637
+ | **OpenCode** | Skill at `~/.config/opencode/skills/hashpilot/` + subagent at `~/.config/opencode/agent/hashpilot.md` |
638
+ | **Pi** | Native extension at `~/.pi/agent/extensions/hashpilot.ts` with 7 custom tools and `/hp` slash command |
639
+
640
+ ### MCP (recommended)
641
+
642
+ HashPilot is also an MCP server, which is the recommended way to wire it into any
643
+ MCP-capable host — Claude Code, Claude Desktop, Cursor, Zed, and others:
644
+
645
+ ```bash
646
+ claude mcp add hashpilot -- hashpilot mcp --stdio
647
+ ```
648
+
649
+ Every tier shows up as a typed tool with the same locking, provenance, and
650
+ undo guarantees the CLI has, and multi-line content with quotes or backticks
651
+ rides inside JSON instead of fighting the shell. Copy-paste configs per host:
652
+ **[docs/INTEGRATION-MCP.md](docs/INTEGRATION-MCP.md)**. The CLI remains fully
653
+ supported and is the fallback for hosts without MCP, and for shell and CI use.
654
+
655
+ All adapters follow the [Adapter Contract](docs/ADAPTER-CONTRACT.md) — a machine-readable JSON protocol any agent can consume.
656
+
657
+ ---
658
+
659
+ ## Architecture
660
+
661
+ ```
662
+ ┌──────────────────────────────────────────────────────────────┐
663
+ │ hashpilot CLI │
664
+ │ (Commander-based, Bun) │
665
+ ├─────────┬──────────┬──────────┬──────────┬───────────────────┤
666
+ │ Read │ AST │ Hash │ Diff │ Verify + Batch │
667
+ │ Search │ Ops │ Ops │ Ops │ + Intent + Route │
668
+ ├─────────┴──────────┴──────────┴──────────┴───────────────────┤
669
+ │ Router (auto-select) │
670
+ │ chooseRoute(): AST → Hash → Diff │
671
+ │ routeEdit(): execute + telemetry + provenance │
672
+ ├──────────────────────────────────────────────────────────────┤
673
+ │ Cross-Cutting Layers │
674
+ │ • Telemetry (JSONL) • Provenance (agent git blame) │
675
+ │ • Config (env→CLI→project→global) • Error/exit codes │
676
+ └──────────────────────────────────────────────────────────────┘
677
+ ```
678
+
679
+ **Key Modules:** `cli.ts` (entry), `router.ts` (dispatch), `ast-edit.ts` (tree-sitter), `hash-edit.ts` (SHA-256), `diff-engine.ts` (LCS), `read.ts`, `grep.ts`, `intent.ts` (M5), `plan-executor.ts`, `verify.ts`, `provenance.ts`, `telemetry.ts`, `config.ts`, `batch-edit.ts`, `doctor.ts`.
680
+
681
+ For deep design rationale, module internals, data flow, and all architecture decisions, see the **[design doc](docs/ARCHITECTURE.md)**.
682
+
683
+ **AST Language Support:**
684
+
685
+ | Language | Extensions | All 7 Operations |
686
+ |----------|-----------|-----------------|
687
+ | TypeScript | `.ts` (not `.d.ts`) | ✓ |
688
+ | TSX | `.tsx` | ✓ |
689
+ | JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | ✓ |
690
+ | Python | `.py` | ✓ |
691
+ | Go | `.go` | ✓ |
692
+ | Rust | `.rs` | ✓ |
693
+
694
+ ---
695
+
696
+ ## Configuration
697
+
698
+ Layered config. Highest priority wins:
699
+
700
+ 1. `HASHPILOT_ROUTE_POLICY` env var
701
+ 2. `--config <path>` CLI flag
702
+ 3. `.hashpilot.json` in project root
703
+ 4. `~/.config/hashpilot/config.json`
704
+ 5. Built-in defaults
705
+
706
+ ```json
707
+ {
708
+ "routePolicy": {
709
+ "languageOverrides": { "python": "hash", "go": null },
710
+ "operationOverrides": { "add-import": "diff" },
711
+ "conflictResolution": "operation"
712
+ },
713
+ "telemetry": { "enabled": true },
714
+ "provenance": { "captureDiffs": false },
715
+ "allowedRoots": []
716
+ }
717
+ ```
718
+
719
+ In `languageOverrides` and `operationOverrides`, `null` **unsets** an entry inherited
720
+ from a lower-priority config — the example above forces Python to the hash tier while
721
+ opting out of whatever `go` rule the global config set.
722
+
723
+ See [Telemetry and Privacy](#telemetry-and-privacy) and
724
+ [Where HashPilot Will Write](#where-hashpilot-will-write) for what those last two do.
725
+
726
+ ---
727
+
728
+ ## Development
729
+
730
+ ```bash
731
+ git clone https://github.com/bigknoxy/HashPilot.git
732
+ cd HashPilot
733
+ bun install
734
+ bun test # 424 tests
735
+ bun run build # Build CLI to dist/
736
+ bun test tests/hash-edit.test.ts # Single test file
737
+ bun test -t "test name pattern" # Filter by test name
738
+ ```
739
+
740
+ ---
741
+
742
+ ## Why Not Just Use sed / grep / awk?
743
+
744
+ | Tool | Problem | HashPilot |
745
+ |------|---------|-----------|
746
+ | `sed` | Line-number based, fragile | Hash-anchored, recovery on stale anchors |
747
+ | `grep + sed` | Wrong match on first occurrence | Cryptographic content identity |
748
+ | `awk` | Pattern-based, no AST awareness | Tree-sitter AST for syntax-safe edits |
749
+ | Manual edit | 3-5 retries per change | 1-2 operations, no re-reading |
750
+
751
+ HashPilot isn't competing with Unix tools — it's the infrastructure layer that lets AI agents use those tools correctly.
752
+
753
+ ---
754
+
755
+ ## License
756
+
757
+ MIT — see [LICENSE](LICENSE).
758
+
759
+ ---
760
+
761
+ ## Project Status
762
+
763
+ Active development. Core editing engine, AST operations, telemetry, and all three adapter integrations are production-ready. Intent-based editing (M5) and provenance tracking (M6) are available as preview features.
764
+
765
+ **Docs policy:** The landing page (README.md) and [design doc](docs/ARCHITECTURE.md) are living documents. Every PR that touches `src/` must update one or both. Every deploy is verified with browser automation. See the CI check `docs-verify`.
766
+
767
+ **Agent quick reference:** [docs/CLI-QUICKREF.md](docs/CLI-QUICKREF.md) is the one page an agent should read before invoking the CLI — every command, flag, output shape, exit code, and the gotchas that otherwise cost a guess-and-retry loop. Its command reference is generated from the CLI's own `--help`, and `bun run lint:docs` (run in CI and by `bun test`) fails if the doc drifts from the binary or if `ROADMAP.md` grows a duplicate or out-of-order row.
768
+
769
+ v1.3.1 — [Release notes](https://github.com/bigknoxy/HashPilot/releases)
770
+
771
+ <!-- agent-skills:doc-keeper:start -->
772
+ ## Reference (auto-tracked by doc-keeper)
773
+
774
+ ### Environment Variables
775
+ - `HASHPILOT_TELEMETRY`: set to `0`/`false`/`off`/`no` to disable telemetry logging. Overridden by `--no-telemetry`.
776
+ - `HASHPILOT_ROUTE_POLICY`: JSON route policy, highest-priority config layer.
777
+ <!-- agent-skills:doc-keeper:end -->