Skip to content

feat: bundle handoff-watchdog Claude Code hooks as SDK installer assets #135

@Gradata

Description

@Gradata

Problem

Issue #127 landed the context-pressure handoff watchdog as:

  • SDK library code in Gradata/src/gradata/contrib/patterns/handoff.py (HandoffWatchdog, HandoffDoc, pick_latest_unconsumed, consume_handoff)
  • Claude Code runtime wiring in two JS hooks: .claude/hooks/user-prompt/handoff-watchdog.js + .claude/hooks/session-start/handoff-inject.js (commits f02fde5a + 89cd7fae)

The JS hooks are tracked in this repo via a .gitignore carve-out, but that only helps fresh clones of this repo on this machine. A user on a different machine who does pip install gradata gets the Python pattern but not the Claude Code wiring — the watchdog stays dormant because nothing in their .claude/hooks/ calls HandoffWatchdog.check() or reads the bridge file.

Proposed behavior

Extend Gradata/src/gradata/hooks/_installer.py so it can install Claude-Code-specific JS hooks alongside the existing Python hooks:

  1. Ship the JS hooks as package data. Move (or duplicate) handoff-watchdog.js and handoff-inject.js into Gradata/src/gradata/hooks/assets/claude_code/ and include them in pyproject.toml [tool.setuptools.package-data].
  2. Installer copy step. Add a function in _installer.py that, when the target project has a .claude/hooks/ directory, copies the JS assets into the correct subdirs (user-prompt/, session-start/). Preserve executable bit.
  3. Settings registration. The two hooks need to fire as UserPromptSubmit and SessionStart entries in ~/.claude/settings.json. Since settings.json is already owned by _installer.generate_settings(), add new HOOK_REGISTRY-style entries for the JS hooks (probably a new type=js_asset variant, since the current registry only handles Python modules).
  4. CLI entrypoint. gradata install-hooks --include-watchdog (or enabled by default at STANDARD profile) triggers the copy + settings update.
  5. Idempotent. Re-running the installer should not duplicate hook entries or clobber user edits — diff against the existing file first.

Out of scope

  • Rewriting the JS hooks in Python (they intentionally read Claude Code's statusline bridge file, which is a JS/Node runtime thing)
  • Cross-provider support (only Claude Code has the hook surface today; Cursor/Windsurf equivalents are a separate issue)

Acceptance

  • JS hook files live under Gradata/src/gradata/hooks/assets/claude_code/ and are included as package data
  • _installer.py has a install_js_hooks(project_dir) function (or similar) that copies them into .claude/hooks/ when invoked
  • HOOK_REGISTRY extended to include the two JS hooks at the appropriate profile tier
  • gradata install-hooks (or existing install command) copies both files and registers them in settings.json
  • Unit tests for the copy step (tmp_path fixture, assert files land in correct subdirs with correct content)
  • Integration test: run installer against a fresh .claude/, invoke the watchdog by simulating a bridge file, confirm the hook emits the expected directive
  • Docs: README section on "Enable context-pressure handoff" with the env var (GRADATA_HANDOFF_THRESHOLD) and a note that the hooks require Claude Code (not provider-agnostic yet)

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions