Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/AGENT_HOOKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Agent hook provider notes

This repo keeps nf-core validation policy provider-neutral in `scripts/agent_hooks/`.
Provider-specific hook formats belong in adapter modules under
`scripts/agent_hooks/providers/`.

## Internal contract

The internal interface is a phase report, not a provider wire format:

```python
report = run_phase("stop", files)
```

Provider adapters translate that report into each agent runtime's required stdout,
stderr, and exit-code behavior.

## Provider shape references

- [Codex](agent-hooks/codex.md)
- [Claude Code](agent-hooks/claude.md)
- [Cursor](agent-hooks/cursor.md)
- [Generic exit-code providers](agent-hooks/generic.md)
57 changes: 57 additions & 0 deletions docs/agent-hooks/claude.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Claude Code hook provider shape

Claude Code uses JSON stdin for hook events and supports structured JSON stdout
for decisions on blocking-capable events.

## Stop input shape

Claude Code `Stop` input is JSON on stdin. Relevant fields include:

```json
{
"hook_event_name": "Stop",
"session_id": "...",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/path/to/project",
"stop_hook_active": false,
"last_assistant_message": "...",
"background_tasks": [],
"session_crons": []
}
```

Important fields for this repo:

- `cwd`
- `hook_event_name`
- `stop_hook_active`
- `last_assistant_message`

## Stop output shape

For `Stop`, block with clean JSON on stdout:

```json
{
"decision": "block",
"reason": "Continue fixing before stopping."
}
```

Allow stopping with:

```json
{
"decision": "approve",
"reason": "nf-core stop hook: checks passed"
}
```

## Rules and gotchas

- Use stdout for the structured response.
- Keep stdout clean JSON; send harness logs to stderr.
- The adapter should return exit code `0` when expressing a structured block.
- If `stop_hook_active` is true, avoid recursive blocking.
- `SessionEnd` is not a substitute for `Stop` because it does not provide the
same blocking control.
102 changes: 102 additions & 0 deletions docs/agent-hooks/codex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Codex hook provider shape

Source of truth: OpenAI Codex sources in `openai/codex`:

- `docs/config.md`
- `codex-rs/hooks/src/lib.rs`
- `codex-rs/hooks/src/events/stop.rs`
- `codex-rs/hooks/src/engine/output_parser.rs`
- `codex-rs/hooks/schema/generated/stop.command.input.schema.json`
- `codex-rs/hooks/schema/generated/stop.command.output.schema.json`
- `codex-rs/config/src/hook_config.rs`

## Events

Codex hook events include:

- `PreToolUse`
- `PermissionRequest`
- `PostToolUse`
- `PreCompact`
- `PostCompact`
- `SessionStart`
- `UserPromptSubmit`
- `SubagentStart`
- `SubagentStop`
- `Stop`

## nf-core hook manifest

The Codex plugin manifest points at `plugins/nf-core-tools/hooks/codex.json`.
That file uses Codex event names and nested command hook entries.

## Hook config shape

```json
{
"hooks": {
"Stop": [
{
"matcher": null,
"hooks": [
{
"type": "command",
"command": "...",
"timeout": 600,
"statusMessage": "..."
}
]
}
]
}
}
```

## Stop input shape

Codex `Stop` input is JSON on stdin:

```json
{
"hook_event_name": "Stop",
"cwd": "/path/to/project",
"session_id": "...",
"turn_id": "...",
"transcript_path": null,
"model": "...",
"permission_mode": "default",
"stop_hook_active": false,
"last_assistant_message": null
}
```

Important fields for this repo:

- `cwd`
- `hook_event_name`
- `stop_hook_active`
- `last_assistant_message`

## Stop output shape

Codex `Stop` output can block with clean JSON on stdout:

```json
{
"decision": "block",
"reason": "Continue fixing before stopping."
}
```

A successful no-block response can be empty stdout or structured JSON without a
block decision. This repo prefers structured JSON for provider adapters.

## Rules and gotchas

- `decision: "block"` requires a non-empty `reason`.
- stdout must be valid hook JSON when using JSON output; route harness logs to stderr.
- Exit code `2` plus stderr is also treated as a continuation/block prompt.
- Other nonzero exits are hook failures, not normal block decisions.
- If `stop_hook_active` is true, avoid blocking again to prevent recursive stop loops.
- Keep a distinct Codex adapter even if it initially resembles Claude. Future
`PreToolUse`, `PermissionRequest`, and `PostToolUse` semantics differ.
96 changes: 96 additions & 0 deletions docs/agent-hooks/cursor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Cursor hook provider shape

Cursor hooks are commands declared in a hook manifest such as `hooks/cursor.json` under event names. The
shape is provider-specific manifest wiring, not the internal nf-core validation
contract.

## Hook config shape

```json
{
"hooks": {
"afterFileEdit": [
{
"command": "./scripts/format-code.sh"
}
],
"beforeShellExecution": [
{
"command": "./scripts/validate-shell.sh",
"matcher": "rm|curl|wget"
}
],
"sessionEnd": [
{
"command": "./scripts/audit.sh"
}
]
}
}
```

## Events

Agent hooks:

- `sessionStart`
- `sessionEnd`
- `preToolUse`
- `postToolUse`
- `postToolUseFailure`
- `subagentStart`
- `subagentStop`
- `beforeShellExecution`
- `afterShellExecution`
- `beforeMCPExecution`
- `afterMCPExecution`
- `beforeReadFile`
- `afterFileEdit`
- `beforeSubmitPrompt`
- `preCompact`
- `stop`
- `afterAgentResponse`
- `afterAgentThought`

Tab hooks:

- `beforeTabFileRead`
- `afterTabFileEdit`

App lifecycle hooks:

- `workspaceOpen`

## Output shape

The Cursor hook shape documented here defines command execution and optional
matchers. It does not define a structured decision JSON contract like Claude or
Codex `Stop` hooks.

Use the Cursor adapter as an exit-code provider:

- success: human-readable report on stdout, exit `0`
- failure: human-readable report on stderr, nonzero exit

## nf-core hook manifest

The Cursor plugin manifest points at `plugins/nf-core-tools/hooks/cursor.json`.
That file uses Cursor event names and command entries.

## nf-core phase mapping

Map Cursor lifecycle events to shared internal phases rather than embedding
nf-core policy in the provider manifest:

```bash
scripts/agent_hooks/run_phase.py after-file-edit --provider cursor
scripts/agent_hooks/run_phase.py stop --provider cursor
```

Current mapping:

- `afterFileEdit` → `after-file-edit` for faster component/schema feedback
- `sessionEnd` → `stop` for final blocking validation
- `stop` → `stop` for agent runtimes that emit Cursor stop events

Add future edit or tool events as thin adapters over additional internal phases.
38 changes: 38 additions & 0 deletions docs/agent-hooks/generic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generic hook provider shape

Use this shape for providers without a structured JSON hook contract.

## Input shape

Generic provider adapters take changed files from argv:

```bash
scripts/agent_hooks/run_phase.py stop path/to/file.nf
```

If no files are passed, the harness falls back to changed git files.

## Output shape

Success:

```text
nf-core stop hook: check results
- PASS: nf-core component lint
- PASS: nf-core schema check
- PASS: nf-core pipelines lint
nf-core stop hook: checks passed
```

Failure:

```text
nf-core stop hook: check results
- FAIL: nf-core pipelines lint
nf-core stop hook: checks failed; continue fixing before stopping
```

## Exit-code behavior

- success: human-readable report on stdout, exit `0`
- failure: human-readable report on stderr, nonzero exit