Skip to content

AI Project Setup

Read this in the first sprint, once the repository exists; it gives you the configuration that makes an AI tool understand your project, and the hooks that constrain it.

A tool that knows nothing about your project produces generic suggestions: the wrong framework, a second way of doing something you already do one way, a dependency nobody agreed to. Given your architecture, your conventions and your constraints, the same tool produces suggestions that fit. This page is the configuration; the Generative AI guide is the practice.

Almost all of it is a file you commit, which is the point: reviewed, versioned and shared, so every teammate’s tool behaves the same way on the same code. agents.md is the cross-tool convention for the instructions, and the Model Context Protocol is the open standard behind the servers. Without them, each teammate prompts differently, gets different answers, and the codebase drifts toward an inconsistency nobody can point at a commit for.

Most AI coding tools support some form of project-level configuration that persists across sessions:

  • AGENTS.md in your repository root: the cross-tool convention, read by a growing number of agents and the right default when your team does not all use the same tool.
  • Claude Code: CLAUDE.md in your repository root.
  • Cursor: .mdc files in the .cursor/rules/ directory. Cursor also reads AGENTS.md. A top-level .cursorrules file is the legacy format; do not start a new project with it.
  • GitHub Copilot: .github/copilot-instructions.md.

Write the content once, in AGENTS.md, and have any per-tool file point at it. Two instruction files that disagree with each other are worse than none: whichever one your teammate’s tool reads is the one that wins, and nobody will notice they have drifted until the suggestions stop matching the code.

What to include in project-level instructions:

  • A brief project overview (what it does, who it is for).
  • The technology stack (languages, frameworks, key libraries).
  • Coding conventions (naming, file structure, error handling patterns).
  • Architectural constraints (“we use a monolith, not microservices”; “all data access goes through the repository layer”).
  • Testing expectations (what to test, which framework, where tests live).
  • Anything the AI should not do (e.g., “do not add new dependencies without discussion”; “do not refactor code outside the scope of the current task”).

Kept to that list, the file is short:

AGENTS.md
## Project
Shift board for <partner>: coordinators post shifts, volunteers claim
them. Read docs/requirements.md and docs/design.md before changing
behavior; decisions and their reasons are in docs/adr/.
## Stack
TypeScript, React 19, Express, Postgres via Prisma, Vitest, Playwright.
## Conventions
- All data access goes through src/server/repos/. No SQL in routes.
- Errors: throw AppError with a code; routes map codes to HTTP status.
- Tests live next to the code as *.test.ts. Run `npm test` before a PR.
## Do not
- Add a dependency without an issue that says why.
- Change the Prisma schema, auth, or deployment config without a human
review. These are one-way doors.
- Paste partner data, user emails, or .env contents into any tool.

The last line is the part your Team Charter calls the AI and confidentiality one-pager, spelled out for the tools. The human-readable version is a section of the charter itself and says the same thing in the partner’s terms:

## AI and confidentiality (in docs/charter.md)
Any tool: our own code, our docs, synthetic test data.
Partner-approved tools only (see list): the partner's API docs and
sample exports.
Never: production data, volunteer names and emails, credentials, the
partner's internal designs. Redact first; when unsure, ask the AI
Coordinator before pasting.

Everything above is a request. The model reads AGENTS.md, usually follows it, and sometimes does not, because “do not change the Prisma schema” is one line competing with everything else in a long context window. A hook is not a request. A hook is a script your tool runs on an event, and it runs whether or not the model was paying attention.

Most agent tools support them now, and the configuration is committed like everything else:

  • Claude Code: .claude/settings.json, with SessionStart, PreToolUse and PostToolUse events matched against a tool name.
  • Cursor: .cursor/hooks.json, with preToolUse, beforeShellExecution, afterFileEdit and others.
  • GitHub Copilot CLI: .github/hooks/*.json, with sessionStart, userPromptSubmitted, preToolUse and postToolUse.

The names differ; the shape does not. A pre-tool hook inspects the call the agent is about to make and can refuse it:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-git.mjs"],
"timeout": 10
}
]
}
]
}
}

The guard itself is short: read the command off standard input, exit non-zero with a message when it matches something you never want, such as a force push or a commit straight to main. The agent reads the message and takes another route. The hooks worth writing on a student project are the dull ones: block commits to the default branch, block edits to a generated directory, run your formatter after every edit so the diff is never noise, and print the current branch and build state at the start of a session so the agent does not start from a stale assumption.

Most tools also take a plain allow and deny list for commands and paths. Start there, and write a hook when the decision needs logic.

Three layers, and which one actually holds

Section titled “Three layers, and which one actually holds”

A hook is one layer of three. Each catches what the one before it misses:

Layer Runs Bypassed by
Agent hook When the agent calls a tool Working outside that agent
Git hook (lefthook, husky) On commit and push git commit --no-verify
CI and branch protection On the pull request Nothing, once you turn off admin bypass

Checks a human should also pass belong in the git layer, because it fires for typed commands and agent commands alike:

lefthook.yml
pre-commit:
commands:
branch:
run: test "$(git branch --show-current)" != main
fail_text: "never commit on main; branch first"
lint:
glob: "*.{ts,tsx}"
run: npx biome check {staged_files}

Anyone can skip that with --no-verify, which is the reason for the third layer: the same checks run in CI, where nobody can skip them, and branch protection turns a red build into a blocked merge. One setting decides whether that last layer is real. Protection lets administrators bypass it by default, and on a student repository everyone is usually an administrator, so turn that off or the gate is a suggestion with extra steps. The workflow file and the repository settings are covered in those guides.

Read the three as a trade between speed and reach. The agent hook is instant and narrow: it stops that agent, in that session, and nothing else. CI is slow and total. Put the fast feedback where it saves you a round trip and the real gate where nobody can route around it, and do not mistake one for the other.

None of this requires a tool that supports hooks. The git and CI layers work with any assistant, or with none, and they are the two that hold.

The Model Context Protocol (MCP) allows AI tools to connect to external data sources and services. Some useful MCP servers:

  • Context7: provides up-to-date library and framework documentation to your AI tool, reducing hallucinated API calls.
  • Playwright MCP: allows your AI tool to interact with a browser for testing and debugging web applications.
  • Database MCP servers: let your AI tool query your database schema directly rather than guessing at table structures.

Which servers a tool loads is itself configuration, and it belongs in the repository with everything else: most tools read a JSON file at a known path (.mcp.json at the root, .cursor/mcp.json, .vscode/mcp.json), so commit it and every teammate gets the same servers. Keep credentials out of it and reference environment variables instead.

Agent skills (sometimes called slash commands or custom commands) are reusable prompts you define once and invoke repeatedly, so a recurring task runs the same way regardless of who asks for it. A skill is a directory containing a SKILL.md file: Markdown instructions with YAML frontmatter naming and describing the skill, optionally alongside scripts and reference files. skills.sh is a public directory of published ones.

Skills a team actually uses:

  • Code review: check a pull request against the team’s coding standards and architectural constraints.
  • Commit messages: follow the team’s convention without anyone rereading it.
  • Test generation: write tests that match the project’s existing patterns and framework.
  • Documentation: update the docs in the team’s format rather than inventing one.

SKILL.md is now close to a cross-tool convention, so where you put the directory matters more than which tool you use:

  • Claude Code: .claude/skills/<skill-name>/SKILL.md. Invoke with /<skill-name>.
  • Cursor: .cursor/skills/<skill-name>/SKILL.md, also .agents/skills/. Pick one from the / menu.
  • GitHub Copilot: .github/skills/<skill-name>/SKILL.md, and it also reads .claude/skills/ and .agents/skills/. Copilot separately supports single-file prompt files at .github/prompts/<name>.prompt.md, invoked as /<name> in Copilot Chat.

Published collections worth reading. Reading a well-built skill is the fastest way to learn what a good one looks like:

  • obra’s Superpowers: brainstorming, subagent-driven development with built-in code review, systematic debugging, and red/green test-driven development. It also includes skills for writing and testing new skills.
  • Matt Pocock’s skills: spec and ticket flows, TDD, code review, domain modelling, and a “grilling” skill that stress-tests a plan by arguing with you about it.

A separate architectural-review pass, a code-review pass and a plan-before-you-build step are not conveniences. They are the same net the hooks and gates above build, one layer earlier. The difference is that a skill runs when someone invokes it and a hook runs on its own, so a check you would be sorry to forget belongs in a hook.

One warning: a skill is someone else’s opinion, executed automatically. Read one before you install it, because your team owns whatever it produces.

Some tools keep memory across sessions: things the assistant noticed or was told once and retains. It is useful for the small stuff you keep repeating, such as a command that always needs a flag or a directory that is generated and should never be edited.

It is also the one item on this page that is not shared. Memory lives on one person’s machine, in one tool, and your teammates never see it, so anything that would change how another person’s tool behaves belongs in AGENTS.md instead. Treat memory as a convenience for you and the committed files as the team’s contract. A team that puts an architectural constraint in memory has written it down in the one place nobody else can read.

  • One source of truth. Write the content in AGENTS.md and have every per-tool file point at it. Two instruction files that disagree are worse than none.
  • A rule the model can ignore is not a rule. AGENTS.md is where you explain a constraint; a hook or a CI check is where you enforce it. Anything you would be angry to find in a diff belongs in the second place as well as the first.
  • Gate the one-way doors first. Schema changes, auth, deployment config and credentials are where a blocked action is worth an inconvenience. Everything else can stay advisory.
  • Commit all of it. Instructions, hooks, skills and MCP configuration belong in the repository, not in one person’s editor settings.
  • Keep instructions short. A file nobody reads is a file the tool half-follows. The list above fits on a page; anything longer belongs in docs/.
  • Write the “do not” section first. It is the part that prevents damage, and it is the part teams forget.
  • Trip every gate once, on purpose. Run the command the hook should block and try the merge protection should stop.
  • Update it when it lies. An instruction file that describes last term’s architecture actively misleads the tool. Re-read it at every checkpoint.

Most teams never write any of this, ship anyway, and the cost shows up somewhere other than the setup: two people solving the same problem two different ways, a dependency that arrived without a discussion, a schema change nobody reviewed. That is a real cost, but it is diffuse, which is why the file keeps not getting written.

The instruction file rots faster than the code. It is written in week 2 and describes the architecture of week 2 forever, and a confidently wrong instruction is worse than a missing one, because the tool follows it.

Hooks are the highest-leverage item on this page and the one teams reach for last. Two or three cover most of the damage a delegated agent can do to a repository: no commits on the default branch, no edits to generated files, formatter on save. Past that the returns fall off fast: teams write elaborate policy hooks, trip over them twenty times a day, and quietly disable the lot in week 6. A gate that fires constantly gets removed, and then you have no gate.

Gates also fail quietly. Cursor’s hooks fail open by default, so a script that crashes waves the action through unless you set failClosed, and a git hook is one --no-verify from irrelevant. A gate you believe in and have never tripped is worse than no gate, because you delegate as though it were there.

Skills are oversold. A skill that encodes a process your team actually follows is leverage; a skill collected because it looked impressive is a prompt you now maintain. The ones that earn their keep on a student project are usually the boring ones: the review pass, the commit message, the test generator.

MCP servers are the least load-bearing thing on this page. A documentation server meaningfully reduces hallucinated API calls; most of the rest are conveniences you can live without, and each one is another moving part between your tool and your data.

None of this requires a paid tool. Every file described here is plain Markdown or JSON in your repository, and a free-tier assistant reads AGENTS.md exactly as well as an expensive one.

The convention layer is young and it is consolidating in public. AGENTS.md emerged from several vendors converging on one filename rather than from a standards body, and the major tools now read it alongside their own. MCP went the same way: published by Anthropic, then adopted broadly enough to become the default way a tool reaches a database, a browser, or a documentation index. Hooks are repeating the pattern a year behind, with three vendors shipping near-identical event models under different names.

The gating layer is where the change is sharpest, and the reason is arithmetic. When most code was typed, review was the gate and it scaled with the typing. When most code arrives from an agent, review is the bottleneck, and organizations respond by moving the load onto checks a machine enforces: required status checks, protected branches, policy hooks that block a class of command outright instead of trusting a reviewer to notice it. Platform teams increasingly ship the hook configuration alongside the linter config, for the same reason they own the linter: the point is not cleverness, it is that everyone’s tool behaves the same way on the same repository.

In research groups the pattern is younger and the pressure is different: reproducibility, not consistency. An instruction file that names the data layout, the environment, and which steps must never be regenerated is doing the same job a good README does, one layer closer to the tool. A hook that refuses to overwrite a published result is doing the job a lab notebook cannot.