Nudge is a collaborative memory layer for agent hooks. It remembers the coding conventions, patterns, and preferences that matter to you so Claude Code or Codex CLI can focus on solving your actual problem instead of tracking a mental checklist of stylistic details.
Think of Nudge as a helpful tap on the shoulder: "Hey, remember this codebase uses turbofish syntax" rather than a guard checking badges at the door.
See docs/PLAN.md for project goals and roadmap.
Human engineers are (rightfully!) particular about code style. But asking an agent to keep dozens of project-specific preferences in working memory competes with focusing on what you actually want to accomplish.
Nudge offloads those details. You encode your preferences once, and Nudge catches the slips, freeing both you and the agent to think at the level of "implement this feature" rather than "implement this feature, and remember the 47 things I care about."
Nudge uses agent hook systems to watch supported operations:
When something matches a rule you've defined:
- Interrupt (PreToolUse rules): Nudge catches the issue before it's written and explains what to fix
- Substitute (PreToolUse Bash rules): Nudge rewrites simple deterministic commands, lets the tool proceed, and tells the model what changed
- Continue (UserPromptSubmit rules): Nudge injects context into the conversation to guide the agent
- Passthrough: No rules matched, everything proceeds normally
These are the rules Nudge uses on its own codebase (yes, we dogfood):
| Preference | What Nudge reminds agents about |
|---|---|
| No inline imports | Move use statements to the top of the file |
| LHS type annotations | Prefer turbofish (::<T>) over let x: T = ... |
| Qualified paths | Import and use shorter names instead of long paths |
| Pretty assertions | Use pretty_assertions in tests for better diff output |
No .unwrap() |
Use .expect("...") with a descriptive message |
Other Attune codebases of course have other rules.
Nudge is a collaborative partner, but trusted partners can be blunt.
When an agent is deep in implementation, gentle suggestions get lost in the noise. A soft "you might want to consider..." will likely be ignored. A direct "Stop. Move this import to the top of the file." gets attention.
This isn't about being harsh, it's about being effective. Think of a rally copilot: they say "HARD LEFT NOW" not because they're angry, but because that's what cuts through when the driver is focused. The trust is what allows the directness.
Guidelines for rule messages:
- Be specific: "Move this import to the top of the file" not "Consider reorganizing imports"
- Be direct: "Stop. Fix this first." not "You might want to think about..."
- Explain why (briefly): "Use turbofish; LHS annotations clutter the variable name"
- Give the fix: Don't just say what's wrong; say what to do instead
- Use suggestions: Capture groups let you generate context-aware fixes (see
nudge claude docsornudge codex docs) - End with "then retry": Tell the agent to retry the operation after fixing
- Write for one match: Your message appears at each match location in a code snippet
Nudge displays violations like Rust compiler errors. Your message appears directly at the matched code:
error: rule violation
|
2 | use std::io;
| ^^^^^^^^ Move this import to the top of the file, then retry.
3 | use std::fs;
| ^^^^^^^^ Move this import to the top of the file, then retry.
|
The pattern: what's wrong -> how to fix -> retry.
For simple mechanical Bash command rewrites, use action: substitute with a regex replace: template instead of a blocking message:
version: 1
rules:
- name: use-yarn-add
action: substitute
on:
- hook: PreToolUse
tool: Bash
command:
- kind: Regex
pattern: "^npm install(?: (?P<args>.*))?$"
replace: "yarn add {{ $args }}"Substitutions work for Claude Code and Codex CLI. Nudge returns the provider's full updated tool input with only command changed, and adds hookSpecificOutput.additionalContext so the model sees what was rewritten.
For the full rule syntax and copy-pasteable examples, run nudge claude docs or nudge codex docs.
If an agent ignores a rule, the fix is usually to make the message more direct, not to give up on the rule.
Attune dogfoods Nudge on its own codebase and on other codebases we manage. When we notice an agent routing around a rule or missing the point, we tune the message until it lands. Treat ignored rules as feedback on clarity, not evidence that rules don't work.
The collaborative spirit lives in why Nudge exists (to help the agent focus on your real problem), not in tiptoeing around feedback.
macOS / Linux:
curl -sSfL https://raw.githubusercontent.com/attunehq/nudge/main/scripts/install.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/attunehq/nudge/main/scripts/install.ps1 | iexFrom source:
git clone https://github.com/attunehq/nudge
cd nudge
cargo install --path packages/nudgeNavigate to any project where you use Claude Code or Codex CLI and run the setup for the agent you use:
nudge claude setup
nudge codex setupClaude setup adds Nudge to .claude/settings.local.json. Codex setup adds Nudge to .codex/hooks.json. You can verify with /hooks in the relevant agent.
Note
Hook configuration is loaded when agent sessions start, so restart open Claude Code or Codex sessions after setup. Future changes to rules are internal to Nudge and therefore do not need an agent restart.
Nudge runs automatically as you use Claude Code or Codex CLI. No changes to your workflow required.
Write some rules for things that you want to be enforced, and then just use your agent normally. You should see Nudge interject when the rules are violated and help the agent stay on track.
Run your agent with debug logging to see hook execution. For Claude Code:
claude --debugYou'll see Nudge's hook being called and its response in the logs.
Use nudge check to validate your entire project against rules, useful for CI pipelines or local linting:
# Check entire project
nudge check
# Check specific paths or patterns
nudge check src/
nudge check "**/*.rs"
# Use in CI (fails build on violations)
nudge check || exit 1nudge check only evaluates file-based block rules for PreToolUse Write/Edit matchers. It ignores action: substitute rules because substitutions rewrite live Bash hook payloads and need a provider to receive updatedInput.
Example output when violations are found:
x Found 3 issues in 2 files
./src/main.rs:42 [no-unwrap]
Use `.expect("descriptive error message")` instead of `.unwrap()`, then retry.
./src/lib.rs:15 [no-inline-imports]
Move this `use` statement to the top of the file, then retry.
./src/lib.rs:23 [no-inline-imports]
Move this `use` statement to the top of the file, then retry.
Checked 25 files against 6 rules
When all checks pass:
Checked 25 files against 6 rules
- .nudge.yaml: 6 rules
You can test a specific rule with the test subcommand:
nudge test --rule no-inline-imports --tool Write --file test.rs \
--content $'fn main() {\n use std::io;\n}'Or pipe raw hook JSON to nudge directly:
echo '{
"hook_event_name": "PreToolUse",
"session_id": "test",
"transcript_path": "/tmp/test",
"permission_mode": "default",
"cwd": "/tmp",
"tool_name": "Write",
"tool_use_id": "123",
"tool_input": {
"file_path": "test.rs",
"content": "fn main() {\n use std::io;\n}"
}
}' | nudge claude hook
# Codex sends apply_patch for file writes and edits; Nudge normalizes those
# into Write/Edit/Delete before evaluating rules.
echo '{
"hook_event_name": "PreToolUse",
"cwd": "/tmp",
"tool_name": "apply_patch",
"tool_input": {
"command": "*** Begin Patch\n*** Add File: test.rs\n+fn main() {\n+ use std::io;\n+}\n*** End Patch\n"
}
}' | nudge codex hook
# Exit 0 with JSON output = Interrupt or Substitute (rule matched)
# Exit 0 with plain text = Continue (UserPromptSubmit context injected)
# Exit 0 with no output = Passthrough (nothing to note)See CLAUDE.md for development instructions, architecture overview, and how to add new rules.