Plans as Version Control Artifacts
TL;DR: In plan-oriented programming, the plan is the primary artifact--reviewed, debated, and validated by humans, AI agents, designers, and PMs before any code is written. By storing plans as Jujutsu change descriptions (note: jj is a VCS, like git), they become permanent, addressable nodes in version history. A thin shim makes them co-editable as markdown files in your editor.
The Paradigm: Plan-Oriented Programming
Most software workflows treat code as the artifact and plans as ephemeral scaffolding. A plan lives in a Google Doc or a Notion page, gets discussed in Slack, maybe referenced in a PR description, and is forgotten the moment the code lands.
Plan-oriented programming inverts this. The plan is the artifact. Code is its expression.
The workflow:
- Draft — A human or AI agent writes an implementation plan: background, constraints, rationale, rejected alternatives, concrete steps.
- Cross-check — A second prompt, or AI agent, or human reviews the plan for correctness, feasibility, and completeness. Designers and PMs weigh in on intent and scope.
- Iterate — The plan is iterated on. Steps are refined. Edge cases are surfaced. This happens before a single line of code exists.
- Implement — Only now does code get written, guided by the plan. The plan is the specification, the context, and the historical record.
- Archive — The plan remains in version history, permanently linked to the code it produced.
This is not waterfall. Plans are living documents that evolve during implementation. But the key shift is that planning is a first-class phase, with its own artifact, not a mental prelude to typing code.
Why Plans Need a Home in the VCS
Plans stored outside the VCS (wikis, docs, chat) suffer from three problems:
- Drift: The plan says one thing, the code does another, and no one updates either.
- Disconnection: Six months later,
git blameshows "refactor auth middleware" and the reasoning is gone. - Inaccessibility: The AI agent writing code can't read your Google Doc. The new team member can't find the Slack thread.
When the plan lives in the VCS--as a change description--it travels with the code. It's versioned. It's queryable--tools can index every line of code against the plan it came from (i.e. git blame). It's right where you need it.
The jj Insight: Stable Change IDs
This workflow requires one thing that git cannot provide: stable references to changes.
Git commit hashes are content-addressable. Rebase, amend, or cherry-pick, and the hash changes. Any reference to it--in a comment, a doc, another commit message--becomes a dead link.
jj (Jujutsu) change IDs are assigned at creation and never change, regardless of rebases, amends, or rewrites. This means:
- A change ID is a permanent address for a plan.
- Code comments can link back to plans with
jj:CHANGE_ID. - Anyone can run
jj show CHANGE_IDto retrieve full context--forever. - Plans can reference each other by change ID, forming a navigable knowledge graph.
Plans aren't metadata about changes. Plans are changes.
The Method
1. A plan is an empty change with a rich description
jj new -r base
jj describe # Write the plan — background, rationale, steps
jj new # Start implementing (plan is preserved as the parent change)
The plan change has no file diff. Its entire content is the description--a markdown document with background, constraints, alternatives considered, and step-by-step approach. Implementation changes are descendants of the plan, forming a tree rooted in intent.
2. Plans are reviewed before code exists
The plan change is the review surface. Collaborators read the description and respond:
- A designer validates that the UX implications are understood.
- A PM confirms scope and priority alignment.
- An AI agent cross-checks feasibility, identifies missing edge cases, or proposes alternative approaches.
- A peer engineer challenges assumptions and checks for architectural fit.
This review happens on the plan change itself--the description is edited, refined, and iterated on until the group has confidence. Only then does implementation begin.
3. Code references plans by change ID
// We bypass the cache here for consistency during concurrent writes.
// Context: jj:kpqxywon
This is a permanent, resolvable link. jj show kpqxywon retrieves the full plan--the why behind the code. The comment is terse; the plan is deep. Anyone doing code archaeology can follow the link and recover full context.
4. Plans split naturally across PRs
When a plan grows beyond one PR, create new plan changes referencing the original:
jj new -r base
jj describe # Temporary placeholder: "Phase 2 — API key support (continues jj:kpqxywon)"
Then jj rebase implementation changes onto the new plan. The lineage is preserved through change ID references. Each phase can be reviewed, implemented, and landed independently while maintaining a navigable thread back to the original intent.
5. A .jj-plans/ directory makes plans co-editable
A shim intercepts jj commands and maintains a .jj-plans/ directory:
.jj-plans/
current.md → symlink to active change's plan
.stack → one-line summary of the full stack
00-kpqxywon.md — closest to trunk/base
01-mtzrlpvq.md
02-ykvsnxrl.md — tip
You and an AI agent both edit these markdown files in the editor — no jj describe clobbering, no modal editor sessions. The shim flushes edits to jj descriptions automatically. jj status shows the stack at a glance:
Plan stack (.jj-plans/; ✓=done *=current ~=has changes [blank]=not started):
✓ 00-kpqxywon :: Refactor auth middleware
~ 01-mtzrlpvq :: Extract auth module
* 02-ykvsnxrl :: Implement JWT strategy
03-abcdefgh :: Add API key support
The AI Collaboration Loop
An AI agent generating code needs intent, not just a task. "Implement JWT auth" is a task. A plan change contains the background (why JWT, not session tokens), the constraints (must support RS256 and EdDSA), the rejected alternatives (API keys were considered but don't support rotation), and the step-by-step approach (extract middleware first, then strategy pattern, then wire up).
The collaboration loop:
Human writes draft plan
→ AI cross-checks: "Step 3 has a race condition if..."
→ Human revises plan
→ PM confirms scope: "Phase 2 can wait for Q4"
→ AI implements, guided by the finalized plan
→ Plan is marked done, lives in history forever
→ Code links back via jj:CHANGE_ID
When the agent reads .jj-plans/current.md before writing code, it has the full decision record. When it's done, the plan--annotated with completion status--becomes the permanent historical record. The code links back to it. The loop is closed:
Plan (jj description) → Code (references jj:CHANGE_ID) → Archaeology (jj show → full plan)
No context is lost. No documentation rots. The VCS is the documentation.
Key Properties
| Property | Mechanism |
|---|---|
| Plans are reviewed before code | An empty change is the review surface |
| Plans survive rebase/amend | jj change IDs are stable |
| Plans are permanently addressable | jj show CHANGE_ID from any code comment |
| Plans are co-editable (human + AI) | .jj-plans/ shim syncs markdown files ↔ descriptions |
| Plans co-exist with code | The commit description is the plan, the commit is the execution of the plan |
| Plans split across PRs | New plan changes reference originals by change ID |
| Plans have status tracking | status: ✅ in description |
| Stack is always visible | jj status appends the plan stack summary |
Getting Started
- Use jj (Jujutsu) as your VCS.
- Install the jj-plan shim in your
$PATH(see below). - Add
.jj-planto your global gitignore. - In a repo:
mkdir .jj-planto activate. - Set a stack boundary:
jj plan stack(or rely ontrunk()with a remote). - Start planning:
jj plan new, write your plan in.jj-plan/current.md, review with your team and AI, then implement.
The jj-plan Shim Source Code
If you name the following jj-plan.zsh script as jj and put it in your $PATH, then it will wrap the "real" jj command and intercept it when needed in order to support the workflow described above.