Skip to content

Implement common hook input adapter: normalize IDE hooks to canonical format#59

Open
sharkich wants to merge 9 commits intov3from
feature/hooks-normalization-tdd
Open

Implement common hook input adapter: normalize IDE hooks to canonical format#59
sharkich wants to merge 9 commits intov3from
feature/hooks-normalization-tdd

Conversation

@sharkich
Copy link
Copy Markdown
Contributor

This pull request introduces a new IDE input normalization adapter for Rosetta hooks, along with comprehensive test coverage. The main focus is on supporting and normalizing stdin input from various IDEs (starting with Claude Code and Codex) into a canonical format for downstream processing. It also sets up an extensible testing framework and documents these changes in the implementation guide.

IDE Input Normalization and Testing:

  • Added adapter.js module to instructions/r2/core/hooks/ for normalizing IDE stdin to the Claude Code canonical format, with detection for Claude Code and Codex, and stubs for Cursor, Windsurf, and Copilot. Exports detectIDE, normalize, formatOutput, and readStdin functions.
  • Implemented a full TDD test suite in adapter.test.js using node:test, covering all code paths for Claude Code and Codex, and including placeholders for future IDEs. Tests use local JSON fixtures and in-memory streams for input simulation.

Workspace Initialization and Documentation:

  • Initialized Rosetta workspace in upgrade mode, generating proxy shells for skills, agents, and workflow commands, and created gain.json for SDLC configuration. Added workspace documentation files: TECHSTACK.md, CODEMAP.md, DEPENDENCIES.md, and ASSUMPTIONS.md.

Hooks and Automation:

  • Added loose-files.js PostToolUse hook (referenced in documentation), which nudges the AI when loose .py or .js files lack module markers, with injected fs for testability and full test coverage (implementation not shown in this diff).
  • Updated documentation in IMPLEMENTATION.md to reflect new hooks, workspace setup, and test exclusion for plugin sync.

sharkich and others added 3 commits April 8, 2026 12:16
- adapter.js: detect Codex before Claude Code (model+turn_id heuristic)
- adapter.test.js: activate Codex tests (remove todo), rename fixture ref
  to codex-post-tool-use-bash.json (Codex v0.39 hooks Bash only)
- claude-code-post-tool-use-write.json: replace hand-crafted fixture with
  real E2E capture (richer tool_response: type, structuredPatch, originalFile)
- codex-post-tool-use-bash.json: schema-derived stub; replace with real
  capture after `codex login` + re-run capture-hook-fixtures.sh --ide codex
- capture-hook-fixtures.sh: new E2E script — spins up temp project, installs
  dump-stdin.js hooks for Claude Code + Codex, captures/sanitizes/saves fixtures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- codex-post-tool-use-bash.json: replace schema-derived stub with real
  stdin capture from Codex v0.118.0. Key findings:
  - tool_response is a plain string (not object like Claude Code)
  - permission_mode is "bypassPermissions" (not "default")
  - transcript_path uses rollout-* naming convention
  - model + turn_id confirmed present and sanitized
- capture-hook-fixtures.sh: switch Codex capture to npx @openai/codex
  (>=0.118.0 required; brew codex 0.39.0 lacks codex_hooks feature flag)

Tests: adapter.test.js 23 pass / 0 fail / 12 todo (Codex GREEN)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sharkich sharkich self-assigned this Apr 14, 2026
Copilot AI review requested due to automatic review settings April 14, 2026 06:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a small hooks “runtime” (stdin normalization + a PostToolUse “loose files” nudge) under instructions/r2/core/hooks/, along with fixtures, an E2E fixture capture helper, and plugin-sync filtering so test artifacts aren’t shipped into generated plugins.

Changes:

  • Introduces an IDE stdin normalization adapter (adapter.js) plus node:test coverage and captured/stub fixtures.
  • Adds a loose-files.js hook that warns when .py/.js files are created/edited outside a detected module boundary.
  • Updates plugin sync (scripts/pre_commit.py) to exclude *.test.js and test-fixtures/ from generated plugin outputs; updates implementation docs and adds planning/handoff docs.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
scripts/pre_commit.py Excludes *.test.js and test-fixtures/ from plugin sync output.
plugins/core-cursor/hooks/loose-files.js Generated hook copy for Cursor plugin.
plugins/core-cursor/hooks/adapter.js Generated adapter copy for Cursor plugin.
plugins/core-claude/hooks/loose-files.js Generated hook copy for Claude plugin.
plugins/core-claude/hooks/adapter.js Generated adapter copy for Claude plugin.
plans/plan-manager-skills/hooks-normalization-PLAN.md Plan for adapter + loose-files hook work.
plans/plan-manager-skills/hooks-normalization-HANDOFF.md Handoff notes, fixtures inventory, and next steps.
instructions/r2/core/hooks/test-fixtures/windsurf-post-tool-use-write.json Stub Windsurf fixture placeholder.
instructions/r2/core/hooks/test-fixtures/unknown-ide-input.json Fixture for unknown-shape error path.
instructions/r2/core/hooks/test-fixtures/dump-stdin.js Helper to dump raw hook stdin to a JSONL file.
instructions/r2/core/hooks/test-fixtures/cursor-post-tool-use-write.json Stub Cursor fixture placeholder.
instructions/r2/core/hooks/test-fixtures/copilot-post-tool-use-write.json Stub Copilot fixture placeholder.
instructions/r2/core/hooks/test-fixtures/codex-post-tool-use-write.json Stub Codex Write fixture placeholder.
instructions/r2/core/hooks/test-fixtures/codex-post-tool-use-bash.json Captured Codex Bash hook fixture.
instructions/r2/core/hooks/test-fixtures/claude-code-pre-tool-use-bash.json Captured Claude Code PreToolUse Bash fixture.
instructions/r2/core/hooks/test-fixtures/claude-code-post-tool-use-write.json Captured Claude Code PostToolUse Write fixture.
instructions/r2/core/hooks/test-fixtures/claude-code-post-tool-use-write-subagent.json Captured Claude Code subagent fixture.
instructions/r2/core/hooks/test-fixtures/claude-code-post-tool-use-edit.json Captured Claude Code PostToolUse Edit fixture.
instructions/r2/core/hooks/loose-files.test.js Unit tests for loose-files hook behavior.
instructions/r2/core/hooks/loose-files.js Loose-file detection hook implementation.
instructions/r2/core/hooks/capture-hook-fixtures.sh E2E script to capture/sanitize fixtures for Claude Code and Codex.
instructions/r2/core/hooks/adapter.test.js Unit tests for adapter detection/normalization/output formatting.
instructions/r2/core/hooks/adapter.js Adapter implementation (Claude Code + Codex detection).
agents/IMPLEMENTATION.md Documents workspace + hooks additions and sync exclusions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plugins/core-cursor/hooks/adapter.js Outdated
Comment on lines +6 to +33
// Claude Code sends all three of these fields on every hook event
const CC_SIGNATURE = ['hook_event_name', 'tool_input', 'session_id'];

/**
* Detect which IDE sent the input based on field shape heuristics.
* @param {object} rawInput
* @returns {'claude-code'}
* @throws {Error} for null/non-object input or unrecognized IDE shape
*/
function detectIDE(rawInput) {
if (rawInput === null || rawInput === undefined) {
throw new Error('Invalid input: null or undefined');
}
if (typeof rawInput !== 'object' || Array.isArray(rawInput)) {
throw new Error('Invalid input: expected a plain object');
}
if (CC_SIGNATURE.every((field) => field in rawInput)) {
return 'claude-code';
}
throw new Error(`Unsupported IDE: ${JSON.stringify(Object.keys(rawInput))}`);
}

/**
* Normalize any IDE input to Claude Code canonical format.
* Claude Code input is already canonical — identity pass-through.
* @param {object} rawInput
* @returns {object} canonical input
* @throws {Error} for unsupported IDE shapes
Comment thread scripts/pre_commit.py
if source_file.name.endswith(".test.js"):
continue
if "test-fixtures" in relative_path.parts:
continue
Comment on lines +298 to +316
run_tests() {
log_info "Running adapter tests..."
if node --test "$SCRIPT_DIR/adapter.test.js" 2>&1 \
| grep -E "^# (pass|fail|todo)" | head -5; then
log_ok "adapter.test.js passed"
else
log_error "adapter.test.js failed — check output above"
return 1
fi

log_info "Running loose-files tests..."
if node --test "$SCRIPT_DIR/loose-files.test.js" 2>&1 \
| grep -E "^# (pass|fail|todo)" | head -5; then
log_ok "loose-files.test.js passed"
else
log_error "loose-files.test.js failed"
return 1
fi
}
Comment thread agents/IMPLEMENTATION.md

### Hooks — IDE Input Normalization

- Added `instructions/r2/core/hooks/adapter.js`: normalizes IDE stdin to Claude Code canonical format. Exports `detectIDE`, `normalize`, `formatOutput`, `readStdin`. Stub placeholders for Cursor/Codex/Windsurf/Copilot (activated when real fixtures are captured).
Comment on lines +61 to +62
if (fs.existsSync(path.join(dir, '.git'))) return true;
if (fs.existsSync(path.join(dir, marker))) return false;
Comment on lines +61 to +62
if (fs.existsSync(path.join(dir, '.git'))) return true;
if (fs.existsSync(path.join(dir, marker))) return false;
Comment on lines +61 to +62
if (fs.existsSync(path.join(dir, '.git'))) return true;
if (fs.existsSync(path.join(dir, marker))) return false;
Comment thread plugins/core-claude/hooks/adapter.js Outdated
Comment on lines +6 to +33
// Claude Code sends all three of these fields on every hook event
const CC_SIGNATURE = ['hook_event_name', 'tool_input', 'session_id'];

/**
* Detect which IDE sent the input based on field shape heuristics.
* @param {object} rawInput
* @returns {'claude-code'}
* @throws {Error} for null/non-object input or unrecognized IDE shape
*/
function detectIDE(rawInput) {
if (rawInput === null || rawInput === undefined) {
throw new Error('Invalid input: null or undefined');
}
if (typeof rawInput !== 'object' || Array.isArray(rawInput)) {
throw new Error('Invalid input: expected a plain object');
}
if (CC_SIGNATURE.every((field) => field in rawInput)) {
return 'claude-code';
}
throw new Error(`Unsupported IDE: ${JSON.stringify(Object.keys(rawInput))}`);
}

/**
* Normalize any IDE input to Claude Code canonical format.
* Claude Code input is already canonical — identity pass-through.
* @param {object} rawInput
* @returns {object} canonical input
* @throws {Error} for unsupported IDE shapes
Copy link
Copy Markdown
Contributor

@isolomatov-gd isolomatov-gd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All tests are to be outside of instructions.
Moreover, you can have all source files outside of instructions too, and only compiled/minified in the instructions (the built final version)

Comment thread instructions/r2/core/hooks/adapter.js Outdated
//
// Exports (for testability): detectIDE, normalize, formatOutput, readStdin

const ADAPTERS = [
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All-in-one file please

@sharkich sharkich changed the base branch from main to v3 April 20, 2026 16:06
@sharkich sharkich changed the title Implement hooks normalization and E2E fixture capture for hooks Implement common hook input adapter: normalize IDE hooks to canonical format Apr 20, 2026
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.

3 participants