Reframe M1-6: AI drives git, lesson=theory, de-slop + issue fixes (#92)
Sync course wiki / sync-wiki (push) Successful in 4s
Sync course wiki / sync-wiki (push) Successful in 4s
Co-authored-by: claude <claude@jpaul.io> Co-committed-by: claude <claude@jpaul.io>
This commit was merged in pull request #92.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Module 5 — Commit the AI's Config, Not Just the Code
|
||||
|
||||
> **The instructions you give the model are as worth versioning as the code it writes.** Write your
|
||||
> project's conventions down once, commit them, and every teammate — and every agent — inherits the
|
||||
> project's conventions down once, commit them, and every teammate (and every agent) inherits the
|
||||
> same setup instead of each of you hand-tuning your own and quietly drifting apart.
|
||||
|
||||
---
|
||||
@@ -22,8 +22,8 @@
|
||||
By the end of this module you can:
|
||||
|
||||
1. Identify the repo-level instructions file your agentic tool reads, and explain what belongs in it.
|
||||
2. Write an instructions file for a real project — conventions, build/test commands, coding
|
||||
standards, off-limits files, house style — that an AI will actually act on.
|
||||
2. Write an instructions file for a real project (conventions, build/test commands, coding
|
||||
standards, off-limits files, house style) that an AI will actually act on.
|
||||
3. Commit that file so the configuration travels with the repo, not with one person's machine.
|
||||
4. Demonstrate the AI obeying the committed instructions, and changing its behavior when you change
|
||||
the file.
|
||||
@@ -65,7 +65,7 @@ a briefing for an agent that will edit this code. Keep it to what changes the AI
|
||||
`python cli.py <command>`. Run tests with `python -m unittest`. Don't claim a change works until
|
||||
the tests pass." This single line stops the AI from inventing a test runner you don't use.
|
||||
- **Coding standards** — formatting, typing, error handling, the libraries you do and don't want.
|
||||
"Use the standard library only — no third-party packages. Type-hint public functions."
|
||||
"Use the standard library only, no third-party packages. Type-hint public functions."
|
||||
- **"Don't touch these files."** — the off-limits list. Generated files, vendored code, secrets,
|
||||
anything the AI should read but never rewrite. "Never edit `tasks.json` by hand; it's generated."
|
||||
- **House style** — the taste calls that otherwise come back wrong every time. "Keep functions
|
||||
@@ -73,7 +73,7 @@ a briefing for an agent that will edit this code. Keep it to what changes the AI
|
||||
cleverness."
|
||||
|
||||
The test of a good line: would you otherwise have to say it again next session? If yes, it belongs in
|
||||
the file. If the AI already gets it right without being told, leave it out — bloat dilutes the
|
||||
the file. If the AI already gets it right without being told, leave it out; bloat dilutes the
|
||||
signal (see *Where it breaks*).
|
||||
|
||||
### Why commit it instead of keeping it in your head (or your settings)
|
||||
@@ -83,50 +83,77 @@ useful for personal preferences, but it's the wrong home for project knowledge,
|
||||
lives: on *your* laptop, invisible to everyone else.
|
||||
|
||||
Picture a two-person project with no committed instructions file. You've trained your local setup to
|
||||
run `python -m unittest` and avoid `tasks.json`. Your teammate's setup hasn't — their agent reformats whole files
|
||||
run `python -m unittest` and avoid `tasks.json`. Your teammate's setup hasn't, so their agent reformats whole files
|
||||
and hand-edits the generated JSON. You're both "using AI on the same repo," but you're getting
|
||||
different behavior, and neither of you can see the other's configuration. That's **drift**: the same
|
||||
codebase, diverging because the rules live in two heads instead of one file.
|
||||
|
||||
Commit the file and that collapses. The configuration is now part of the repo. Clone the repo, get
|
||||
the rules. A new teammate — or a brand-new agent that's never seen the project — is configured
|
||||
the rules. A new teammate (or a brand-new agent that's never seen the project) is configured
|
||||
correctly on the first run, because the setup travels *with the code* instead of with whoever set it
|
||||
up. This is the same move as Module 2's "the repo is durable memory the AI can read," aimed one level
|
||||
up: not just the code's history, but the instructions for working on it.
|
||||
|
||||
### The real unlock: AI behavior becomes reviewable
|
||||
### Shared config vs. personal config
|
||||
|
||||
The instructions file is the main thing worth committing, but it's not the only AI config a tool drops
|
||||
in a repo. Those files split cleanly into *shared* (belongs in the repo, so every collaborator and
|
||||
every agent inherits it) and *personal* (your machine, your keys, your taste, kept out). Take Claude
|
||||
Code as the concrete case (sub your own agent's filenames):
|
||||
|
||||
| File | Shared or personal |
|
||||
| --- | --- |
|
||||
| `CLAUDE.md` (the instructions file) | **Shared** — the whole point of this module |
|
||||
| `.claude/settings.json` (project settings: permissions, hooks config) | **Shared** — the team runs the same setup |
|
||||
| `.claude/settings.local.json` (your personal overrides) | **Personal** — gitignored for you |
|
||||
| `.mcp.json` (the MCP servers the project uses) | **Shared if the project relies on them** |
|
||||
| `.claude/commands/`, `.claude/agents/`, `.claude/hooks/` | **Shared if the project uses them** |
|
||||
|
||||
The principle is tool-agnostic. This very repo commits an `AGENTS.md` instead of a `CLAUDE.md` (same
|
||||
job, vendor-neutral name) and keeps personal settings out. The line to hold: anything that defines
|
||||
*how this project is worked on* is shared; anything that's your own machine or your secrets is not.
|
||||
Rather than guess the split yourself, you can ask the agent which of its config files belong in the
|
||||
repo. The lab does exactly that.
|
||||
|
||||
### AI behavior becomes reviewable
|
||||
|
||||
Here's the part that makes this more than a convenience. Once the instructions live in the repo, **a
|
||||
change to how the AI works on this project is a change to a tracked file** — so it shows up exactly
|
||||
like a code change does:
|
||||
change to how the AI works on this project is a change to a tracked file**, so it shows up exactly
|
||||
like a code change. Tighten "keep functions small" into "no function over 30 lines" and `git diff`
|
||||
reports it the same way it reports an edit to `tasks.py`:
|
||||
|
||||
```bash
|
||||
git diff
|
||||
```diff
|
||||
## House style
|
||||
-- Keep functions small and single-purpose.
|
||||
+- No function over 30 lines; split anything longer.
|
||||
```
|
||||
|
||||
When someone tightens "keep functions small" into "no function over 30 lines," or adds
|
||||
`infra/` to the don't-touch list, that decision arrives as a *diff* you can read, question, and
|
||||
accept or reject. It's no longer an invisible tweak in one person's settings that silently changes
|
||||
what the AI does for everyone. The way your team works with AI becomes a reviewable artifact with a
|
||||
history — you can `git log` it and see *why* a rule exists and when it was added.
|
||||
That decision arrives as a *diff* you can read, question, and accept or reject. It's no longer an
|
||||
invisible tweak in one person's settings that silently changes what the AI does for everyone. The way
|
||||
your team works with AI becomes a reviewable artifact with a history: `git log` shows *why* a rule
|
||||
exists and when it was added.
|
||||
|
||||
The full version of this lands in **Module 10**, where that diff becomes a pull request someone
|
||||
actually reviews before it merges, and **Module 8**, where a shared remote means the file reaches the
|
||||
whole team. You don't have those yet — so for now the payoff is local: the file is committed, the
|
||||
whole team. You don't have those yet, so for now the payoff is local: the file is committed, the
|
||||
behavior is recorded, and `git diff` already shows changes to it as plainly as changes to any code.
|
||||
The habit starts now; the team-scale payoff arrives on schedule.
|
||||
|
||||
### This course commits its own
|
||||
|
||||
You don't have to take this on faith — this repo does exactly what the module teaches. At the root of
|
||||
*The Workflow* is an `AGENTS.md` file: the committed instructions for the agents that help author the
|
||||
course. It states what the repo is, the core promises (model-agnostic, GitHub-as-default-not-
|
||||
requirement, the load-bearing dependency chain), the voice, the lab conventions, and a flat "Don't"
|
||||
list. Open it:
|
||||
You don't have to take this on faith: this repo does exactly what the module teaches. At the root of
|
||||
*The Workflow* is an `AGENTS.md` file, the committed instructions for the agents that help author the
|
||||
course. (Claude Code reads `CLAUDE.md` by default; `AGENTS.md` is the same job under a vendor-neutral
|
||||
name, and most tools can be pointed at it.) It states what the repo is, the core promises
|
||||
(model-agnostic, GitHub-as-default-not-requirement, the load-bearing dependency chain), the voice, the
|
||||
lab conventions, and a flat "Don't" list. Because it's committed, its history reads like a changelog
|
||||
of how agents work here:
|
||||
|
||||
```bash
|
||||
git show HEAD:AGENTS.md # or just open AGENTS.md in your editor
|
||||
git log --oneline AGENTS.md # its history — every change to how agents work on this repo
|
||||
```text
|
||||
$ git log --oneline AGENTS.md
|
||||
4bd586b Tighten the no-slop voice rule; thin em-dashes
|
||||
ced344d Add the git-reframe section (AI drives git from Module 4)
|
||||
9e9bb51 Initial commit
|
||||
```
|
||||
|
||||
That file is why every module in this course sounds like one course instead of twenty-seven
|
||||
@@ -137,8 +164,8 @@ tutorials. It's the worked example for everything below.
|
||||
A committed instructions file is the lightweight foundation. It says *how this project works* in
|
||||
general — always-on context the AI reads every session. When you find yourself wanting to capture a
|
||||
*specific repeatable procedure* ("here's exactly how we cut a release," "here's our playbook for
|
||||
adding a new CLI command"), that's the structured big sibling: **Skills (Module 21)**. Same instinct —
|
||||
write the knowledge down, commit it, let the AI execute it your way — but packaged as reusable
|
||||
adding a new CLI command"), that's the structured big sibling: **Skills (Module 21)**. Same instinct
|
||||
(write the knowledge down, commit it, let the AI execute it your way) but packaged as reusable
|
||||
playbooks instead of a single always-on briefing. Start with the instructions file; graduate to
|
||||
skills when a procedure earns its own page.
|
||||
|
||||
@@ -147,21 +174,21 @@ skills when a procedure earns its own page.
|
||||
## The AI angle
|
||||
|
||||
This is the course thesis applied to your own configuration. **The model is the cheap, swappable
|
||||
part; the setup you build around it is the durable artifact.** When you swap models next quarter —
|
||||
and you will — your committed instructions file carries over unchanged. The new model reads the same
|
||||
part; the setup you build around it is the durable artifact.** When you swap models next quarter (and
|
||||
you will), your committed instructions file carries over unchanged. The new model reads the same
|
||||
conventions, the same test command, the same don't-touch list, and behaves consistently on day one.
|
||||
You configured the *project*, not the model.
|
||||
|
||||
Three things make this specifically an AI problem, not a generic config chore:
|
||||
|
||||
- **AI has no memory across sessions, but it reads files.** A committed instructions file is the
|
||||
cleanest way to give an ephemeral agent durable, project-specific context — written once, read
|
||||
cleanest way to give an ephemeral agent durable, project-specific context: written once, read
|
||||
every session, by every model.
|
||||
- **AI is confidently inconsistent without a spec.** Unprompted, it'll pick a test runner, a
|
||||
formatting style, a place to put new code — and pick differently next time. The instructions file
|
||||
formatting style, a place to put new code, and pick differently next time. The instructions file
|
||||
is how you make "the way we do it here" the default instead of a coin flip.
|
||||
- **AI behavior is otherwise invisible.** A teammate's hand-tuned local rules silently change what
|
||||
the AI does. Committing the rules drags that into the open where it can be reviewed — which is the
|
||||
the AI does. Committing the rules drags that into the open where it can be reviewed, which is the
|
||||
whole reason this audience trusts version control in the first place.
|
||||
|
||||
---
|
||||
@@ -179,42 +206,53 @@ editor-integrated AI (Module 4) for the part where the AI obeys the file.
|
||||
- Optionally, a test command for the AI to honor — Python's built-in `python -m unittest` works with
|
||||
nothing to install (you'll write a real suite in Module 13; until then it simply reports no tests).
|
||||
|
||||
### Part A — Write the instructions file
|
||||
### Part A — Write the instructions file and let the AI commit the config
|
||||
|
||||
1. Look up the instructions filename your tool reads. Copy this module's starter,
|
||||
`lab/instructions-file-starter.md`, to that filename at the **root of your `tasks-app` repo**.
|
||||
(If your tool reads several names, copy it to each, or symlink them.)
|
||||
1. Look up the instructions filename your tool reads (Claude Code uses `CLAUDE.md`; sub your own).
|
||||
Open an AI session in the `tasks-app` repo and direct it to create that file from this module's
|
||||
starter, made true for the project:
|
||||
|
||||
```bash
|
||||
cd ~/ai-workflow-course/tasks-app
|
||||
# replace <YOUR_TOOL_FILE> with the name your tool actually reads:
|
||||
cp /path/to/modules/05-commit-the-ai-config/lab/instructions-file-starter.md <YOUR_TOOL_FILE>
|
||||
```
|
||||
> *"Read `~/ai-workflow-course/modules/05-commit-the-ai-config/lab/instructions-file-starter.md`.
|
||||
> Create my tool's instructions file at the root of this repo seeded from it, and adjust every line
|
||||
> so it's accurate for this tasks-app. Don't commit yet — I want to review it first."*
|
||||
|
||||
2. Open it in your editor and make it true for *your* project. The starter is filled in for the
|
||||
`tasks-app`, but read every line and confirm it matches reality — wrong instructions are worse
|
||||
than none. At minimum, set the real test command (or delete the line if you don't have tests
|
||||
yet).
|
||||
You're handing the AI the file creation and placement. You keep the judgment over *content*: a
|
||||
wrong instruction is worse than none.
|
||||
|
||||
3. Commit it. This is the point of the whole module:
|
||||
2. Read what it produced, line by line. The starter is filled in for `tasks-app`, but confirm it
|
||||
matches reality. At minimum, check the test command is real (or have it drop the line if you don't
|
||||
have tests yet). Fix anything off before it gets committed.
|
||||
|
||||
```bash
|
||||
git add <YOUR_TOOL_FILE>
|
||||
git commit -m "Add committed AI instructions for tasks-app"
|
||||
```
|
||||
3. Now ask the AI which config should travel with the repo, then let it stage and commit:
|
||||
|
||||
The configuration now travels with the repo.
|
||||
> *"Which of the AI config files in this repo should be committed so a teammate gets the same setup,
|
||||
> and which are personal to my machine? Stage the shared ones and commit them with a clear message."*
|
||||
|
||||
A good answer separates *shared* from *personal*. For Claude Code that means commit `CLAUDE.md` and
|
||||
`.claude/settings.json`; leave `.claude/settings.local.json` out (gitignored personal overrides);
|
||||
commit `.mcp.json` and anything under `.claude/commands/`, `.claude/agents/`, or `.claude/hooks/`
|
||||
*if the project uses them*. For a fresh `tasks-app` that's usually just the instructions file.
|
||||
Letting the agent stage and commit is the point: from here on you direct the git work and check the
|
||||
result.
|
||||
|
||||
4. Verify it landed the way you wanted:
|
||||
|
||||
> *"Show me what you just committed."*
|
||||
|
||||
Confirm the commit contains the instructions file and only the files you meant to share (no
|
||||
`settings.local.json`, no secrets). This commit is the point of the whole module: the configuration
|
||||
now travels with the repo.
|
||||
|
||||
### Part B — Watch the AI obey it
|
||||
|
||||
4. Start a **fresh** AI session in your editor (so it picks up the file cleanly) and give it a task
|
||||
5. Start a **fresh** AI session in your editor (so it picks up the file cleanly) and give it a task
|
||||
that the instructions constrain. Pick a command your app doesn't have yet (so this is a real
|
||||
feature, not a re-add) — for example:
|
||||
|
||||
> *"Add a `search <term>` command that lists only the tasks whose title contains `term`. Then
|
||||
> confirm it works."*
|
||||
|
||||
5. Watch for the file taking effect. A correctly-configured agent should, without you saying any of
|
||||
6. Watch for the file taking effect. A correctly-configured agent should, without you saying any of
|
||||
it this time:
|
||||
- put the logic where your conventions said it goes (core in `tasks.py`, CLI wiring in `cli.py`);
|
||||
- **not** hand-edit `tasks.json` (you marked it off-limits);
|
||||
@@ -224,40 +262,38 @@ editor-integrated AI (Module 4) for the part where the AI obeys the file.
|
||||
You're checking that behavior you'd normally have to *dictate every session* now happens by
|
||||
default. That delta is the file working.
|
||||
|
||||
6. If it ignored a rule, that's signal too — tighten the wording, commit the change, and try again.
|
||||
7. If it ignored a rule, that's signal too: tighten the wording, commit the change, and try again.
|
||||
Vague instructions get vague compliance; specific, imperative lines ("Never edit `tasks.json` by
|
||||
hand — it is generated") land far better than soft ones ("try to avoid editing generated files").
|
||||
hand; it is generated") land far better than soft ones ("try to avoid editing generated files").
|
||||
|
||||
### Part C — Make a behavior change reviewable
|
||||
|
||||
7. Now change *how the AI works* and watch it show up as a diff. Add a house-style rule to the file —
|
||||
say, a hard line length:
|
||||
8. Now change *how the AI works* and watch it show up as a diff. Direct the AI to add a house-style
|
||||
rule to the instructions file, say a hard line length:
|
||||
|
||||
> Add to the instructions file: `Keep functions under 20 lines; split anything longer.`
|
||||
> *"Add this line to the instructions file under house style: `Keep functions under 20 lines; split
|
||||
> anything longer.` Don't commit yet — I'll review the diff first."*
|
||||
|
||||
8. Before committing, read the change exactly as a reviewer would:
|
||||
9. Before anything gets committed, read the change exactly as a reviewer would. This is your
|
||||
verification step, so run it yourself:
|
||||
|
||||
```bash
|
||||
git diff
|
||||
```
|
||||
|
||||
That diff *is* the change to your AI workflow — readable, attributable, revertable. Commit it:
|
||||
That diff *is* the change to your AI workflow: readable, attributable, revertable. When it's right,
|
||||
direct the AI to record it:
|
||||
|
||||
```bash
|
||||
git add <YOUR_TOOL_FILE>
|
||||
git commit -m "Require functions under 20 lines"
|
||||
```
|
||||
> *"Commit that with a message describing the rule."*
|
||||
|
||||
9. Look at the history of just this file:
|
||||
10. Confirm the history. Ask the AI to surface it (or read it yourself):
|
||||
|
||||
```bash
|
||||
git log --oneline <YOUR_TOOL_FILE>
|
||||
```
|
||||
> *"Show me the commit history of the instructions file."*
|
||||
|
||||
Every line is a decision about how the AI behaves on this project — recorded, not lost in someone's
|
||||
local settings. (In Module 8 this file reaches your whole team via a remote; in Module 10 that diff
|
||||
becomes a PR someone reviews before it lands. The habit you just built is what those modules turn
|
||||
into a team workflow.)
|
||||
Every line is a decision about how the AI behaves on this project, recorded rather than lost in
|
||||
someone's local settings. (In Module 8 this file reaches your whole team via a remote; in Module 10
|
||||
that diff becomes a PR someone reviews before it lands. The habit you just built is what those
|
||||
modules turn into a team workflow.)
|
||||
|
||||
---
|
||||
|
||||
@@ -267,22 +303,23 @@ Be honest about what a committed instructions file does and doesn't buy you:
|
||||
|
||||
- **It's guidance, not a guarantee.** The file biases the model strongly; it does not bind it. An AI
|
||||
can still ignore a line, especially a vague one, especially deep in a long session. The enforcement
|
||||
that *can't* be ignored — tests that fail the build, scans that block a merge — is **CI
|
||||
that *can't* be ignored (tests that fail the build, scans that block a merge) is **CI
|
||||
(Module 14)** and **security scanning (Module 15)**. The instructions file reduces how often the AI
|
||||
goes wrong; it doesn't replace the gates that catch it when it does.
|
||||
- **Bloat kills it.** A 300-line instructions file is read the way *you* read a 300-line terms-of-
|
||||
service: not really. Every line you add dilutes the rest. Keep it to what actually changes behavior,
|
||||
and prune lines the model already honors without being told.
|
||||
- **Stale instructions are worse than none.** A file that says "run the tests with `python -m
|
||||
unittest`" after you've switched to a different runner will actively misdirect the AI. The file is code-adjacent — it has to be
|
||||
maintained like code, and reviewed like code. That's exactly why committing it (so changes are
|
||||
unittest`" after you've switched to a different runner will actively misdirect the AI. The file is
|
||||
code-adjacent: it has to be maintained like code, and reviewed like code. That's exactly why
|
||||
committing it (so changes are
|
||||
visible) matters.
|
||||
- **The team payoff isn't here yet.** On a solo local repo, the "no more drift between teammates"
|
||||
argument is theoretical — there's only you. The full value lands with a shared remote
|
||||
argument is theoretical: there's only you. The full value lands with a shared remote
|
||||
(**Module 8**) and review (**Module 10**). What you get *now* is the habit and the local history;
|
||||
don't oversell the team benefit until the team can actually pull the file.
|
||||
- **It is not a security control.** Telling an agent "don't touch `secrets.env`" is a convention, not
|
||||
a permission boundary — a sufficiently confused or adversarial agent can still read or write it.
|
||||
a permission boundary: a sufficiently confused or adversarial agent can still read or write it.
|
||||
Real isolation and least-privilege for agents come later (**Modules 16 and 22**). The instructions
|
||||
file expresses intent; it doesn't enforce it.
|
||||
|
||||
@@ -294,14 +331,14 @@ Be honest about what a committed instructions file does and doesn't buy you:
|
||||
|
||||
- Your `tasks-app` repo has a committed instructions file at the root, filled in to match the actual
|
||||
project, and `git log` shows the commit that added it.
|
||||
- You've watched a fresh AI session honor a rule from the file — placing code where your conventions
|
||||
said, respecting the don't-touch list, or running your stated test command — *without you saying it
|
||||
- You've watched a fresh AI session honor a rule from the file (placing code where your conventions
|
||||
said, respecting the don't-touch list, or running your stated test command) *without you saying it
|
||||
that session*.
|
||||
- You've changed a behavior rule, read the change with `git diff`, and committed it — so a change to
|
||||
- You've changed a behavior rule, read the change with `git diff`, and committed it, so a change to
|
||||
how the AI works is now a reviewable diff with a history.
|
||||
- You can explain, in one sentence, why committing the file beats each teammate hand-tuning their own
|
||||
setup: the configuration travels with the repo, so nobody drifts.
|
||||
|
||||
When the AI behaves like it already knows your project the moment you open it — and you didn't say a
|
||||
word this session — the file is doing its job. Module 6 takes the safety net further: branches, so the
|
||||
When the AI behaves like it already knows your project the moment you open it, and you didn't say a
|
||||
word this session, the file is doing its job. Module 6 takes the safety net further: branches, so the
|
||||
AI can try something wild in a sandbox you can throw away.
|
||||
|
||||
Reference in New Issue
Block a user