# dev/ 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. ## Layout ``` dev/ ├── QUICKSTART.md # one-screen cheat sheet ├── README.md # this file ├── plans/ │ ├── -/ # per-feature spec + review trail │ │ ├── prd.md # Product Requirements (EARS acceptance criteria) │ │ ├── design.md # Technical blueprint (optional; skip for trivial features) │ │ ├── tasks.md # Task list with _Requirements:_ / _Design:_ tags │ │ ├── codex-sessions.md # Append-only log of codex session UUIDs per stage │ │ ├── copilot-sessions.md # Same, for GitHub Copilot CLI (if used) │ │ ├── gemini-sessions.md # Same, for Google Gemini CLI (if used) │ │ └── kiro-sessions.md # Same, for Kiro CLI (if used) │ └── archive/ # Completed/abandoned plans (keeps active list scannable) ├── notes/ # Freeform working notes (ad-hoc) ├── research/ # Research artifacts (protocol traces, reference code digs, etc.) └── 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. - `` = 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/-`. 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. ## Skill workflow The spec-driven skills chain cleanly: 1. `/create-prd` — asks clarifying questions, drafts `prd.md`, runs a codex peer-review loop. 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 ` 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/`. ## Traceability tags | Location | Format | Example | |----------|--------|---------| | PRD acceptance criteria | `R.` | `R2.1` | | Design section | `D§` | `D§4.2` | | Task | `T.` | `T3.1` | | Task list (markdown) | `_Requirements: R1.1, R1.2_` / `_Design: D§2_` | appended to task line | | Source code comment | `// spec:/` | `// spec:user-auth/R2.1+D§6` | Searching: ```bash 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 `-` 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 "..." ...`. - Conventional commit prefixes: `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, `dev:` (for `dev/`-only changes). - Commit messages reference the parent task and PRD: `Task from dev/plans/-/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 ` 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 `/**` edits. Planners typically work on a single long-lived plans commit or a short stack (`jj new -m "dev: "`). - **Feature branches** — one per PRD, bookmark named `feature/-`, rooted on `main` or a shared `dev:` infrastructure commit. Contains `/**` edits plus the specific `dev/plans/-/` 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. Create a workspace per parallel agent: ``` # From the main clone: jj workspace add --name planner ../-planner jj workspace add --name impl ../-impl # Each agent's Claude Code (or Codex/Copilot/etc.) session runs in its own directory: cd ../-planner # planner operates here cd ../-impl # implementer operates here ``` `jj workspace list` enumerates; `jj workspace forget ` 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/- ``` 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. #### Planned: JJ_AGENT_FEATURE A future `JJ_AGENT_FEATURE` env var will give commits and end-of-session bookmarks a human-readable feature name instead of the raw session UUID: ```bash export JJ_AGENT_FEATURE=auth # example # commits become: wip(auth:abc12345): file1, file2 # 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:)` messages are the identifiers to use. ### Pre-flight check (every agent, every session start) If not using a dedicated workspace, run before any file write: ``` jj workspace list # confirm expected workspace jj status jj log -r '@|@-' --no-graph -T 'change_id.short() ++ " " ++ bookmarks ++ " [" ++ description.first_line() ++ "]\n"' # 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 ` or `jj edit ` 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/-` 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: ``` jj squash --from --into feature/- dev/plans/- ``` Only the specific plan dir moves; the rest of the plans commit stays intact. The implementer, operating on `feature/-`, 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/-, after editing the plan dir in-place: jj squash --from @ --into dev/plans/- ``` Keeps the corrected plan on the shared plans commit so other feature branches eventually rebase onto the fix. ### Shared-infrastructure edits (README.md, skills, tools) 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 `), 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 --into `. 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.