Skip to content

feat: add Claude Code hooks integration#13

Open
nandanugg wants to merge 6 commits intocortexkit:mainfrom
nandanugg:feat/claude-code-hooks
Open

feat: add Claude Code hooks integration#13
nandanugg wants to merge 6 commits intocortexkit:mainfrom
nandanugg:feat/claude-code-hooks

Conversation

@nandanugg
Copy link
Copy Markdown

Summary

  • Add install/uninstall scripts for Claude Code hooks integration
  • Tool interception routes Read, Grep, Glob through AFT for indexed performance
  • CLI wrapper provides semantic commands (outline, zoom, call_tree, callers, impact, trace_to)
  • Global AFT.md instructions teach Claude to use AFT for 60-90% context savings

Installation

./scripts/install-claude-hooks.sh

Test plan

  • Install script is idempotent (safe to run multiple times)
  • All semantic commands work (outline, zoom, call_tree, callers, impact, trace_to)
  • Hook interception works for Read, Grep, Glob tools
  • Test on fresh system without existing Claude Code config

🤖 Generated with Claude Code

ualtinok and others added 6 commits April 16, 2026 15:39
Add install/uninstall scripts that set up AFT hooks for Claude Code:
- Tool interception for Read, Grep, Glob via PreToolUse hooks
- CLI wrapper for semantic commands (outline, zoom, call_tree, callers, etc.)
- Global AFT.md instructions so Claude learns to use AFT for context savings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add MANDATORY section emphasizing AFT-first approach
- Add Decision Tree for quick command selection
- Rename "Best Practices" to "Rules (NOT suggestions)"
- Add Context Protection section to prevent context exhaustion
- Clarify that AFT applies to all file types, not just code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add prominent "AFT applies to ALL file types" note in MANDATORY section
- Change "Before ANY code exploration" to "Before reading ANY files"
- Add docs/config example to decision tree
- Add new row for docs/configs in "When to Use What" table
- Add rule cortexkit#6: "ALWAYS outline before sampling"

Addresses feedback that code-centric language caused AFT to be skipped
for markdown/documentation files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove Read from hooked tools list (conflicts with Edit validation)
- Add guidance to use `aft read` via Bash for indexed reads
- Add warning: use native Read tool when editing is needed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Subagents don't follow ordering guarantees, so leaving "outline first"
as a mid-step instruction doesn't work. Run outline yourself and
include the output in the subagent prompt.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ualtinok added a commit that referenced this pull request Apr 25, 2026
…llowups

Second full-codebase audit found 38 items; 32 confirmed real after triple-
verification, plus a third pass after Oracle review caught one introduced
SSRF bypass.

Security:
- aft-cli: createGitHubIssue uses spawnSync with argv to prevent shell
  injection through repo/title (#6).
- opencode + pi: restrict_to_project_root defaults to true for plugin
  contexts; the Rust CLI default stays false for direct/scripted use (#1).
- opencode: per-server random RPC token (32B hex) stored in JSON port file;
  every request requires the token; legacy integer port files still parsed
  for backward compatibility (#23).
- opencode: url-fetch SSRF guard with manual redirect handling (max 5 hops),
  full IPv6 expansion, and IPv4-mapped/compatible bypass detection
  (::ffff:127.0.0.1, ::127.0.0.1, [::]); allowPrivate escape hatch (#32 +
  Oracle followup).
- Rust: handle_git_conflicts now validates each conflicted file path
  through ctx.validate_path() (#20).

Cross-plugin parity:
- pi-plugin pool keys bridges by realpathSync canonicalization, mirroring
  opencode (#5).
- pi-plugin zoom multi-symbol fan-out routes through callBridge so each
  parallel request carries Pi's session_id (#16 — regression from v0.15.3).
- pi-plugin tool-surface ALL_ONLY constants align with opencode (#9).
- both bridges enforce 64MB MAX_STDOUT_BUFFER and treat overflow as crash
  (#10).
- pi-plugin aft_transform validates per-op required parameters (#17).
- both bridges' compareSemver implements semver pre-release ordering (#29).

Rust correctness:
- glob edit_match wraps multi-file writes in checkpoint snapshot with
  rollback on failure (#3).
- LSP client kills+waits child on shutdown timeout and via Drop impl (#4).
- type-checker working_dir uses config.project_root, not path.parent() (#7).
- ast_search/grep return invalid_pattern errors instead of empty matches
  on malformed regex/AST patterns (#11).
- zoom ambiguous suggestions output 1-based start-end line ranges (#12).
- zoom line-range response uses clamped end_line (#13).
- configure.validate_on_edit accepts booleans (#18).
- checkpoint restore creates parent directories (#19).
- lsp_hints paths_match uses canonical comparison + separator-bounded
  suffix matching (#22).
- format.resolve_tool --version probe has 2s timeout (#24).
- backup.canonicalize_key fallback logs at debug (#25).
- read.handle_directory caps at 1000 entries with truncation note (#34).
- read uses saturating_add/sub for end_line math (#36).
- lsp_rename + lsp_find_references use consistent 1-based character (#37).
- ast_search/replace comments now reference panic=unwind (#27).

Workflow + docs:
- release.yml has top-level concurrency control (#35).
- test job runs bun build before publish-crates (#8).
- release.yml uses sha256sum on Ubuntu (#29).
- version-sync.mjs comment reflects 9 packages (#28).

Verification: 726 Rust tests pass / 1 ignored (was 718, +8 new tests),
383 TS tests pass (was 362, +21 new tests covering RPC auth, SSRF guard,
pool canonicalization, semver pre-release, structure validation, edit_match
atomicity, IPv4-mapped IPv6 bypass detection). Typecheck + lint clean.
ualtinok pushed a commit that referenced this pull request Apr 30, 2026
Audit findings #12 and #13 from the v0.18 council (verified solo
findings from MiniMax 2.7).

* BUG-18 — transaction only called the lightweight
  `lsp_notify_file_changed`, unlike write_match / batch / write which
  call `lsp_post_write` and surface diagnostics inline. Multi-file
  transactions silently skipped diagnostic collection. Now calls
  `lsp_post_write` per successful write, threads multi_file_write_paths
  through for the workspace/didChangeWatchedFiles flow, merges per-file
  outcomes (diagnostics, pending_servers, exited_servers), and surfaces
  `lsp_diagnostics`, `lsp_complete`, `lsp_pending_servers`,
  `lsp_exited_servers` on the response — matching every other write
  path.

* BUG-19 — `compute_new_content` silently picked fuzzy_matches[0]
  when multiple matches existed, while dry-run correctly errored with
  `ambiguous_match`. So dry-run says "this is ambiguous" but apply
  silently rewrites the FIRST match — could be the wrong one. Both
  paths now share the same `find_single_fuzzy_match` helper that
  errors on ambiguity.

* Match-not-found stayed `transaction_failed` (not `invalid_request`):
  the request itself is well-formed, only the data didn't match. The
  transaction-rolls-back-on-failure e2e test asserts this contract.
ualtinok added a commit that referenced this pull request Apr 30, 2026
…al (#19, #13)

Background bash tasks now survive aft restart. Spawned children are detached
from the aft process via setsid() and write their output directly to disk;
task metadata is persisted to <storage>/bash-tasks/<session_hash>/<task_id>.json
so a fresh aft on startup can rehydrate Running tasks and redeliver pending
completions.

Architecture (Oracle-reviewed):
- Pre-spawn metadata write (status=Starting) BEFORE Command::spawn() so a
  crash in the spawn window leaves a recoverable record on disk instead of
  an orphaned child with no trace. Replay marks Starting tasks as Failed.
- Detached spawn via libc::setsid() in pre_exec, replacing process_group(0)
  on the bg path. Foreground bash unchanged.
- stdin: null. stdout/stderr redirected directly to disk files via
  Stdio::from(File::create(...)) — child writes the file, aft never reads
  pipes, no Rust reader thread to keep alive.
- Shell wrapper writes exit code via temp+rename (atomic) so readers never
  observe partial .exit content. User command is passed via argv ($1),
  NEVER interpolated into the wrapper body — string injection safe.
- Terminal-state monotonic: once .json is Killed/Failed/Completed/TimedOut,
  no later observation can flip it. Late .exit appearance after a kill
  doesn't change status.
- Completion durability: completion_delivered: bool field in .json. On
  startup replay, terminal-state tasks with completion_delivered=false
  re-enqueue into the in-memory queue. Plugin-side bash_drain_completions
  marks delivered=true after successful drain.
- Kill semantics: idempotent. bash_kill checks for .exit existence — if
  present (shell wrapper got out before kill), leave the file alone; if
  absent, write 'killed' marker. Either way, .json transitions to Killed.
- Single watchdog thread polls .exit files every 500ms for completion
  signaling and enforces timeouts (default 30 min, configurable per call).
- main.rs detach() on stdin EOF replaces shutdown() — children survive
  graceful aft exit; only explicit cleanup kills.
- Output reads tail directly from .stdout/.stderr files; 100MB cap enforced
  ONLY post-terminal (truncating live files would create sparse holes).
- Windows path returns 'background bash is not yet supported on Windows'
  pending Job Objects + DETACHED_PROCESS implementation.

New status variants: Starting, Killing, TimedOut.

Files:
- NEW: bash_background/persistence.rs (210 lines) — atomic JSON I/O,
  exit-file parsing, terminal-state guard, session-scoped paths.
- NEW: bash_background/watchdog.rs (45 lines) — polling thread.
- NEW: tests/integration/bash_background_persistence_test.rs (364 lines)
  — covers spawn detached, replay across restart, exit-file atomicity,
  terminal-state monotonicity, completion durability, kill idempotency,
  disk-tail reads without truncating live files, watchdog deadline,
  session isolation, stale-Running cleanup.
- REWRITTEN: bash_background/registry.rs (838 lines, was 612) —
  detached spawn, in-memory state derived from disk on startup,
  idempotent terminal transitions, completion_delivered tracking,
  detach() vs shutdown() split.
- REWRITTEN: bash_background/buffer.rs (146 lines, was ~400) —
  disk-only reads, post-terminal cap.
- backup.rs: hash_session helper exposed pub(crate) for shared use.
- bash_background/mod.rs, process.rs: re-exports + new variants.
- commands/configure.rs: replay invocation.
- main.rs: detach() on stdin EOF.

Existing timeout test in bash_background_test.rs updated to match new
TimedOut terminal status (was previously Failed).

Test count: 937 → 955 Rust tests (+18 passing, 0 failing, 1 ignored).
Plugin counts unchanged (no plugin layer changes).

Closes council findings #19, #13. Foreground bash unchanged. Plugin
protocol unchanged — drain_completions transparently sees disk-rehydrated
completions.

Implements v0.18 detached bash architecture per design Oracle-reviewed
in this session.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants