feat(install): support project-local agent setup - #3
Conversation
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1e15e3a043
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Generate Cursor project rules, permissions, and supported hooks from the local installer so Cursor can prefer tokensave safely within a repo.
Add installer-generated Cursor project hooks for the local install: - sessionStart: steer the Agent toward tokensave MCP tools and report index freshness (fire-and-forget). - afterShellExecution: on Agent-run git checkout/switch/worktree add, bootstrap /maintain branch tracking via the shared library branch-add path (idempotent, no subprocess); other state-changing git commands run a coalesced incremental sync. - workspaceOpen: ensure the current branch DB exists (branch add if missing) and run a catch-up incremental sync. Performance: afterFileEdit now does a TARGETED single-file sync of only the edited path(s) via sync_if_stale_silent (matcher "Write"), never a full-tree scan, since scan cost scales with repo size not edit size and the Agent can edit many files per turn. All Cursor hooks are fail-open and only act when .tokensave/ exists. The installer now reconciles its own hook entries on reinstall so refinements like the afterFileEdit matcher reach pre-existing configs. Caveat (documented): Cursor hooks only observe the Cursor Agent's own actions and IDE lifecycle. Manual/external-terminal git checkout and in-place branch switches are not seen (workspaceOpen does not fire for in-place checkout); the git post-commit hook and on-demand MCP staleness check cover those.
Codex supports a Claude-style lifecycle hook system (verified against 0.136.0); the old "Codex has no hook system" premise was false and is removed. Mirror the Cursor hook set for Codex on both global and project-local installs. New hidden hook subcommands (Codex stdin JSON -> Codex stdout schema): - hook-codex-session-start / hook-codex-user-prompt-submit: emit hookSpecificOutput.additionalContext steering toward tokensave MCP tools + index-freshness hint; UserPromptSubmit also resets the per-project local counter. - hook-codex-subagent-start: redirect research/explore subagents via additionalContext (Codex SubagentStart cannot hard-stop a subagent). - hook-codex-post-tool-use: apply_patch edits -> targeted single-file sync of the patched paths; Bash git commands -> shared classifier routing branch switches to `branch add` and other state changes to a coalesced incremental sync. Fail-open and silent. Installer writes/merges ~/.codex/hooks.json (global) and <root>/.codex/hooks.json (local) idempotently with backups, registering SessionStart, UserPromptSubmit, SubagentStart, and PostToolUse (matcher Bash|apply_patch). Prints the `/hooks` trust-gate guidance and doctor reports hook presence + the trust requirement. PreToolUse is intentionally not installed (documented partial guardrail); accounting parser and session search are deferred to a follow-up. Reuses shared helpers (counter reset, targeted sync, branch add, git-command classifier, coalesced sync). Track .codex/ + AGENTS.md in git to match the committed Cursor local-install precedent.
… hooks Introduce a shared, pure soft-hint engine (src/hooks/tool_hints.rs) that returns model-visible context only — it never denies, rewrites, or blocks tool calls. Hints are conservative (high-confidence search / broad-read / call-graph / impact / symbol / file-lookup patterns and explore subagents) and deduped to one per category per session. Wire the engine into agent-specific adapters: - Cursor: subagentStart and a new preToolUse handler emit nonblocking `additional_context` only (continue: true), never deny. - Codex: a new PreToolUse handler emits `hookSpecificOutput.additionalContext` only, matching Codex's documented schema; SubagentStart steers via context. Register the new soft-hint hooks in the installers: - Cursor `.cursor/hooks.json` gains `preToolUse` (matcher Shell|Bash|Read|ReadFile|Grep|Glob|Search|Task). - Codex `.codex/hooks.json` gains `PreToolUse` (matcher Bash|Read|Grep|Glob|Search|Task) and uninstall now reconciles it. Both remain idempotent and preserve foreign hooks. Also includes branch-tracking hardening (rollback on failed branch add, path-traversal guards), install-write hardening, and the session schema foundation (sessions/session_messages tables + FTS in global_db, plus the provider-neutral SessionRecord/SessionMessageRecord types). Transcript parsers and the sessions CLI are intentionally deferred to the next phase. Regenerate the repo's committed `.cursor/` and `.codex/` configs via the installer (kept as installer output per project policy) and document the soft-hint behavior + generated-config policy in README and USER-GUIDE.
Cursor's afterFileEdit event carries a single top-level `file_path`; its `edits` array holds change ranges, not per-edit paths. Drop the speculative defensive loop (and the dedup Vec it required) in favor of a single-path early return. Behavior is unchanged for the documented schema.
Flake ScriptedAlchemy#3: `broker_bounds_lsp_document_write_hangs` and `broker_drops_lsp_client_after_partial_diagnostics_frame_timeout` intermittently failed on loaded runners. Both tests force an engine crash, then re-run a healthy fake LSP server and assert it recovers. The recovery collection paid a tight 150ms (`FAKE_LSP_TIMEOUT`) wall-clock budget that must cover a real python spawn plus a didOpen -> publishDiagnostics round trip. Under load that spawn can exceed 150ms, so the recovery `refresh_documents(...).unwrap()` false-fired with a spurious timeout. Fix (test only): the two recovery calls now use a dedicated generous `FAKE_LSP_RECOVERY_TIMEOUT` (3s), well under the 6s outer async bound so a genuine hang is still caught. The crash-forcing first half keeps its tight 50ms internal timeout — it is *meant* to time out, and load only makes that expected outcome more certain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
tokensave install --localfor project-scoped agent setup without mutating user-level install tracking or global git hooks..cursor/mcp.json,.cursor/rules/tokensave.mdc,.cursor/permissions.json, and.cursor/hooks.jsonfrom the installer..tokensave/exists):sessionStart: steer the Agent toward tokensave MCP tools + report index freshness.subagentStart: block research/explore subagents until tokensave was tried.beforeSubmitPrompt: reset the local token counter.afterFileEdit(matcherWrite): targeted single-file sync of only the edited path(s) viasync_if_stale_silent— never a full-tree scan (scan cost scales with repo size, not edit size; the Agent can edit many files per turn).afterShellExecution: branch-aware. Agent-rungit checkout/switch/worktree addbootstraps/maintains tokensave branch tracking via the shared library branch-add path (idempotent, no subprocess); other state-changing git commands (pull/merge/rebase/reset/cherry-pick/stash apply|pop) run a coalesced incremental sync.workspaceOpen: ensure the current branch DB exists (branch add if missing) + catch-up incremental sync.afterFileEditmatcher) reach pre-existing configs..cursor/*artifacts from the installer (not hand-edited).Codex lifecycle hooks (added)
Codex now ships a Claude-style lifecycle hook system (verified against Codex 0.136.0). The old
src/agents/codex.rspremise "Codex has no hook system" was false and has been removed. This adds a Codex hook set mirroring the Cursor work, on both global (~/.codex/hooks.json) and project-local (<root>/.codex/hooks.json) installs, written in Codex's nestedhooks[event] -> [{ matcher?, hooks: [{ type:"command", command, timeout }] }]shape and reconciled idempotently (foreign hooks preserved, backups on overwrite). Each handler reads Codex's single stdin JSON event and emits Codex-shaped stdout (not the Claude/Cursor/Kiro shapes).New hidden subcommands:
hook-codex-session-start(SessionStart) /hook-codex-user-prompt-submit(UserPromptSubmit): emithookSpecificOutput.additionalContextsteering toward tokensave MCP tools + an index-freshness hint (suggesttokensave initif uninitialized). UserPromptSubmit also resets the per-project local counter. Project root resolved from the eventcwd.hook-codex-subagent-start(SubagentStart): redirect research/explore subagents toward tokensave MCP tools viaadditionalContext. CodexSubagentStartcannot hard-stop a subagent (continue:falseis ignored for this event per the docs), so this steers rather than denies — a deliberate deviation from the CursorsubagentStartdeny.hook-codex-post-tool-use(PostToolUse, matcherBash|apply_patch):apply_patchedits → targeted single-file sync of just the patched paths (parsed from the patch envelope, no full-tree scan);Bashgit commands → reuse the shared classifier (cursor_shell_sync_plan) to route branch switches →branch::add_branch_trackingand other state-changing commands → coalesced incremental sync. Fail-open and silent.Shared helpers are reused (counter reset, targeted sync, branch add, git-command classifier, and a new
run_coalesced_incremental_sync). Codex installer also prints the/hookstrust-gate guidance andtokensave doctor --agent codexreports hook presence + the trust requirement. The repo's.codex/*+AGENTS.mdare regenerated from the installer and tracked in git, matching the committed Cursor local-install precedent (.codex/removed from.gitignore).Performance decision
afterFileEdit/ Codexapply_patchpost-tool-use deliberately avoidTokenSave::sync()(fullscan_files()over the whole tree) and instead sync only the edited paths. Shell/workspace events use a full incrementalsync()because a broad change set is expected there (coalesced/guarded for back-to-back git commands).Blind spots (intentional, documented)
git checkoutand in-place branch switches are NOT seen (workspaceOpendoes not fire for in-place checkout).PostToolUseonly fires forapply_patchedits and "simple" Bash — raw-shell file edits,unified_exec, andWebSearchare not observed; there is no first-class branch-switch event, so branch switches are derived from Bashgitcommands. CodexPreToolUseis intentionally not installed (documented partial guardrail).Test plan
cargo test --test hooks_test(Cursor + Codex pure classifiers, routing, session context, targeted-edit / apply_patch path resolution, cwd-based project root)cargo test --test agent_test(Cursor + Codex installer output incl. matchers + idempotent reconciliation; global + local)cargo test(full suite — all passing)cargo fmt --checkgit diff --checkcargo run -- install --local --agent cursorcargo run -- install --local --agent codex