From arc
Installs Claude Code hooks for Biome auto-formatting, lint-on-stop, git safeguards, context monitoring; plus Husky git hooks for pre-commit linting and pre-push typechecks. Use for project setup with Biome.
npx claudepluginhub howells/arc --plugin arcThis skill uses the workspace's default tool permissions.
<tool_restrictions>
Provides Ktor server patterns for routing DSL, plugins (auth, CORS, serialization), Koin DI, WebSockets, services, and testApplication testing.
Conducts multi-source web research with firecrawl and exa MCPs: searches, scrapes pages, synthesizes cited reports. For deep dives, competitive analysis, tech evaluations, or due diligence.
Provides demand forecasting, safety stock optimization, replenishment planning, and promotional lift estimation for multi-location retailers managing 300-800 SKUs.
<tool_restrictions>
AskUserQuestion — Preserve the one-question-at-a-time interaction pattern for user choices. In Claude Code, use the tool. In Codex, ask one concise plain-text question at a time unless a structured question tool is actually available in the current mode. Do not narrate missing tools or fallbacks to the user.EnterPlanMode — BANNED. Do NOT call this tool. This skill has its own structured process. Execute the steps below directly.ExitPlanMode — BANNED. You are never in plan mode.
</tool_restrictions>--remove: Remove all Arc-installed hooks (Claude Code + git hooks). Read .claude/settings.json, remove Arc hooks, write back. Remove Arc-created git hooks from .husky/ or .vite-hooks/. Report what was removed. Done — skip all other steps.
--git-only: Skip Claude Code hooks (Steps 1-7). Jump directly to Step 8 (git hooks).
--claude-only: Install only Claude Code hooks (Steps 1-7). Skip Step 8 (git hooks).
No flag (default): Install both Claude Code hooks AND git hooks.
grep -q '"@biomejs/biome"' package.json 2>/dev/null
If Biome is in package.json: Continue to Step 2.
If Biome is NOT found:
AskUserQuestion:
question: "Biome not found in package.json. Arc hooks require Biome for auto-formatting and linting. How would you like to proceed?"
header: "Biome Required"
options:
- label: "Install Biome and continue"
description: "Add @biomejs/biome as a dev dependency and set up all hooks"
- label: "Skip formatting hooks"
description: "Install context monitor and git guard only, skip Biome-dependent hooks"
- label: "Cancel"
description: "Exit without installing any hooks"
If user picks "Install Biome and continue":
# Detect package manager from lockfile
if [ -f pnpm-lock.yaml ]; then
pnpm add -D @biomejs/biome
elif [ -f yarn.lock ]; then
yarn add -D @biomejs/biome
else
npm install -D @biomejs/biome
fi
Then check for biome.json / biome.jsonc. If missing:
npx @biomejs/biome init
If user picks "Skip formatting hooks": Set SKIP_BIOME=true, continue to Step 3.
If user picks "Cancel": Stop. Do not install any hooks.
./node_modules/.bin/biome --version
If this fails, the binary isn't available. Tell the user and offer to reinstall.
Also check for biome config:
ls biome.json biome.jsonc 2>/dev/null
If no config exists, note this — biome will use defaults, which is fine.
Build the hooks object to merge into .claude/settings.json.
Biome format hook (PostToolUse, Edit|Write):
{
"matcher": "Edit|Write|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path // .tool_input.filePath // empty' | { read file_path; case \"$file_path\" in *.js|*.ts|*.jsx|*.tsx|*.json|*.jsonc|*.css|*.graphql) ./node_modules/.bin/biome format --write \"$file_path\" 2>/dev/null || true ;; esac; }"
}
]
}
Biome lint hook (Stop):
{
"hooks": [
{
"type": "command",
"command": "git diff --name-only --diff-filter=d HEAD 2>/dev/null | grep -E '\\.(js|ts|jsx|tsx|json|jsonc|css|graphql)$' | xargs -r ./node_modules/.bin/biome check --fix --unsafe 2>/dev/null || true"
}
]
}
TypeScript check hook (Stop):
Only include this if the project has TypeScript (tsconfig.json exists).
{
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit 2>&1 | tail -20 || true"
}
]
}
Git guard hook (PreToolUse, Bash):
This hook blocks destructive git operations before they execute. Always install — not dependent on Biome.
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' | grep -qE 'git\\s+(reset\\s+--hard|push\\s+(-f|--force)|clean\\s+-f|checkout\\s+\\.)' && echo '{\"decision\":\"block\",\"reason\":\"Destructive git operation blocked by Arc hooks. Ask the user first.\"}' || true"
}
]
}
Context monitor hook (PostToolUse, all tools):
Determine the absolute path to Arc's hooks/ directory by resolving the Arc install root from this skill's location. Use that resolved path as ${ARC_HOOKS_PATH}.
Read hooks/arc-context-monitor.js from the resolved Arc install root to confirm it exists.
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "node ${ARC_HOOKS_PATH}/arc-context-monitor.js"
}
]
}
Where ${ARC_HOOKS_PATH} is the resolved absolute path to the Arc plugin's hooks/ directory.
Statusline hook:
{
"type": "command",
"command": "node ${ARC_HOOKS_PATH}/arc-statusline.js"
}
If SKIP_BIOME is true: Only include the git guard, context monitor, and statusline hooks. The tsc hook is independent of Biome — include it if tsconfig.json exists.
cat .claude/settings.json 2>/dev/null
If file doesn't exist:
mkdir -p .claude
Start with an empty object {}.
If file exists: Parse the JSON. Preserve ALL existing fields (permissions, enabledMcpjsonServers, enableAllProjectMcpServers, enabledPlugins, etc.).
This is the critical step. NEVER clobber existing hooks — merge with them.
Read the existing hooks object (may be undefined).
For PreToolUse:
hooks.PreToolUse array (or empty array)git.*reset.*--hard)For PostToolUse:
hooks.PostToolUse array (or empty array)biome format)arc-context-monitor)For Stop:
hooks.Stop array (or empty array)biome check)tsc --noEmit)tsconfig.json in the project, skip the tsc hookFor Statusline:
hooks.Statusline array (or empty array)arc-statusline)Write the merged settings back: Use the Write tool to write the complete JSON (pretty-printed with 2-space indent).
CRITICAL: Preserve all non-hook fields exactly as they were. Do not add, remove, or modify anything outside the hooks key.
Read back .claude/settings.json and confirm the hooks are present.
Count installed hooks:
Arc hooks installed in .claude/settings.json
PreToolUse (Bash) → blocks destructive git ops (force push, reset --hard)
PostToolUse (Edit|Write) → biome format --write on edited file
PostToolUse (all tools) → context monitor (warns at 35%/25% remaining)
Stop → biome check --fix --unsafe on all changed files
Stop → tsc --noEmit (if tsconfig.json exists)
Statusline → context bar showing usage
Hooks run automatically — zero token cost, zero agent effort.
To remove: /arc:hooks --remove
If SKIP_BIOME was true:
Arc hooks installed in .claude/settings.json
PreToolUse (Bash) → blocks destructive git ops (force push, reset --hard)
PostToolUse (all tools) → context monitor (warns at 35%/25% remaining)
Statusline → context bar showing usage
Biome hooks skipped (not installed). Run /arc:hooks again after adding Biome.
To remove: /arc:hooks --remove
This step installs pre-commit and pre-push git hooks to enforce typecheck + lint at the git level. This catches errors that Claude Code hooks can't — like commits made outside Claude, or when the AI session ends before running Stop hooks.
Check which hook system the project uses:
.vite-hooks/ directory exists, OR package.json has "prepare": "vp config" → use Vite+ hooks.husky/ directory exists, OR package.json has "prepare": "husky" → use Husky hooksFor Husky projects (or new installs):
Check if husky is installed:
grep -q '"husky"' package.json 2>/dev/null
If not installed:
# Detect package manager
if [ -f pnpm-lock.yaml ]; then
pnpm add -D husky lint-staged
elif [ -f bun.lockb ] || [ -f bun.lock ]; then
bun add -D husky lint-staged
elif [ -f yarn.lock ]; then
yarn add -D husky lint-staged
else
npm install -D husky lint-staged
fi
Ensure prepare script exists in package.json:
"scripts": { "prepare": "husky" }
Ensure .husky/ directory exists:
mkdir -p .husky
For Vite+ projects: Hooks go in .vite-hooks/. Ensure directory exists:
mkdir -p .vite-hooks
Check if lint-staged config exists in package.json. If not, add it:
"lint-staged": {
"*.{js,ts,jsx,tsx,json,jsonc,css}": "biome format --write --no-errors-on-unmatched"
}
For Vite+ projects: lint-staged is handled by vp staged configured through vite.config.ts. Do not add lint-staged config to package.json. If vp staged is not configured, check vite.config.ts for staged config. If missing, note this to the user.
Determine the hooks directory ($HOOKS_DIR):
.husky/.vite-hooks/pre-commit ($HOOKS_DIR/pre-commit):
For Husky projects:
pnpm lint-staged
pnpm typecheck && pnpm lint
For Vite+ projects:
vp staged
For bun-based projects (detected by bun.lockb or bun.lock):
bun run typecheck && bunx lint-staged
pre-push ($HOOKS_DIR/pre-push):
For Husky projects:
pnpm typecheck || exit 1
pnpm lint || exit 1
For Vite+ projects (adapt to the project's existing scripts):
pnpm typecheck || exit 1
pnpm lint || exit 1
Make both files executable:
chmod +x $HOOKS_DIR/pre-commit $HOOKS_DIR/pre-push
Important: If hook files already exist, read them first. Only overwrite if they are missing typecheck or lint steps. Never remove project-specific steps that are already present (like env validation, SDK guards, test runs, etc.).
If turbo.json exists, ensure typecheck and lint tasks have cache: false:
# Read turbo.json and check cache settings
For each task (typecheck, lint, check-types):
cache is not false, set cache: falseThis is non-negotiable. Turborepo caching on typecheck/lint is the #1 cause of type errors slipping through git hooks. Turbo replays a cached success exit code without actually running tsc or biome, making hooks pass when they should fail.
Do NOT disable cache on build or test tasks — those have legitimate outputs and benefit from caching.
Git hooks installed in $HOOKS_DIR/
pre-commit → lint-staged (biome format on staged files) + typecheck + lint
pre-push → full typecheck + lint (safety net before push)
turbo.json → cache: false for typecheck/lint (if applicable)
Errors will now be caught before they leave your machine.
## Claude Code hooks
- The biome format hook uses `jq` to extract the file path from the tool input. This is standard on macOS (via Homebrew) and most Linux distros. If jq is missing, the hook silently fails (|| true).
- The Stop hook uses `xargs -r` which is a no-op if there are no files. On macOS, `xargs` without `-r` still works (just runs biome with no args, which exits cleanly).
- The context monitor hooks reference files inside the Arc plugin. If the user uninstalls Arc, these hooks will silently fail (node script not found → exit 1, but hooks don't block).
- `--unsafe` in the Stop hook enables Biome's unsafe fixes (like removing unused imports). This is intentional — at conversation end, we want maximum cleanup.
- The PostToolUse format hook handles both `file_path` (Edit tool) and `filePath` (NotebookEdit) via jq fallback.
- The tsc hook uses `tail -20` to avoid flooding the stop output — just enough to see if there are errors and what they are.
- The git guard uses PreToolUse with `decision: block` to stop destructive commands before execution. This is especially important for users running with `--dangerously-skip-permissions` or liberal auto-approve settings.
- The git guard blocks: `git reset --hard`, `git push --force` / `git push -f`, `git clean -f`, `git checkout .`. These are the operations that destroy uncommitted work with no undo.
.husky/ directory — hook files are directly in .husky/pre-commit, .husky/pre-push, etc. No _ subdirectory or husky.sh sourcing needed..vite-hooks/ with a _/ subdirectory containing the hook runner. git config core.hooksPath is set to .vite-hooks/_ by vp config. User hooks go directly in .vite-hooks/pre-commit, .vite-hooks/pre-push, etc.pnpm lint-staged first (fast — only formats staged files), then pnpm typecheck && pnpm lint (catches errors before commit).|| exit 1 instead of && so each step reports its own failure.turbo typecheck in a git hook may return a cached success from a previous run, silently passing despite new type errors. This is the #1 cause of type errors getting through hooks.cache: false should ONLY be set on typecheck, lint, and check-types tasks. build and test tasks should keep their cache — those have real outputs and benefit from caching.