template-jj/dev
Sid 77e1ff19b6 feat(planctl): lint rules — tag-syntax, cross-reference, file-presence, EARS
* lint.go: Severity / Code (full set) / Diagnostic types migrated
  here from their temporary home in index.go. Code constants form the
  grep-stable public contract per design §4.
* checkTagSyntax (passthrough over idx.MalformedTags).
* checkCrossRef with four diagnostic classes (orphan-requirement,
  orphan-design, uncovered-requirement, uncovered-design), R2.8
  hasDesign gate, R2.9 infra-sentinel exemption, and sorted-id emission
  for stable uncovered-* order.
* checkCloseoutFiles gated on R3.4 fully-closed-out definition
  (at-least-one + all-checked); emits Line=0 diagnostics with empty
  Path for the emitter to substitute plan-dir.
* newMissingPRDResult: R3.1/R3.2 fatal short-circuit.
* checkEars with the five design §3.4 regex patterns, narrow
  `^\*\*[^*]+\*\*` bold-prefix exemption, and --no-ears R4.4 skip.
* sortDiagnostics uses sort.SliceStable with (Path, Line, Code,
  Message) tie-breakers so output is deterministic run-to-run per
  PRD §6 even when two diagnostics share the first three keys
  (missing-closeout-file Line=0 pair).
* Position extended with a Text field populated at extraction time
  so checkEars keeps its literal `(idx, skip)` signature without
  passing a second *ScanResult.
* lint_test.go: one subtest per checker plus sort stability
  regression.

Parent task 4.0 from dev/plans/26172-planctl/tasks.md.
Codex code-review session: 019db2b8-21c8-7d61-85d6-1da5c18d8033 (2 rounds).
2026-04-21 19:11:29 -06:00
..
notes dev: add spec-driven workflow, agent skills, and AGENTS.md convention 2026-04-19 23:37:12 -06:00
plans feat(planctl): lint rules — tag-syntax, cross-reference, file-presence, EARS 2026-04-21 19:11:29 -06:00
research dev: add spec-driven workflow, agent skills, and AGENTS.md convention 2026-04-19 23:37:12 -06:00
tools dev: add spec-driven workflow, agent skills, and AGENTS.md convention 2026-04-19 23:37:12 -06:00
QUICKSTART.md dev: add spec-driven workflow, agent skills, and AGENTS.md convention 2026-04-19 23:37:12 -06:00
README.md dev: add spec-driven workflow, agent skills, and AGENTS.md convention 2026-04-19 23:37:12 -06:00

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/
│   ├── <YYWWD>-<feature-slug>/           # 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.
  • <slug> = kebab-case, 24 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.

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.

Traceability tags

Location Format Example
PRD acceptance criteria R<parent>.<child> R2.1
Design section D§<number> D§4.2
Task T<parent>.<child> T3.1
Task list (markdown) _Requirements: R1.1, R1.2_ / _Design: D§2_ appended to task line
Source code comment // spec:<feature-slug>/<ref> // spec:user-auth/R2.1+D§6

Searching:

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 "..." ....
  • Conventional commit prefixes: feat:, fix:, refactor:, test:, docs:, chore:, dev: (for dev/-only changes).
  • 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.

Create a workspace per parallel agent:

# From the main clone:
jj workspace add --name planner ../<PROJECT_NAME>-planner
jj workspace add --name impl ../<PROJECT_NAME>-impl

# 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.

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 <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:

jj squash --from <plans-commit> --into feature/<YYWWD>-<feature-slug> dev/plans/<YYWWD>-<feature-slug>

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:
jj squash --from @ --into <plans-commit> dev/plans/<YYWWD>-<feature-slug>

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 <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:

./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.