dev
This skill should be used when the user asks to 'start a feature', 'build a feature', 'implement a feature', 'develop', 'new feature', or needs the full 7-phase development workflow with TDD enforceme
SKILL.mdskills/dev/SKILL.md
GitHubdev — a code change, run through work with a test-first gate
What this skill carries — grep references/ for any subject the names below miss:
!d=${CLAUDE_SKILL_DIR}; command -v skill-toc >/dev/null 2>&1 && exec skill-toc "$d"; s=$HOME/.claude/skills/plugin-utils/bin/skill-toc; [ -x "$s" ] && exec "$s" "$d"; echo "(skill-toc unavailable: references and scripts are NOT listed here — install the plugin-utils plugin, or start a new session so its bin/ reaches PATH)"
The lifecycle is work. Read it and follow it.
This file is a delta: it supplies the domain — the CLARIFY axes, the task-row shape, the lens,
the mechanical checks, the refs, the authority text. It ships no workflow.js and restates none of
work's mechanics.
What makes a run dev rather than plain work is one thing: every implementation task carries a
redCommand (the one exception: redDisposition — see below), so work's probes execute the failing test before the implementer and the passing
test after it, and the JS reads both exit codes. A task whose acceptance cannot be expressed as one
command that fails now and passes later is not ready to plan.
Phase 1 — CLARIFY
work's Phase 1, on these axes. Ask them before any reconnaissance: code says how the system
works, not what the user wants.
- Outcome — what behaviour exists after this run that does not exist now, and what does done look like for one user.
- Exclusions — what must not change: files, interfaces, dependencies, behaviour other callers rely on.
- Automated test framework and the exact command — the runner, and the literal command string a probe will execute. No framework in the repo means the first task is test infrastructure, decided here. A manual-only test proposal is a blocker: resolve an automated approach or leave this workflow. Never waive it silently.
- The user workflow, production protocol and transport the real test must exercise — the action sequence a user performs, the protocol production actually uses, and the observable result. A test over an alternate path proves nothing about the one that ships.
- The first failing test and what RED looks like — which test fails first, and the failure
output that counts. A syntax error, an import error, a broken fixture, a missing dependency or an
unrelated failure elsewhere in the suite is not the RED this workflow accepts. This is
computed, not asserted:
red_probe_gateclassifies a collection/import error ascould-not-runand refuses to dispatch. For a surface that does not exist yet, that means a stub — one that exists and raises, so the failure is an assertion rather than an ImportError — and the stub is named in the plan'sscaffoldPathsso the main thread may author it while the run is armed. - Required runtime evidence — what has to be observed at runtime rather than read in source: logs, HTTP/WS exchanges, rendered output, exit codes, screenshots.
- Review surface — working tree, commit range, or PR.
- Language and its language server — the language this change is written in (observed for an existing repo, decided here for new code), and the LSP that follows from it. Install it before reconnaissance, project-scoped, so recon and every implementer navigate by symbol rather than by grep.
Installing the LSP is two things, and the plugin is only one of them.
claude plugin install <lang>-lsp@claude-plugins-official --scope project
--scope project writes enabledPlugins into .claude/settings.json — a committed file, so it
enables the server for everyone who opens the repo; --scope local writes
.claude/settings.local.json instead. Official servers: pyright (py), typescript (ts/js),
gopls, rust-analyzer, clangd, jdtls, kotlin, csharp, php, ruby, lua, swift,
liquid.
The plugin declares a command, never a binary — pyright-lsp is just
{"command": "pyright-langserver"}. Enabling it while the server is absent registers something
that fails at first use with ENOENT: Executable not found in $PATH.
The binary is global, and that is not a preference. Claude Code resolves command from the
process PATH only — never the plugin root, never ${CLAUDE_PROJECT_DIR}, never a project
node_modules/.bin or pixi env. A project-local language server is unreachable by construction;
install it the way this machine installs system tools (nix here, or the distro package), not with
pixi add. What stays project-scoped is the enablement above and the server's own config —
pyrightconfig.json points a global pyright at the project interpreter, and a global
typescript-language-server still loads the project's own typescript from node_modules.
Registration is separate again: a mid-session install is inert until /reload-plugins. Self-send
it rather than asking the user to type it. Then prove it with one LSP call on a real file in
the repo — documentSymbol is enough. The install command's exit code says the plugin was
enabled, not that a server answers.
work's remaining axes are taken as work states them, with one domain binding: work axis 4
(observable success criteria) is answered with the target project's own test, lint and build
commands, and those strings become mechanicalChecks verbatim.
Then reconnoitre — a scouting Explore or Plan subagent, never a read of the whole tree into this
conversation — and return: the entry point and data flow, the integration boundaries, the existing
test harness and the files a new test extends, and the decisions code cannot answer. Re-ask only what
reconnaissance newly raised.
Before planning, present 2–3 feasible architectures with their boundaries, testing implications and trade-offs, and obtain an explicit choice. A sole viable option still needs its trade-off accepted.
Phase 2 — PLAN
work's Phase 2. The plan opens with frontmatter workflow: dev — required, so a context clear at
approval resumes here and not in work.
Three domain requirements on the table:
redCommandper implementation task — one invocation, no shell operators, failing now for the intended missing behaviour and passing once the task is done. It goes in the plan's Run sizingTest-first:block too, because it costs 2 agents against the fan-out ceiling.redDisposition— the only substitute forredCommand, and plan-lint (dev-red-disposition) refuses any other text:no-behaviour: <reason>(docs, config, rename — nothing to pin) oralready-covered: <test that pins it>. A task carrying one is judged on itsacceptanceCmd/verification, not on a test that went RED.refson every task row and on the lens — required, may be empty.work's spine does not validate it;wc-probeP7 refuses an absent key in THIS file, so a live run assembled from an approved plan is unchecked. Writerefs: []to state "no domain rules" rather than omitting the key.- Narrow
writablePaths— the probe runs a command that loads code the implementer can edit, so a wide writable set lets the implementer reach the thing proving its own RED. scaffoldPathsfor greenfield only, naming files and not directories — the test suite needs no entry (no task writes it, so the guard already allows it); the stub does, because it is the implementer's output and a precondition of its own red gate. A scaffold as wide as a task's writable surface isscaffold-swallows-taskat plan-lint, and it is the guard turned off with extra steps.
Phase 3 — GOAL
work's Phase 3 unchanged.
Phase 4 — the work call
The args go in the plan's <!-- work:dispatch --> arming block, and the dispatch is work's own
work-dispatch.sh — never a hand-written runner line. That script owns the TIER 1 plan-lint
gate, which refuses to dispatch on a major/critical plan finding and fails CLOSED on a verdict it
cannot count; hand-rolling the invocation silently drops it. work owns the wait, the result handling
and the return shape too, and work-result.sh reads the verdict. This run's projectDir is the
session repo, so work's own run directory is already inside it and no --run-dir override applies.
There is no built-in Workflow call — the guard at
~/.claude/hooks/main-thread-guard.sh denies that tool outright.
bash ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/work-dispatch.sh # armed plan; or pass one
bash ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/work-dispatch.sh --provider codex # "run this through codex"
Forward the provider. A provider named in this skill's $ARGUMENTS, however it is spelled —
--provider codex, --dispatch codex, "run this on gpt" — is --provider codex on the line
above. --provider is the only spelling the scripts take, and both reject the others by name.
Omitting it silently runs the user's codex request on claude.
const projectDir = /* the session repo */
const goal = /* one sentence */
const planPath = /* the approved plan's path */
const testCmd = /* the project's test command, from CLARIFY axis 3 */
const lintCmd = /* the project's lint command, or null */
const buildCmd = /* the project's build command, or null */
const taskName = /* the task's name */
const taskWork = /* what to build */
const taskPaths = /* narrow writablePaths — see Phase 2 */
const taskAcceptance = /* the criterion the verifier checks */
const taskRed = /* the exact command from CLARIFY axis 3, scoped to the first failing test */
const args = {
projectDir,
goal,
// The plan's table verbatim. Every implementation task carries redCommand and refs.
tasks: [
{ id: "T1",
name: taskName,
work: taskWork,
writablePaths: taskPaths,
acceptance: taskAcceptance,
redCommand: taskRed,
refs: ["${CLAUDE_PLUGIN_ROOT}/skills/dev/references/tdd.md",
"${CLAUDE_PLUGIN_ROOT}/skills/dev/references/writing-good-tests.md"] },
],
// The TARGET project's own commands, collected at CLARIFY. Nothing generic — a check that does
// not run this repo's suite gates nothing.
// ONE entry point; three lose one silently. A missing --test-cmd, --lint-cmd, --build-cmd or --plan is exit 2 unless --no-lint-cmd / --no-build-cmd / --no-plan declines it.
mechanicalChecks: [
{ name: "dev",
cmd: `bash ${CLAUDE_PLUGIN_ROOT}/skills/dev/scripts/check.sh --project-dir ${projectDir} --plan ${planPath} --test-cmd "${testCmd}"${lintCmd ? ` --lint-cmd "${lintCmd}"` : ' --no-lint-cmd'}${buildCmd ? ` --build-cmd "${buildCmd}"` : ' --no-build-cmd'}` },
],
// check.sh runs every checks/dev/constraints/ script through `workflow-check dev --constraints-only`. work-checks.sh runs ruleChecks as a command (no agent); Jev scores the rules under checks/dev/rules/ (LOOP, MOCK, NET, SHELL, WEAK); a rule with p >= 0.85 lands in rulesThatFailed and blocks, lower ones reach the lens as a ranked checklist.
ruleChecks: { name: 'jev-dev-rules', cmd: `bun ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/rule-check.ts --workflow dev --project-dir ${projectDir} --plan ${planPath}` },
// One lens after verification and mechanical checks; its checklist covers all five dimensions.
lens: {
agentType: "Explore",
refs: ["${CLAUDE_PLUGIN_ROOT}/skills/dev/references/lens-security.md",
"${CLAUDE_PLUGIN_ROOT}/skills/dev/references/lens-performance.md",
"${CLAUDE_PLUGIN_ROOT}/skills/dev/references/writing-good-tests.md"],
prompt: "Judge the change in the working tree against the approved plan and the goal. CHECKLIST, every item in scope on every run: (1) CRITERIA vs ARTIFACTS — for each success criterion in the plan and goal, is there an artifact in the tree that satisfies it? Missing or partial satisfaction is a finding, CRITICAL where the deliverable cannot stand without it. (2) SCOPE intent — path containment is check.sh's scope leg, so do not re-derive it; judge only unrequested features and silently skipped plan items (a plan task with no artifact), CRITICAL where a plan task the goal depends on was skipped. (3) SECURITY of the changed code, against the finding classes in the first ref — read it in full first. A finding names a file and line and states the concrete attack vector: the input an attacker controls, the path it travels, and what it reaches. Pre-existing defects this change did not introduce are out of scope. CRITICAL where the vector reaches attacker-controlled input on a production path. (4) PERFORMANCE — the runtime cost of the changed code, against the finding classes in the second ref, read in full first. A finding names a file and line, sits on a path that runs often enough to matter, and states the cost as Big-O over the input that actually grows or as concrete latency/memory; speculation without a growing input is not a finding. CRITICAL where the growth makes a production path unusable at realistic input size. (5) TEST PINS BEHAVIOUR: for each task, does the test that went RED (for a task with `redDisposition`, its acceptanceCmd/verification instead) assert the behaviour its acceptance names, not an incidental property (exit status only, a log line)? A test that would stay green with the behaviour removed is a MAJOR finding. Severity is MAJOR at minimum for all five. MODE — RED (a task was flagged or a mechanical check failed): diagnose EVERY failure in the digest and route it, naming cause and fix, to the task id whose writablePaths own the file, or to 'plan' when no task can own the fix. MODE — GREEN (everything passed): make one open-ended pass over the whole checklist and report each finding with an ownerTask. In both modes, rule every carried finding open or closed against evidence you actually read; silence leaves it open.",
},
authorityExtra: [
"TDD RULE — no implementation before a genuine RED observation.",
"Write the smallest real test that exercises the production path this task names, run it, and read the output. Accept RED only when it fails for the intended MISSING BEHAVIOUR — never a syntax error, an import error, a broken fixture, an unavailable dependency, or an unrelated failure elsewhere in the suite. If you implemented before observing RED, delete the implementation and restart the task; a test written beside its fix passes whether or not it ever exercised the defect.",
"A test must do what the user does. Exercising an alternate protocol or transport, or inspecting source and logs instead of runtime behaviour, is not evidence for a task whose acceptance names a user-visible result.",
"Your task's redCommand is executed by probes outside your control, before and after you work. A self-reported RED is not evidence and never was.",
"Rules: ${CLAUDE_PLUGIN_ROOT}/skills/dev/references/tdd.md and ${CLAUDE_PLUGIN_ROOT}/skills/dev/references/writing-good-tests.md govern every task — read the latter IN FULL before writing a test. Browser and web work also follows ${CLAUDE_PLUGIN_ROOT}/skills/dev/references/testing-web.md; desktop and native work also follows ${CLAUDE_PLUGIN_ROOT}/skills/dev/references/testing-desktop.md.",
].join("\n"),
verifierAgentType: "Explore",
}
Path containment is mechanical: check.sh's scope leg (work/scripts/scope-check.sh, given --plan) fails a changed path
under no task's writablePaths or scaffoldPaths, naming each. Lens item (2) judges intent only.
verifierAgentType and the lens agentType pin Explore because it has no Edit and no Write: a
judge that structurally cannot modify the tree beats a prompt asking it not to.
implementerAgentType is deliberately unset: the output is code under TDD, and a custom agent body would replace the software-engineering prompt the tasks depend on.
Add ${CLAUDE_PLUGIN_ROOT}/skills/dev/references/testing-web.md or
${CLAUDE_PLUGIN_ROOT}/skills/dev/references/testing-desktop.md to a task's refs when that
task's real test drives a browser or a native app. authorityExtra names them; refs is what makes
an implementer read one in full.
Handle the result per work's Phase 4 — including that a red-not-red, red-unproven or
green-not-green verdict lands in tasksThatFlagged and is fixed as a task, not as a lens.
Phase 5 — HUMAN REVIEW
work's Phase 5 unchanged, on the review surface from CLARIFY axis 7.
Red flags
| Situation | Wrong move | Right move |
|---|---|---|
| Guard denies a pre-dispatch write | --abandon to get moving |
read the denial: an uncovered path is already writable, and a covered one that must pre-exist goes in scaffoldPaths. --abandon unguards the rest of the session and silences the Stop nudge with it |
| RED is an ImportError on the module being built | call it red and dispatch | the probe refuses it as could-not-run; write the throwing stub, declare it in scaffoldPaths |
| LSP enabled, binary never installed | trust the install's exit 0 | one LSP documentSymbol on a repo file before recon; ENOENT there is the whole failure |
| No test harness in the repo | proceed and test by hand | test infrastructure is the first task, decided at CLARIFY — absence of tests is never a waiver |
| The failing test needs two steps | redCommand: "build && test" |
work throws on shell operators; put the steps in a script and name the script |
| Task's real test would drive a browser or an app | assert on source or logs | the runtime references are refs on that task; a screenshot with no assertion is not evidence |
| Adding a domain review dimension | declare a second lens beside this one | work takes ONE lens; add the dimension to its checklist, or make it a leg of check.sh when an exit code can decide it |
| RED reported by the implementer | accept it | probes execute redCommand on both sides; the verdict is the JS's, never the doer's |
Something work does not obviously do |
write a dev/workflow.js |
ask which work parameter is missing |