Agent Workflows
The plan→execute→review→test loop, skills, and hooks.
Repo & folder organization
How a repo is organized directly affects how well agents navigate and modify it. A well-structured repo is a better feedback signal than documentation.
Layer folders vs feature folders
Agents navigate feature folders far more reliably — everything a payments change touches lives in one place.
Layer folders (agent-hostile)
Feature folders (agent-friendly)
Agent-ready repo root
The coding agent loop
The core agentic loop: Plan → Execute → Review → Test → Automate, with feedback flowing backward at every stage.
┌──────────────────────────────────────────┐
│ │
▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ PLAN │──▶│ EXECUTE │──▶│ REVIEW │──▶│ TEST │──▶│ AUTOMATE │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
▲ ▲ │ │
│ │ │ │
│ └──────────────┘ │
│ (fix) │
│ │
└────────────────────────────────────────────┘
(rethink scope)The forward arrows are the happy path. The backward arrows are where most of the value comes from: Review kicks bad changes back to Execute, and Test failures can bounce all the way back to Plan when they reveal the scope was wrong.
- Plan — define the task clearly, use
/grill-meto surface assumptions, agree on "done" before starting - Execute — one coherent change at a time; break large tasks down
- Review — linters and types as first pass; human review for what machines can't catch
- Test — write tests alongside, not after; TDD is cheap with agents
- Automate — hook into CI so the full loop runs on every PR
Skills
Skills are reusable agent capabilities — packaged prompts, tools, or workflows invoked by name.
/grill-me— adversarial assumption flushing/lint-rule— generate a custom ESLint rule from a description/review— structured code review with specific focus areas/test— generate behavior-based tests for a given function or module/explain— plain-language explanation of a code path/refactor— guided refactoring with safety checks
Focused skill reviews
Every codebase has attributes worth enforcing that no off-the-shelf linter knows about — your React patterns, your API conventions, your query discipline. Run narrow, focused skill-based reviews in parallel in CI, one per concern. Each skill knows one thing about your codebase and checks it thoroughly.
Good candidates — anything too semantic for a linter, that you'd normally leave the same code review comment about repeatedly:
- React best practices — re-renders, missing keys, stale effect dependencies, improper memoization
- API data flow — type alignment, auth on protected routes, missing rate limits, inconsistent error shapes
- DB queries — N+1 patterns, missing indexes, unsafe raw SQL, migration rollback safety
- Security patterns — ownership checks, input sanitization, session/token handling
- Observability gaps — new endpoints without logging/metrics, external calls without timeout/retry
Use openai/codex-action or anthropics/claude-code-action to run a focused review agent per PR. Each action takes a prompt file that defines what the reviewer cares about.
Review this pull request strictly for React best practices.
Load the rules in `.agents/skills/react-best-practices/README.md` and
apply them to every changed `.tsx` file.
For each finding, output:
- File and line
- Severity (critical | warn | note)
- One-sentence explanation of what's wrong
- Suggested fixHooks (Claude Code)
Hooks run shell commands at specific points in the agent lifecycle — the primary extension point for injecting feedback signals, enforcing guardrails, and automating quality steps.
Configured in .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [{ "type": "command", "command": "npm run lint:fix $CLAUDE_TOOL_INPUT_PATH" }]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/check-dangerous-commands.sh" }]
}
]
}
}Hook types:
- PreToolUse — runs before a tool executes; exit
2blocks the action and feeds stderr back to the model - PostToolUse — runs after a tool completes; ideal for formatting, linting, logging
- Notification — triggered when Claude sends a notification
- Stop — triggered when Claude finishes a task
Practical uses — each is a feedback signal the agent receives immediately:
- Auto-format on write — Prettier on every edit
- Lint on edit — ESLint --fix after every file change
- Type check on change —
tsc --noEmitafter TS edits - Test on change — run the relevant test file; keep the red-green loop tight
- Dangerous command check — scan Bash commands for risky patterns before they run
Example: enforce a PR-based workflow
A PreToolUse hook that blocks direct commits and pushes to main, forcing agents to work in feature branches with PRs:
#!/usr/bin/env bash
# Exit 2 tells Claude Code to block the tool call and feed stderr back to the model.
set -euo pipefail
COMMAND=$(jq -r '.tool_input.command // ""')
[[ "$COMMAND" == *"git"* ]] || exit 0
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
PROTECTED='^(main|master)$'
if [[ "$COMMAND" == *"git commit"* ]] && [[ "$CURRENT_BRANCH" =~ $PROTECTED ]]; then
cat >&2 <<EOF
Refusing to commit directly to '$CURRENT_BRANCH'.
Create a feature branch and open a PR instead:
git checkout -b feat/<short-name>
git commit ...
git push -u origin HEAD
gh pr create --fill
EOF
exit 2
fi
if [[ "$COMMAND" == *"git push"* ]] && \
[[ "$COMMAND" =~ (^|[[:space:]:])(main|master)([[:space:]]|$) ]]; then
echo "Refusing to push directly to main/master. Open a PR instead: gh pr create --fill" >&2
exit 2
fi
if [[ "$COMMAND" == *"git push"* ]] && [[ "$CURRENT_BRANCH" =~ $PROTECTED ]]; then
echo "Refusing to push from '$CURRENT_BRANCH'. Switch to a feature branch and open a PR." >&2
exit 2
fi
exit 0{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash .claude/hooks/block-main-writes.sh" }
]
}
]
}
}The refusal message becomes the agent's next-step instruction — because stderr is fed back on exit 2, the agent reads "create a feature branch and open a PR" and actually does it.
settings.json(checked in) — shared hooks for the whole teamsettings.local.json(gitignored) — personal hooks, local logging, preferences
Hooks are where policy lives. Prompts describe what to do; hooks enforce what's allowed — and they run whether you're in the loop or not.