跳到正文
DEV Community· Gabriel Anhaia·· 3 小時前AI 評分67

Makefile 是最佳智能體説明文件:每個程式碼庫在智能體操作前都需要的 10 個 Makefile 目標

A Makefile Is the Best Agent Doc: 10 Targets Every Repo Needs Before an Agent Touches It

AI 導讀

Gabriel Anhaia 提出一套供編碼智能體使用的 Makefile 範本,列出 10 個目標,涵蓋安裝、測試、檢查、開發及資料庫重設。他建議在 AGENTS.md 指示智能體先執行 `make help`、使用 Makefile 目標,並以 `make check` 通過作為任務完成條件;檢查依次執行 lint、typecheck、test 和格式檢查。

正文

Hand a coding agent a failing test in a repo it has never seen, and watch where the first turns go. It reads package.json. It reads a README that still mentions a script someone deleted. It opens the CI file to see what really runs. Then it guesses.

The guess is where it goes wrong. If it types npx vitest in a shell that looks interactive, Vitest starts in watch mode and waits for file changes instead of exiting. Whether that happens depends on the harness, which is exactly the kind of guess you don't want it making. Running the whole suite to check one file costs every test in tokens and minutes, and which database reset is safe is left for it to work out alone.

Anthropic's own Claude Code best practices put the cost plainly: "Claude stops when the work looks done. Without a check it can run, 'looks done' is the only signal available." The same page tells you to put "Bash commands Claude can't guess" in your instructions file.

A Makefile is the cheapest way to make those commands hard to guess wrong: one stable name per job and a help target that lists them all. Ten targets do it.

The Makefile

This one is for a Node and TypeScript service with Postgres through Prisma. Swap the recipes for your stack. The target names are the part worth keeping.

.DEFAULT_GOAL := help
.PHONY: help setup test test-one lint fmt typecheck \
       dev db-reset check

PORT ?= 3000

help: ## List every target
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
      | awk 'BEGIN {FS = ":.*?## "}; {printf "%-10s %s\n", $$1, $$2}'

setup: ## Install deps exactly as locked
    npm ci

test: ## Run the whole suite once, no watch mode
    npx vitest run

test-one: ## One file: make test-one FILE=src/a.test.ts
    @test -n "$(FILE)" || { echo "need FILE=path"; exit 2; }
    npx vitest run $(FILE)

lint: ## Lint without auto-fixing
    npx eslint .

fmt: ## Rewrite files to the house format
    npx prettier --write .

typecheck: ## Types only, writes no files
    npx tsc --noEmit

dev: ## Dev server: make dev PORT=4000
    PORT=$(PORT) npm run dev

db-reset: ## LOCAL only: drop, migrate, seed
    @case "$$DATABASE_URL" in \
      *@localhost:*|*@localhost/*) ;; \
      *@127.0.0.1:*|*@127.0.0.1/*) ;; \
      *) echo "db-reset: DATABASE_URL is not local"; exit 1;; \
    esac
    npx prisma migrate reset --force
    npx prisma db seed

check: lint typecheck test ## The done check
    npx prettier --check .

Recipe lines must start with a real tab (GNU make manual). If a copy-paste turns them into spaces, make rejects the file, so check that first when it complains. The help recipe is the self-documenting pattern marmelab published in 2016: any target with a ## comment on its line shows up in the list.

An agent at a control panel of identical labelled switches, one per job, instead of a tangle of loose wires
One switch per job. The agent never has to rewire anything to run the tests.

What each target is for

  1. make help, the default goal. .DEFAULT_GOAL := help makes plain make print the list, so an agent's first exploratory command returns the whole menu instead of building something.
  2. make setup installs dependencies from the lockfile with npm ci, not npm install, so a fresh worktree gets the versions you tested against. This is also the one command a new clone or a new worktree needs before anything else.
  3. make test runs the suite once with vitest run. Spelling out run matters: per the Vitest CLI docs, plain vitest is watch mode in a development terminal and only falls back to a single run in CI or when stdin is not a TTY.
  4. make test-one FILE=... runs one file. Claude Code's own best practices suggest "Prefer running single tests, and not the whole test suite, for performance." The test -n line fails fast with a usage hint instead of silently running everything when the agent forgets FILE=. Vitest treats the argument as a substring filter on the path, so a partial name works too.
  5. make lint lints without --fix. You want the agent to see the findings and fix them in a turn you can review, not have a tool rewrite twenty files behind its back.
  6. make fmt is the opposite on purpose: prettier --write rewrites files in place. Formatting is never worth a debate, so give the agent one command that ends it.
  7. make typecheck runs tsc --noEmit. Types only, no build output to clean up, and a non-zero exit when something is wrong.
  8. make dev PORT=4000 starts the dev server. PORT ?= 3000 assigns only if PORT is not already defined (conditional assignment), and make turns every environment variable it sees into a make variable (Environment). So PORT=4000 make dev and make dev PORT=4000 both work, and two agents in two worktrees can run side by side on different ports, as long as your server reads PORT. This target never exits, so tell agents in AGENTS.md whether they may start it at all.
  9. make db-reset is the only target here that destroys data. The case guard is a rough check: it refuses to run unless DATABASE_URL has @localhost or @127.0.0.1 followed by a port or a slash, and it assumes the URL is exported in the environment. It is a speed bump that fails closed, so it also rejects local URLs written another way, such as a socket path; adjust the pattern for your setup. Prisma's reset docs also describe a guard of their own: when an agent such as Claude Code or Cursor attempts the reset, Prisma blocks it and the agent has to ask for your explicit consent. I haven't checked whether that detection fires when the command runs inside make, so treat the URL check as yours. Note that since Prisma 7, migrate reset no longer seeds on its own, which is why db seed is a separate line.
  10. make check is the gate. Lint, types, tests, then a prettier --check that exits 1 if anything is unformatted.

Two lines in AGENTS.md

The Makefile only helps if every agent knows to use it. AGENTS.md is the shared place for that: the site lists Codex, Cursor, Gemini CLI, Aider, goose, opencode, GitHub Copilot's coding agent and others as supporting it, and it suggests putting build and test commands there. Two lines are enough:

Run `make help` for every task. Use make targets, not raw commands.
A task is finished only when `make check` passes.

Claude Code reads AGENTS.md directly from v2.1.277, but by default only when there is no CLAUDE.md or CLAUDE.local.md in your working directory or above it (memory docs). If you already have a CLAUDE.md, add one line to it so both files say the same thing:

@AGENTS.md

That @path import expands the file into Claude's context, and the same docs say keeping the import never makes Claude read AGENTS.md twice.

Many different agents walking through one door marked only with a single gear symbol
One file, every agent. The targets are the contract; AGENTS.md is the signpost.

make check decides when the task is done

make check lists lint typecheck test as prerequisites, so they run first and the formatting check runs last. When any recipe exits non-zero, make "gives up on the current rule, and perhaps on all rules" and exits non-zero itself (GNU make, Errors). That single exit code is the pass or fail signal the Claude Code best practices ask for: "Give Claude something that produces a pass or fail, and the loop closes on its own."

Two choices to make on purpose. First, make -k check keeps working through the other prerequisites after one fails (-k), so lint, types and tests all report in one run, which suits an agent fixing several things in one turn. Second, keep check free of anything that writes: no --fix, no --write. A done check that edits files can pass on code the agent never saw.

You can make the gate harder than an instruction. The same docs describe a Stop hook that "runs your check as a script and blocks the turn from ending until it passes." With the Makefile in place, that script is one line: make check.

Running it per task in Hermes IDE

I build Hermes IDE, so weigh this section with that in mind. Hermes runs Claude Code, Codex and other CLI agents each in its own terminal, and can start every task on its own branch and worktree. A file called .hermes/worktree.toml tells it how to prepare each new worktree, per the worktree recipe docs. With the Makefile above, the recipe is three lines:

# .hermes/worktree.toml
setup = ["make setup"]
copy = [".env"]
done_when = ["make check"]

setup runs in order inside the new worktree, and the first failing command stops the rest. copy brings over files git ignores, like .env, and refuses the whole recipe if a pattern matches a tracked file. done_when sets the checks Hermes runs to decide a task is done, here make check. The first time Hermes sees the recipe, and whenever it changes, it shows you the commands and runs nothing until you choose Run setup.

The Hermes IDE New task sheet with a refund-endpoint task typed, chips for Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and the Hermes will run preview
The ⌘N New task sheet: Claude Code on orders-api in a new hermes/ worktree, with Accept edits, model and effort chips and a preview of the command Hermes will run.

Hermes, a CI job, a Stop hook and you at the terminal all run the same make check.


If this was useful

Of the ten targets, db-reset is the one that can hurt you, and it gets harder to write safely the more your data store does: migrations, seeds, extensions, a second store for cache or search. Database Playbook is my short guide to choosing the right store for each system and the trade-offs you live with after. If agents are about to touch your schema, it is a good read before they do. And if you run several agents at once, Hermes IDE is free, source-available under BSL 1.1, on macOS, Windows and Linux (GitHub).

Database Playbook: Choosing the Right Store for Every System You Build

來源:DEV Community · dev.to