Working artifacts for the spec-driven workflow used in this repository. Tracked in version control (not excluded) so the history of how a feature was planned, designed, and executed travels with the code.
**Upstream boundary** (forks only): if this repo is a fork with an upstream you shouldn't leak `dev/` content to, see "Upstream boundary" below. For greenfield projects, ignore that section.
└── tools/ # Local developer utilities (hooks, scripts)
├── install-hooks.sh # Symlinks hook scripts into .git/hooks/
└── pre-push-guard.sh # Optional: refuses pushes touching dev/ to a configured upstream
```
## Plan directory convention
Plan directories carry a chronological `YYWWD-` prefix so they sort by creation date and short slugs don't collide over time:
-`YY` = two-digit ISO year, `WW` = two-digit ISO week, `D` = ISO day-of-week (`1`=Mon … `7`=Sun). Compute with `date +%y%V%u` at the moment the directory is created. Don't backdate.
-`<slug>` = kebab-case, 2–4 words, describing the feature. No spaces, no uppercase.
Example: on 2026-04-19 (`date +%y%V%u` = `26167`), a plan for pane focus sync lives at `dev/plans/26167-pane-focus-sync/`.
Feature branches mirror the plan directory: `feature/<YYWWD>-<feature-slug>`. That keeps `jj bookmark list` / `git branch -a` in the same chronological order as `ls dev/plans/`.
Ad-hoc plan directories that predate the spec-driven skills (monolithic `plan.md`) are fine — they just use the same dir prefix without the `prd.md` / `design.md` / `tasks.md` shape.
2.`/create-design` — produces `design.md` (skip for trivial features).
3.`/generate-tasks` — two-phase task decomposition into `tasks.md`.
4.`/process-task-list` — executes sub-tasks one at a time with user approval, runs codex code review at parent-task boundaries, commits via jj.
**Fresh session per stage.** Each review stage (PRD, design, tasks, each parent-task code review, pre-PR) starts its own clean codex session. Session UUIDs are logged in `codex-sessions.md` (and `copilot-sessions.md` / `gemini-sessions.md` / `kiro-sessions.md` if other reviewers are used), but they're **not resumed across stages** — this keeps each review's context uncluttered. See the `codex` skill's "Cross-stage continuation" section for the rationale.
**Pre-codex `planctl lint` gate (optional but recommended).** Before spawning a codex review at any stage, run `planctl lint <plan-dir>` to catch traceability defects the reviewer would otherwise flag — orphan refs, malformed tags, missing close-out files, EARS violations. It runs in single-digit milliseconds per plan and its classification codes (`orphan-requirement`, `tag-unclosed`, etc.) map to the categories prior codex rounds have surfaced. Installed via `go install forgejo.zerova.net/sid/template-jj/cmd/planctl@latest`; spec at `dev/plans/26172-planctl/`.
rg 'spec:user-auth' # all code tied to one feature
rg 'spec:.*/R2\.1' # all code tied to a specific requirement across features
rg 'spec:.*/D§4' # all code tied to a specific design section
```
Note: the in-code `spec:` tag uses the **bare slug**, not the full `<YYWWD>-<feature-slug>` directory prefix. Collisions inside a single codebase are unlikely, and the shorter form is friendlier in code.
## Scope discipline
PRD and design are **authoritative**. No features, APIs, or schema changes that aren't traceable to the PRD or design. If an emerging need appears mid-execution, log it as a new sub-task with its own `_Requirements:_` tag rather than silently expanding scope. Useful-but-unrelated fixes become new sub-tasks; don't bundle them into an existing one.
## Commit convention
- This template uses **jj** (colocated with git). Commits are authored via `jj describe -m "..." -m "..." ...`.
- Commit messages reference the parent task and PRD: `Task <N.0> from dev/plans/<YYWWD>-<feature-slug>/prd.md`.
- Never `--no-verify`.
- No `Co-Authored-By` lines.
## Multi-agent workflow
Running multiple agents (Claude Code sessions, other LLM CLIs, or your own terminal) against the same repo in parallel is supported, but requires discipline — the working tree is shared, and one agent's `jj new <somewhere-else>` flips it underneath another agent mid-edit. Two layers of separation keep this sane.
### Separation by commit class
Two disjoint commit classes, never overlapping:
- **Plans commits** — description starts with `dev:`; changes confined to `dev/plans/**`, `dev/notes/**`, `dev/research/**`. A plans commit NEVER contains `<SOURCE_ROOT>/**` edits. Planners typically work on a single long-lived plans commit or a short stack (`jj new <plans-parent> -m "dev: <what I'm drafting>"`).
- **Feature branches** — one per PRD, bookmark named `feature/<YYWWD>-<feature-slug>`, rooted on `main` or a shared `dev:` infrastructure commit. Contains `<SOURCE_ROOT>/**` edits plus the specific `dev/plans/<YYWWD>-<feature-slug>/` dir the branch implements (moved in via `jj squash`). Implementers work here.
Plans commits and feature branches are **siblings**, not ancestor/descendant. The feature branch owns its own plan dir via `jj squash`; plans for other features stay on the plans commit so the planner can keep extending.
### Primary isolation: jj workspaces
`jj workspace` is the proper tool for concurrent parallel agents. Each workspace has its own working directory sharing the same `.jj/` object store; each has an independent `@` pointer, so one agent's branch switch never flips another's tree.
# Each agent's Claude Code (or Codex/Copilot/etc.) session runs in its own directory:
cd ../<PROJECT_NAME>-planner # planner operates here
cd ../<PROJECT_NAME>-impl # implementer operates here
```
`jj workspace list` enumerates; `jj workspace forget <name>` removes. Workspaces share commit history, so the planner's commits are immediately visible to the implementer without copy steps.
**Use workspaces whenever two agent sessions will run concurrently.** The rest of this section (env var conventions, pre-flight) is for the single-workspace degraded mode.
### Env var conventions (secondary — same-workspace)
When multiple agents must share one working directory, use env vars to self-identify and trigger pre-flight mismatch checks:
```
AGENT_ROLE=planner # or impl, reviewer, …
AGENT_BOOKMARK=feature/<YYWWD>-<feature-slug>
```
An agent's pre-flight compares `@`'s bookmark against `$AGENT_BOOKMARK`; mismatch triggers a prompt before any write. The spec-driven skills (`create-prd`, `create-design`, `generate-tasks`, `process-task-list`) respect this convention when set.
This is a convention, not a jj feature. `jj` itself reads `JJ_USER` / `JJ_EMAIL` / `JJ_OP_USERNAME` but has no "my bookmark" concept — the env var is a tripwire for the agent's own pre-flight logic. For real isolation, use workspaces; the env var is the fallback.
# session-end bookmark: feat/auth (instead of wip/claude-abc12345)
```
Not yet implemented — the daemon and hook script need changes to read and propagate it. When available, set it alongside `AGENT_BOOKMARK` in the same env block. Until then, the raw `wip(claude:<session_id>)` messages are the identifiers to use.
# If AGENT_BOOKMARK is set, verify @'s bookmark matches:
[ -n "$AGENT_BOOKMARK" ] && jj log -r @ -T bookmarks | grep -q "$AGENT_BOOKMARK" || echo "WARNING: working-tree bookmark does not match \$AGENT_BOOKMARK"
```
Interpret the output:
- **Drafting agent (planner):** if `@`'s description doesn't start with `dev:`, you're on a feature branch — writes to `dev/plans/**` land in the wrong commit. `jj new <plans-commit>` or `jj edit <plans-commit>` first; or ask the user which drafting context to use.
- **Implementing agent:** if `@`'s description starts with `dev:`, you're on a plans commit — code edits land in the wrong commit. `jj new feature/<YYWWD>-<feature-slug>` first.
- **Both:** re-run `jj status` periodically. The working tree can flip underneath you if another agent runs `jj new` / `jj edit` / `jj bookmark` in parallel. A write failure that looks strange ("file doesn't exist?") is usually a silent branch switch — re-check before assuming the file system broke.
### Handoff: planner → implementer
When the planner finishes drafting a feature's PRD/design/tasks and the implementer should start:
Only the specific plan dir moves; the rest of the plans commit stays intact. The implementer, operating on `feature/<YYWWD>-<feature-slug>`, now sees that plan dir in its commit's history.
### Handoff: implementer → planner (rare)
If the implementer discovers the plan needs a correction:
```
# On feature/<YYWWD>-<feature-slug>, after editing the plan dir in-place:
Files shared across every branch — `dev/README.md`, `dev/QUICKSTART.md`, `.claude/skills/**`, `dev/tools/**` — live on a designated infrastructure commit (often the initial `dev: add spec-driven workflow infrastructure` commit or its descendant). Edits to these files should land on that commit specifically (`jj edit <infra-commit>`), not in a feature branch or plans commit, so every consumer rebases onto the update cleanly. An infrastructure commit edit rewrites descendants; jj auto-rebases them on your next operation.
### Stranded work recovery
If a file write lands in the wrong commit (common when an agent missed a branch switch), recover with `jj squash --from <wrong-commit> --into <right-commit> <path>`. The path-restricted squash moves only the mis-landed files; the rest of both commits is untouched.
### Coordination points
- **Before starting a long session** (drafting a PRD, processing a task list), the agent announces its working commit in a one-line message so the user and other agents see what's active.
- **Before a branch switch** (`jj new`, `jj edit`, `jj bookmark --move`), the agent signals the user — the switch affects every agent's working tree.
- **Prefer in-place edits** on the current commit over `jj new` when possible — fewer working-tree flips for other agents to notice.
## Upstream boundary (forks only)
If this repository is a fork and `dev/` artifacts should never land in a PR against the upstream, set the environment variable `UPSTREAM_BLOCKED_PATTERN` to a substring that matches the upstream's remote URL (e.g. `upstream-org/upstream-repo`). Then install the pre-push guard:
```bash
./dev/tools/install-hooks.sh
```
The guard at `.git/hooks/pre-push` (symlinked from `dev/tools/pre-push-guard.sh`) checks the push target's remote URL against `UPSTREAM_BLOCKED_PATTERN` and refuses pushes whose diff touches any `dev/` path. Pushes to other remotes (your personal fork, your team's private mirror, etc.) are unaffected.
For greenfield projects with no upstream to protect against, leave `UPSTREAM_BLOCKED_PATTERN` unset — the guard becomes a no-op and you can skip installing it.