Skip to main content

Axiom VIII: Version Control is Memory

Axiom VII gave James tests that define what "correct" means. But tests capture what the code should do. They say nothing about what the code used to do, why it changed, or who changed it. A week after the $12,000 discount fix, that gap became painfully clear.

James's team lead called a post-mortem. "Walk us through the original bug," she said. "Show us what the function looked like before the fix." James opened apply_discount(), but only the current version remained. The buggy implementation was gone, overwritten when the AI regenerated. He checked his git history:

commit a1b2c3d
wip

commit b2c3d4e
updates

commit c3d4e5f
fix stuff

Three commits from that week. No messages explaining what changed or why. No record of the original buggy implementation. No trace of which test caught the error. No documentation of the decision to switch from manual review to TDG. James had the right code now, but no memory of how he got there.

"I commit constantly," James said after the meeting, defensive. "Three commits that week. I'm already writing tests, types, and documentation, and you want me to spend five minutes crafting a paragraph for every commit too?"

"Pull up your three commits," Emma said.

James did: wip, updates, fix stuff. He stared at them. "Okay, the messages are bad. But the code is in there. I could diff each one and figure out what changed."

"Your team lead just asked you to do exactly that," Emma said. "How long would it take?"

James imagined opening each diff: 47 files changed across the three commits, some containing multiple unrelated changes. He would have to reconstruct the timeline, figure out which lines were the discount fix versus the shipping update versus the test additions. It would take an hour of archaeology. A single sentence per commit, explaining why the change was made, would have answered the question in seconds.

"The code records what changed," James said. "But nobody (including me in three weeks) can tell why."

Emma pulled up her own project's history:

commit d4e5f6g
fix(orders): correct discount calculation - multiply by (1 - rate)

apply_discount() was returning price * discount_rate instead of
price * (1 - discount_rate), giving 85% discount instead of 15%.
Caught by TDG test: assert apply_discount(order, 0.15).total == 85.0

Root cause: ambiguous prompt "apply percentage discount" - AI
interpreted as multiply-by-rate. Added 5 specification tests
to prevent recurrence. See test_discount.py.

Impact: $12,000 loss over weekend. Post-mortem: PM-2025-003

Same fix. But Emma's commit was a memory. It recorded not just what changed, but why it changed, what caused the original error, how it was caught, and where to find the broader context. Six months from now, anyone reading that commit would understand the full story without asking a single question.

"Git is not a backup tool," Emma said. "It is the memory of your project. Every commit is a decision you are recording for your future self, your teammates, and your AI. If the memory is wip, you have amnesia."

This is Axiom VIII.


The Problem Without This Axiom

James's post-mortem exposed a pattern that every developer who uses AI will recognize. He had been productive, shipping features, fixing bugs, regenerating implementations through TDG. But his git history was a graveyard of meaningless messages: wip, updates, fix stuff, changes, done. Every commit recorded that something changed. None recorded why.

The consequences compounded:

SituationJames's ExperienceWhat Disciplined Git Provides
"What was the original bug?"File overwritten, no recordgit show HEAD~3:src/discount.py shows the buggy version
"Which test caught the error?""I think it was the boundary test..."Commit message names the specific assertion
"When did shipping get slow?"Manual log searching, guessworkgit bisect finds the exact commit
"Can we undo the ORM change?"Risky manual reversalgit revert abc123 safely creates inverse commit
AI asks "What's the project context?"James explains from scratch every sessionAI reads git log for recent decisions

The cost was invisible day-to-day but catastrophic at the post-mortem. Every undocumented decision became a question nobody could answer. Every unexplained change became a mystery. The team spent three hours reconstructing a story that disciplined commit messages would have told in three minutes.


The Axiom Defined

Axiom VIII: Version Control is Memory. Git provides the persistent memory layer for all work. Every decision, every change, every experiment is recorded. Git is not just version control; it is the system of record for software evolution.

This axiom elevates git from a tool (something you use to save work) to a system (the authoritative record of how and why your software became what it is). James had been using git as a save button (git add . && git commit -m "wip"), the way you might press Ctrl+S in a document editor. Emma taught him to use it as a journal, each entry recording a decision, its rationale, and its context.

The key insight: files give you current state; git gives you all past states and the story between them.

What Git Actually Records

When used with discipline, git captures four dimensions of project memory:

DimensionGit MechanismWhat It Preserves
DecisionsCommit messagesWhy changes were made, what alternatives were rejected
ExperimentsBranchesParallel approaches tried, including failed ones
MilestonesTagsStable points you can always return to
AccountabilityBlame/LogWho made each decision and when

Together, these form the institutional memory that James's post-mortem was missing: a record that survives team changes, context switches, and the passage of time.


From Principle to Axiom

In Chapter 17, you learned Principle 5: Persisting State in Files. Remember the comparison: re-explaining your project conventions every session wastes 10 minutes each time. Over 20 sessions, that is 3+ hours lost. Writing a single CLAUDE.md file once takes 20 minutes but saves every future session from starting at zero. That principle established a critical insight: AI systems are stateless between sessions, so all important context must live in files that AI can read.

Axiom VIII builds directly on that foundation:

Principle 5Axiom VIII
Persist state in filesManage that state with history
Files give you current stateGit gives you all past states
CLAUDE.md tells AI what to do nowGit log tells AI what was tried before
Files are the interfaceGit is the memory behind the interface
Solves: "AI forgot my conventions"Solves: "Nobody remembers why"

The relationship is complementary: Principle 5 says where to persist (files). Axiom VIII says how to manage persistence over time (version control). Files without git are snapshots. Files with git are a narrative.

James experienced this progression firsthand:

  • Before Principle 5: His project conventions lived in his head. Every AI session started from zero.
  • After Principle 5: His conventions lived in CLAUDE.md. The AI could read current rules.
  • After Axiom VIII: His conventions lived in versioned CLAUDE.md. The AI could read not just the current rules, but the history of why each rule was added, including the $12,000 discount incident that prompted the TDG requirement.
The Discipline That Preceded Git

The idea that version control could serve as institutional memory has roots older than most developers realize. In April 2005, Linus Torvalds built Git's core in roughly two weeks, not as a side project, but out of necessity. The Linux kernel, the largest collaborative software project in history, had been using a proprietary tool called BitKeeper for version control. When BitKeeper revoked its free license, Torvalds needed a replacement that could handle thousands of distributed developers collaborating without a central server.

The tools that preceded Git (CVS in 1990 and Subversion in 2000) required a central server. Every commit went through a single point of failure. If the server was down, nobody could commit. If the server was lost, the history was lost. Torvalds designed Git to be distributed: every developer's copy contains the complete history. There is no single point of failure. The memory lives everywhere.

But Torvalds's deeper insight was about what version control records. CVS tracked file changes. Git tracks snapshots of the entire project state: every commit captures the complete state of every file at that moment. This means you can reconstruct your project at any point in its history, not just individual files. The project's memory is not a collection of diffs. It is a sequence of complete states, each connected to the decision that produced it.

James's wip commits squandered this power. Git was designed to be a complete institutional memory. He had been using it as a save button.


Git as System of Record

New to Git?

If these git commands look unfamiliar, Chapter 23 covers version control foundations (commits, branches, and pull requests) from the ground up. Review that chapter first, then return here to see how those same tools become your project's memory system.

Git branching workflow: main branch with a feature/discount branch forking off, commits, and a Pull Request + Review merge back

After the post-mortem, Emma spent an afternoon teaching James how git actually works when used with discipline. "Git gives you four tools," she said. "Commits, branches, tags, and blame. Each one is a different kind of memory."

Commits Are Decisions

Every commit should answer one question: "What decision was made, and why?"

The code diff shows what changed. The commit message explains why it changed. Together, they form a decision record. Emma showed James how to query that record:

# Find all feature decisions this year
git log --oneline --since="2024-01-01" --grep="feat"

# Read the full context of a specific decision
git show abc123

# Find who made a specific decision and when
git blame src/config.py
$ git log --oneline --since="2024-01-01" --grep="feat"
a1b2c3d feat(shipping): add international surcharge calculation
e5f6g7h feat(orders): implement discount calculation per TDG specs
k9l0m1n feat(auth): add OAuth2 PKCE flow for mobile clients

$ git blame src/config.py
d4e5f6g (Emma 2024-03-15) FREE_SHIPPING_THRESHOLD = 75.0 # PM-2024-019
a1b2c3d (James 2024-03-20) INTERNATIONAL_SURCHARGE = 8.00
b2c3d4e (James 2024-04-01) MAX_ORDER_ITEMS = 50

"Think of each commit as a journal entry," Emma told James. "The diff is what happened. The message is why it matters."

Branches Are Experiments

Branches are not just for "features." They are parallel experiments, hypotheses being tested. When James wanted to try replacing his JSON storage with SQLite (the relational approach from Axiom VI), Emma told him to create a branch:

# Start an experiment
git checkout -b experiment/try-sqlite-storage

# Work on the experiment...
# If it succeeds: merge it
git checkout main && git merge experiment/try-sqlite-storage

# If it fails: keep the record, delete the branch
git checkout main
git branch -d experiment/try-sqlite-storage
# The commits still exist in reflog for 90 days

Even failed experiments have value. The commit history on a deleted branch records what was tried and why it did not work, preventing the team from repeating the same failed approach six months later. "Your team already tried Redis caching last quarter," Emma pointed out. "If they had kept the experiment branch, the new developer would not have spent two weeks rediscovering why it did not work."

Tags Are Milestones

Tags mark stable, known-good states you can always return to. James learned to tag his project before any risky change:

# Mark a release
git tag -a v1.2.0 -m "Feature complete: order management with TDG tests"

# Mark a significant decision point
git tag -a pre-sqlite-migration -m "Last commit before JSON->SQLite migration"

# Return to any milestone instantly
git checkout v1.2.0
$ git tag -a v1.2.0 -m "Feature complete: order management with TDG tests"

$ git tag
pre-sqlite-migration
v1.0.0
v1.1.0
v1.2.0

If the SQLite migration had failed catastrophically, James could return to pre-sqlite-migration in one command, with no manual reversal and no guesswork about what the project looked like before.

Blame Is Context, Not Accusation

Despite its name, git blame is a context tool. It answers: "Who wrote this line, when, and as part of what change?" James used it to understand a mysterious constant in the shipping module:

# Find the context for a confusing line
git blame src/shipping.py -L 42,42
d4e5f6g (Emma 2024-03-15 14:23:01 -0500 42) FREE_SHIPPING_THRESHOLD = 75.0  # PM-2024-019

Now James knows: Emma set this, on March 15, for product requirement PM-2024-019.

Without blame, that 75.0 would be a magic number. Nobody would know where it came from or whether it was safe to change. With blame, the full context is one command away.


Commit Discipline

The power of git-as-memory depends entirely on commit quality. James's wip commits were not just lazy; they were destroying information. Every time he bundled three unrelated changes into one commit with no message, he was erasing the decisions that produced those changes. Emma called this "voluntary amnesia."

Reading Git Commands for the Concept

The sections below show many git commands (git add, git commit, git log, git tag). Focus on what each command achieves (save a change, describe why, search history, mark a stable version) rather than memorizing the syntax. You will practice these commands yourself when you set up version control in hands-on chapters.

Atomic Commits: One Logical Change

Each commit should contain exactly one logical change. Emma gave James a simple test: if you have to use "and" to describe it, split it:

# BAD: Multiple unrelated changes in one commit
git add .
git commit -m "fix discount bug and update shipping and add tests"

# GOOD: Three separate atomic commits
git add src/discount.py tests/test_discount.py
git commit -m "fix(orders): correct discount calculation - multiply by (1 - rate)

apply_discount() returned price * rate instead of price * (1 - rate),
giving 85% discount instead of 15%. Caught by TDG specification test:
assert apply_discount(order, 0.15).total == 85.0

Impact: $12,000 loss over weekend. Post-mortem: PM-2025-003"

git add src/shipping.py
git commit -m "feat(shipping): raise free shipping threshold from 50 to 75

Product team decision: $50 threshold was losing margin on small orders.
Analytics showed 68% of orders between $50-$75 added items to qualify.
New threshold reduces free shipping orders by 31%.

Ref: PRODUCT-2025-047"

git add tests/test_shipping.py
git commit -m "test(shipping): add TDG specs for international surcharge

Five specification tests covering domestic, international, free shipping
threshold, and boundary conditions. Written before AI implementation
per TDG workflow (Axiom VII)."
Reading git commands for the pattern, not the syntax

The git commands in this section (git add, git commit -m, git log) are tools you will use hands-on in later chapters. For now, focus on what information each commit records (the prefix type, the scope, the "why"), not the command syntax.

Conventional Commits: Structured Prefixes

After a week of writing atomic commits, James noticed a new problem: his messages were descriptive but unscannable. Reading twenty commit messages to find "the one where I changed the shipping logic" took too long. Emma introduced him to conventional commits: a structured prefix system that makes history scannable at a glance:

PrefixMeaningExample
feat:New featurefeat(shipping): add international surcharge calculation
fix:Bug fixfix(orders): correct discount calculation
refactor:Code restructure (no behavior change)refactor(orders): extract discount logic to module
test:Adding/fixing teststest(shipping): add TDG specs for free shipping threshold
More commit types (expand)
PrefixMeaningExample
docs:Documentationdocs(api): document order endpoints
chore:Maintenancechore(deps): update fastapi to 0.109.0
perf:Performance improvementperf(shipping): replace O(n^2) rate lookup with dict
ci:CI/CD changesci(github): add Python 3.12 to test matrix

The format: type(scope): description

# James's order management project - scannable history
git log --oneline

# a1b2c3d feat(shipping): add international surcharge calculation
# b2c3d4e fix(orders): correct discount calculation - multiply by (1 - rate)
# c3d4e5f test(orders): add TDG specs for discount edge cases
# d4e5f6g refactor(orders): extract discount logic to module
# e5f6g7h perf(shipping): replace O(n^2) rate lookup with dict
# f6g7h8i docs(orders): document discount business rules

At a glance, James could see: a new shipping feature, the discount bug fix, TDG tests, a refactor, the performance fix for the O(n^2) shipping function from Axiom VII's Green Bar Illusion, and documentation. This is scannable memory: the table of contents for his project's story.

The WHY Rule

The most important discipline, and the one that would have saved James's post-mortem, is this: commit messages explain WHY, not WHAT.

The diff already shows what changed. The message must explain what the diff cannot:

# BAD: Describes WHAT (redundant with the diff)
git commit -m "change FREE_SHIPPING_THRESHOLD from 50 to 75"

# GOOD: Explains WHY (context the diff cannot provide)
git commit -m "feat(shipping): raise free shipping threshold from 50 to 75

Product team decision: $50 threshold was losing margin on small orders.
Analytics showed 68% of orders between $50-$75 added items to qualify.
New threshold reduces free shipping orders by 31%.

Ref: PRODUCT-2025-047"

Six months from now, when someone asks "why is the threshold 75 and not 50?", the commit message answers immediately. It was a product decision backed by analytics, not an arbitrary choice. No Slack archaeology required. This is what James's post-mortem was missing: the why behind every change.

Reading Checkpoint

This is a natural stopping point. If you need a break, bookmark this spot and return when you are ready. Everything above covers the core concept; everything below applies it through exercises and practice.


Git and AI: The Collaboration Protocol

When James started using Claude Code for his order management project, he discovered that disciplined git history served a second purpose: it made the AI smarter. The AI could read his commit messages to understand not just the current code, but the decisions that shaped it.

AI Can Read Git History for Context

AI tools can examine your project's history to understand decisions:

# AI reads recent changes to understand current direction
git log --oneline -20

# AI reads the full context of why a change was made
git show abc123
Advanced: filtering git log by file (expand)
# AI reads the discount module's evolution
git log --follow --oneline src/discount.py
$ git log --follow --oneline src/discount.py
d4e5f6g fix(orders): correct discount calculation - multiply by (1 - rate)
c3d4e5f refactor(orders): extract discount logic to module
a1b2c3d feat(orders): implement discount calculation per TDG specs

The --follow flag tracks a file even if it was renamed, giving the AI a complete picture of how a single module evolved over time.

When James's CLAUDE.md said "always use TDG for business logic," the git history explained why: the $12,000 discount disaster. The AI could provide better suggestions because it understood the reasoning behind the rule, not just the rule itself.

AI Commits Should Be Clearly Labeled

When the AI generates code that gets committed, James learned to label it clearly:

# Clear attribution in commit message
git commit -m "feat(orders): implement discount calculation per TDG specs

Passes all 5 specification tests in test_discount.py.
Handles 0%, 15%, and 100% discount edge cases.

Co-Authored-By: Claude <noreply@anthropic.com>"

This matters for three reasons:

  1. Accountability: Code review knows which commits need extra scrutiny
  2. Learning: You can filter git log --author="Claude" to see AI contribution patterns
  3. Audit: In regulated environments, AI-generated code may require additional review

Git Diff as AI Code Review Input

The most natural input for AI code review is a git diff. James started sending diffs to the AI instead of entire files. The diff showed exactly what changed, with no noise:

# Review staged changes before committing
git diff --staged

# Review a feature branch against main
git diff main...feature/order-discounts

# Ask AI to review the diff
git diff main...feature/order-discounts | pbcopy
# Paste into AI: "Review this diff for correctness and edge cases"
$ git diff --staged
diff --git a/src/shipping.py b/src/shipping.py
index 3a1b2c3..d4e5f6g 100644
--- a/src/shipping.py
+++ b/src/shipping.py
@@ -12,6 +12,9 @@ def calculate_shipping(weight_kg: float, destination: str) -> float:
+ if destination != "US":
+ base_rate += INTERNATIONAL_SURCHARGE
+ return base_rate

Branches for AI Experiments

When James asked the AI to try something experimental (like rewriting his shipping calculator with a different algorithm), Emma insisted he always use a branch:

# Create a safe sandbox for AI experimentation
git checkout -b ai/experiment-new-shipping-algorithm

# AI generates a new shipping calculator...
# You run TDG tests against it...

# If tests pass: merge to main
git checkout main && git merge ai/experiment-new-shipping-algorithm

# If tests fail: discard without risk
git checkout main
git branch -D ai/experiment-new-shipping-algorithm

The branch prefix ai/ makes it immediately clear which branches contain AI-generated experiments. James could let the AI try radical approaches (a completely different discount algorithm, a table-driven shipping calculator) without any risk to the working code on main.

The Agentic Development Workflow

Emma showed James the standard workflow she used for all AI-assisted development on the order management project:

main (stable, protected)

├── feature/order-discounts (human + AI work)
│ ├── commit: test(orders): add TDG specs for discount logic (human)
│ ├── commit: feat(orders): implement discount calculation (AI, reviewed)
│ ├── commit: test(orders): add boundary case for 100% discount (human)
│ └── commit: docs(orders): document discount business rules (AI, reviewed)

└── Pull Request → Human reviews all AI commits → Merge to main

Key rules:

  • main is always stable: Never commit directly to main
  • Feature branches isolate work: Both human and AI changes go here
  • Pull requests require review: Especially for AI-generated code
  • Each commit is atomic: One logical change, clearly attributed

"Notice the pattern," Emma told James. "The human writes the tests, the specification. The AI writes the implementation and documentation. The commit history shows exactly who decided what. This is TDG encoded into your git workflow."


Anti-Patterns: How Git Memory Fails

Open any project that has been running for more than a year and run git log --oneline | head -20. If you see wip, fix, stuff, update, changes, you are looking at a project with amnesia. The commit log reads like a list of words rather than a story. git blame on any line returns a message that tells you nothing.

A developer left six months ago and took the entire architectural context with them, because none of it was written in commits. git bisect is useless because every commit changes forty files for three unrelated reasons. The team lead says "we tried caching last year" but nobody can find the experiment, nobody remembers why it failed, and a new developer spends two weeks rediscovering the same dead end.

This history is not missing information by accident. It is missing information because each developer chose the two-second shortcut of git commit -m "wip", and a thousand two-second shortcuts became a project that cannot explain itself.

These specific patterns destroy git's value as memory. Recognize and avoid them:

Anti-PatternWhy It FailsBetter Approach
Giant commits ("fix everything")Impossible to understand, revert, or bisectOne logical change per commit
Empty messages ("wip", "stuff", "asdf")Zero memory value; future you learns nothingExplain WHY with conventional prefix
Committing secrets/credentialsSecurity breach waiting to happenUse .gitignore and environment variables
Force-pushing shared branchesRewrites other people's historyOnly force-push your own unshared branches
Not using branches for experimentsExperiments pollute main historyBranch first, merge only if successful
Committing generated filesNoise in diffs, merge conflicts.gitignore build outputs, node_modules/, etc.
Squashing all commits on mergeDestroys the detailed decision historyPreserve atomic commits; only squash true "wip"
Never tagging releasesNo stable milestones to reference or rollback toTag every release and significant milestone

The "Giant Commit" Problem in Detail

James's worst commit: "weekly update", 47 files changed, 2,391 insertions, 856 deletions. He could not revert part of it, bisect through it, or explain any individual change at the post-mortem. Emma showed him the alternative: 12 atomic commits, each a discrete memory that can be individually understood, reverted, or referenced:

feat(auth): add OAuth2 PKCE flow for mobile clients
fix(db): resolve connection leak under high concurrency
refactor(api): extract validation into middleware layer
test(auth): add PKCE challenge verification tests
docs(deploy): update Kubernetes manifest for v2.3
perf(search): add trigram index for fuzzy name matching
...

Git as Time Machine

Still Reading for the Idea, Not the Syntax

This section shows commands like git bisect, git revert, and git cherry-pick. The important thing is what they let you do: find when something broke, safely undo a change, selectively apply a fix. Understanding the capability matters more than the command names right now.

Git does not just record history; it lets you travel through it. Two weeks after adopting commit discipline, James experienced its first real payoff: his shipping calculator started returning wrong rates for international orders. Instead of reading through code to find the bug, Emma showed him how to let git find it.

Bisect: Finding When Things Broke

git bisect performs a binary search through history to find the exact commit that introduced a bug:

# Start bisecting
git bisect start

# Mark current state as bad (the bug exists now)
git bisect bad

# Mark a known-good state (the bug did not exist here)
git bisect good v1.2.0

# Git checks out a middle commit. You test it:
python -m pytest tests/test_auth.py
# Tell git the result:
git bisect good # or: git bisect bad

# Repeat until git finds the exact commit:
# "abc123 is the first bad commit"
# feat(auth): add session timeout handling

# Clean up
git bisect reset
Full bisect output (expand)
$ git bisect start
$ git bisect bad
$ git bisect good v1.2.0
Bisecting: 25 revisions left to test after this (roughly 5 steps)
[f1a2b3c] refactor(orders): extract discount logic to module

$ python -m pytest tests/test_auth.py
PASSED
$ git bisect good
Bisecting: 12 revisions left to test after this (roughly 4 steps)

... (repeating good/bad for each step) ...

$ git bisect bad
abc123def is the first bad commit
commit abc123def
Author: James <james@company.com>
Date: Thu Mar 21 16:42:00 2024 -0500

feat(auth): add session timeout handling

$ git bisect reset
Previous HEAD position was abc123d... feat(auth): add session timeout handling
Switched to branch 'main'

With James's new atomic commits, bisect pinpointed the problem in six steps across fifty commits. The culprit was a commit where the AI had regenerated the international surcharge logic and introduced a rounding error. Because the commit was atomic (one logical change), James knew exactly which code to fix. If he had still been making giant "weekly update" commits, bisect would have been useless: finding the bad commit would still leave him sifting through hundreds of unrelated changes.

Revert: Safe Undo

git revert creates a new commit that undoes a previous commit, without rewriting history:

# Safely undo a specific commit
git revert abc123

# Revert creates a NEW commit:
# "Revert 'feat(auth): add session timeout handling'"
# This preserves the full story: we tried it, it broke things, we reverted it.
$ git revert abc123
[main g7h8i9j] Revert "feat(auth): add session timeout handling"
2 files changed, 3 insertions(+), 47 deletions(-)

Unlike git reset, revert is safe for shared branches because it adds to history rather than erasing it. The story is preserved: we tried it, it broke things, we reverted it. Future James, or a new team member, can read the full narrative.

Advanced: cherry-pick (expand)

Cherry-Pick: Selective Application

git cherry-pick applies a specific commit from one branch to another. When James found a critical bug fix on his experiment branch that needed to go to main immediately, Emma showed him cherry-pick:

# A critical fix was made on a feature branch
# Apply just that fix to main without merging everything
git checkout main
git cherry-pick def456

# The fix is now on main, with full attribution preserved
$ git checkout main
Switched to branch 'main'
$ git cherry-pick def456
[main j1k2l3m] fix(shipping): correct rounding for international orders
Date: Fri Mar 22 09:15:00 2024 -0500
1 file changed, 3 insertions(+), 2 deletions(-)

Viewing Past States

After learning these commands, James realized he could have answered his team lead's post-mortem question, if his commits had been disciplined. With proper git history, recovering any past version is one command:

# See a file as it was at any point in history
git show v1.0.0:src/discount.py

# Compare the buggy version to the fixed version
git diff abc123 def456 -- src/discount.py

# See all files at a past state (read-only exploration)
git stash # save current work
git checkout v1.0.0
# explore the entire project as it was at the release...
git checkout main
git stash pop # restore current work

If James had made an atomic commit for the original apply_discount() implementation, he could have shown the team lead exactly what the buggy code looked like, when it was introduced, and which TDG test caught the error, all from a single git show command.


Try With AI

Prompt 1: Transform Bad Version Labels into Good Memory

I have five versions of a group project report, saved with these labels:

1. "updated stuff"
2. "fix"
3. "wip"
4. "changes"
5. "done"

Here is what actually changed in each version:

1. Rewrote the introduction to focus on climate data instead of general pollution
2. Fixed a wrong statistic: the original said 40% but the real number is 28%
3. Added a new section comparing two solutions but it is not finished yet
4. Renamed all references from "global warming" to "climate change" for consistency
5. Removed a paragraph that contained an unverified claim from an unreliable source

For each version, rewrite the label as a meaningful description that explains
WHAT changed and WHY. Then explain: what would a new team member learn from
your rewritten labels that the originals completely fail to communicate?

What you're learning: The difference between saving copies and recording decisions. Notice how each rewritten label captures reasoning that would otherwise be lost, the same reasoning James could not reconstruct at his post-mortem. The original labels treat version history as a backup tool; the rewrites treat it as institutional memory. This is the core of Axiom VIII: the why behind every change is the most valuable information you can record.

Prompt 2: Design a Change Log for a Group Project

Four students are collaborating on a semester-long research project.
They work on different sections, sometimes edit each other's work,
and need to submit a final version in 8 weeks.

Design a change tracking system that:
- Makes it clear WHO changed WHAT and WHEN
- Keeps a "known good" version that is always complete and correct
- Gives each student a safe space to try changes without breaking
the main document
- Requires someone to review changes before they become part of
the main version
- Lets the team undo a specific change if it turns out to be wrong

Describe the system using everyday language: folders, labels,
review steps, and rules. Then explain: what goes wrong if the team
skips the review step? What goes wrong if everyone edits the
main document directly?

What you're learning: Version control as a collaboration protocol. The system you design (with a protected main version, separate workspaces, and review gates) mirrors exactly how professional teams use git. The review step is the trust boundary: work freely in your own space, but changes must be checked before they reach the shared version. This is the workflow Emma taught James: isolate work, review before merging, keep the main version always stable.

Prompt 3: Investigate a Mystery Using History

You are the editor of your school newspaper. Last month's issue was great.
This month's issue has three problems: a headline with a typo, a photo
with the wrong caption, and a paragraph that contradicts an earlier article.

You have 20 versions of the document saved over the past month, each with
a label describing what changed and why.

Walk me through how you would use the version history to:
1. Find WHEN each problem was introduced (which version?)
2. Find WHO made each change and WHY they made it
3. Decide for each problem: should you undo the change entirely,
or fix it forward with a new edit?
4. Prevent these problems from happening in next month's issue

Then explain: why is this investigation IMPOSSIBLE if all 20 versions
were labeled "update", "edits", "final", "final2", "done"?

What you're learning: Version history as an investigative tool, not just a storage tool. Searching through history to find when something broke only works when each version is a meaningful record. This is exactly how James found his shipping bug, tracing through well-labeled commits to find the one that introduced the error. Meaningless labels like "update" make the investigation impossible, just as James's wip commits made his post-mortem impossible.


PRIMM-AI+ Practice: Version Control is Memory

Predict [AI-FREE]

Close your AI assistant. You have 5 versions of a school essay saved as:

  1. essay.docx
  2. essay_final.docx
  3. essay_FINAL_v2.docx
  4. essay_REAL_final.docx
  5. essay_submitted.docx

Your teacher asks: "What changed between version 2 and version 4? And WHY did you make those changes?"

Predict:

  • Can you answer the teacher's question from the filenames alone?
  • What information is missing that would let you answer?
  • How many of these files might actually be identical?

Write your answers. Rate your confidence from 1 to 5.

Run

Ask your AI assistant: "What is wrong with naming files 'final_v2_REAL_final'? What information should each version of a document record to be useful as a history?"

Compare. Did the AI identify the same missing information you did?

Answer Key: What to Look For

The AI should identify several problems with "final_v2_REAL_final" naming:

  • No record of WHAT changed: The filenames say "final" and "v2" but nothing about what was actually different between versions. Was it a new introduction? A restructured argument? Fixed citations?
  • No record of WHY it changed: Did the teacher give feedback? Did you find a better source? Did you change your thesis? The filenames carry zero reasoning.
  • Possible duplicates: Some "versions" might be identical, and you cannot tell from the names alone. You might have 5 files but only 3 actual changes.
  • No ordering guarantee: Is "FINAL_v2" newer than "REAL_final"? The naming gives no reliable sequence.
  • Useful history needs: Each version should record what changed, why it changed, and when. Think of it like a journal, not just a stack of copies.

If your prediction matched on at least 3 of these points, your instinct for what makes history useful is strong. If the AI caught something you missed, note which gap in your thinking it reveals.

Investigate

Write in your own words why recording WHAT changed and WHY for each version is more valuable than just saving copies with different names. What can you do with a meaningful version history that you cannot do with a pile of files named "final_v2"?

Now connect this to James's story. His post-mortem failed for exactly the same reason your essay filenames fail. The history existed (he had three commits; you have five files) but it told him nothing. His team lead asked "What was the original bug?" and James could not answer, just as your teacher asks "What changed between version 2 and version 4?" and you cannot answer. In both cases, the problem is not missing saves; it is missing reasoning. Every version without a "why" is a memory with amnesia.

Apply the Error Taxonomy: every commit message is a specification of what changed and why. "wip" and "finalv2" are specification errors: they look like specifications (a commit message _should describe the change) but they specify nothing. When the teacher asks "what changed between v2 and v4?", the missing specification makes the question unanswerable. The history exists, but the specifications are empty. Compare this to a test that says assert result == True without stating what result is expected. The form of a specification is there, but the content is missing.

Now go deeper: imagine your teacher not only asks what changed, but asks you to undo version 3 because it introduced a mistake. With "essay_FINAL_v2.docx" naming, can you do that safely? What information would you need in each version's record to make a selective undo possible? Connect this to James's post-mortem: his team wanted to undo one specific change, but "fix stuff" told them nothing about what that change contained.

Modify

Rewrite these 5 file versions as meaningful save points. For each one, write one sentence explaining WHY the change was made:

  1. essay.docx → Version 1: ___
  2. essay_final.docx → Version 2: ___
  3. essay_FINAL_v2.docx → Version 3: ___
  4. essay_REAL_final.docx → Version 4: ___
  5. essay_submitted.docx → Version 5: ___

For example: "Version 2: Rewrote introduction to use a personal anecdote because teacher said the opening was too generic."

Make [Mastery Gate]

Think of a project you have worked on (an essay, a presentation, a design, a plan). Write a version history with 4-5 entries. Each entry must include:

  • What changed (be specific: "rewrote the conclusion" not "made edits")
  • Why it changed (what motivated the change: feedback? new idea? mistake found?)
  • The date (approximate is fine)

This version history is your mastery gate. Someone reading it should understand the full evolution of your project, not just the final result, but the journey that produced it and the reasoning behind each change.


The Permanent Record

A month into his new commit discipline, James made a different kind of mistake. He was setting up the database connection for his order management system and committed the file with the connection string hardcoded: DATABASE_URL=postgresql://admin:s3cretPass@prod-db.company.com/orders. He realized immediately, deleted the line, and committed the fix. Problem solved, or so he thought.

"The password is still in your history," Emma told him. She ran one command:

# This finds secrets in ALL of history, not just current files
git log -p --all -S 'DATABASE_URL'
Full git log output showing the leaked secret (expand)
commit c3d4e5f6
Author: James <james@company.com>
Date: Wed Mar 19 11:30:00 2024 -0500

feat(db): add database connection setup

diff --git a/src/config.py b/src/config.py
+DATABASE_URL=postgresql://admin:s3cretPass@prod-db.company.com/orders

The original commit appeared, with the full connection string visible. Deleting the file in a later commit does not erase it from history. Git's perfect memory, the same feature that makes it invaluable for recording decisions, makes it dangerous for secrets. Once committed, a credential exists in every clone of the repository, forever.

The Permanent Record is the flip side of Axiom VIII: git remembers everything, including things you wish it would forget. The same mechanism that would have preserved James's buggy apply_discount() for the post-mortem also preserves every accidentally committed password, API key, and credential.

Prevention is the only reliable defense:

# Create .gitignore BEFORE your first commit
echo ".env" >> .gitignore
echo "*.pem" >> .gitignore
echo "credentials.json" >> .gitignore

# Verify nothing sensitive is staged
git diff --staged --name-only
# Check: do any of these files contain secrets?

# Use environment variables instead
export DATABASE_URL="postgresql://user:pass@host/db"
# Reference in code: os.environ["DATABASE_URL"]

If you accidentally commit a secret, as James did:

  1. Rotate the credential immediately (assume it is compromised)
  2. Remove from current files and commit the removal
  3. For sensitive repositories, use git filter-branch or BFG Repo-Cleaner to purge from history
  4. Force-push the cleaned history (this is the one valid use of force-push)

James had to rotate the database password that afternoon. An hour of work that a .gitignore file would have prevented entirely. He never committed a credential again.



James scrolled through his refactored commit history. Each message explained a decision, not just a change. "It's institutional memory," he said quietly. "In operations, we had runbooks. Every time something went wrong, someone wrote down what happened and why. This is the same thing, just for code."

"Better, actually," Emma said. "Runbooks get stale. Git history is tied to the exact state of the code at the moment the decision was made. You can't get out of sync."

"The WHY rule is the part that clicks for me. The diff shows what changed. The message explains the reasoning. Six months from now, that's the only context I'll have."

Emma nodded. "And the AI reads those messages too. Good commit history gives it better context for future changes. Bad history gives it nothing." She hesitated. "I should admit, I'm not great at knowing when a commit is too big. I'll say 'keep it atomic' and then catch myself bundling three changes into one because I'm in a rush. It's a discipline problem, not a knowledge problem."

"Branches as experiments, though," James said. "That I get. Try something, throw it away if it doesn't work, no damage to the main line."

"Just watch the Permanent Record. Git remembers everything, including secrets you commit by accident. A .gitignore before your first commit prevents the one mistake that's genuinely hard to undo."

James closed his terminal. "So now I've got tests, types, relational data, and git tracking every decision. But I'm running all these checks by hand. Formatting, linting, type checking, tests..."

"That's the next axiom," Emma said. "Verification as a pipeline. Every check, automated, on every push."