Skip to content
Back to skills

Dev Story

ASecurity

Implement a story from its story file. Use when the user says "dev story", "implement story", or "dev {story file path}".

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
ai-agentsgoswiftbashdockertestingcode-reviewgit

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 23, 2026

npx -y skills add rterakedis/leanwheel-skills --skill dev-story --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dev Story?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dev Story
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rterakedis-dev-story/badge)](https://www.skillsdirectory.com/skills/rterakedis-dev-story)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dev-story
description: Implement a story from its story file. Use when the user says "dev story", "implement story", or "dev {story file path}".
---

# Dev Story Skill

**Goal:** Implement all tasks until ACs satisfied and DoD passes.

**Iron rule:** Only stop when: (a) all tasks done and DoD passes, (b) HALT condition, or (c) user says stop.

## Activation

1. Identify story: user path or first `Status: ready-for-dev` in `docs/epics/`. Stop if not found.
2. Read story file completely.
3. Read CLAUDE.md if exists (may override Dev Notes).
4. **If `docs/setup/swift/` exists** (Apple platform project): read the files relevant to this story's tasks before implementing:
   - Any story touching data models, services, or `@Observable`: read `docs/setup/swift/state-management.md`
   - Any story touching async loading, background work, or threading: read `docs/setup/swift/concurrency.md`
   - Any story adding new features, services, or project structure: read `docs/setup/swift/architecture.md`
   - Any story adding views or UI components: read `docs/setup/swift/ui-composition.md`
   - Any story adding tests: read `docs/setup/swift/testing.md`
   - Any story adding views or UI components: also read `docs/setup/swift/accessibility.md`
   - If the project uses SwiftData and the story touches models, queries, or persistence: read `docs/setup/swift/swiftdata.md`
   - If the project uses Core Data / `NSPersistentCloudKitContainer` and the story touches the model, persistence, or sync: read `docs/setup/swift/core-data-cloudkit.md` — every schema rule there is a launch crash, and a deployed violation is a permanently wrong Production schema
   - Any story adding user-facing strings: read `docs/setup/swift/localization.md`
   - Any story adding or changing seed/preview/demo records, or writing copy for a regulated or consequential surface (tax, health, legal, safety, financial): read `docs/setup/swift/demo-data-and-copy.md`
   - Any story that changes the Xcode project itself — build settings, capabilities, targets, bundled resources, schemes: read `docs/setup/swift/xcode-footguns.md` (these fail at runtime or with a misleading error, never at compile time)
   - Any story adding/changing a persisted model entity, adding user-facing views, or touching launch behavior: read `docs/setup/swift/testability.md` (seed scenarios, launch arguments, deep-link routes, accessibility identifiers)
   - Any story that needs the app *rendered* — screenshot verification, adding a flow, or stabilizing a screen: read `docs/setup/swift/simulator.md` (`scripts/sim.sh`, the screenshot matrix, flow conventions)
   - Read `docs/setup/swift/anti-patterns.md` if present
   - If `docs/setup/swift/iphone-duo.md` exists and the story touches layout, toolbars/tab bars, sheets, or any custom container: read it
   - If `docs/setup/swift/ipados-specific.md` exists and the story touches navigation, split view, drag-and-drop, pointer, keyboard, or multi-window: read it
   - If `docs/setup/swift/macos-specific.md` exists and the story touches menus, windows, toolbar, settings, tables, or file operations: read it
4b. **If `docs/setup/web/` exists** (web/SSG project): read the files relevant to this story's tasks before implementing:
   - Any story touching stylesheets, tokens, or layout: read `docs/setup/web/css-design-system.md`
   - Any story adding pages, forms, or content templates: read `docs/setup/web/accessibility-seo.md`
   - If `docs/setup/web/astro.md` exists and the story touches `.astro` files, collections, or images: read it
   - If `docs/setup/web/hugo.md` exists and the story touches `layouts/`, `content/`, or `assets/`: read it
   - Read `docs/setup/web/anti-patterns.md` if present
4c. **Design contract** (UI stories): if the story has a `### Design Contract` in Dev Notes, it is the design source of truth — use its tokens, states, and reuse list; do not read `docs/ux/` again. If the story changes user-visible UI but has **no** Design Contract and `docs/ux/DESIGN.md` exists: read DESIGN.md frontmatter and the relevant EXPERIENCE.md sections before implementing (and note the gap in Completion Notes so `/create-story` improves next time).
5. Execute **TRANSITION** with `new_label: in-progress` (skip if unavailable).
5b. Record the review base: `git rev-parse HEAD`. The reviewer diffs everything since it, committed or not — note any unrelated uncommitted changes in Completion Notes.
6. Confirm: "Implementing {epic}.{story}: {title}. Starting..."

## Execution

For each task in order:
1. Read task. Understand file, action, outcome.
1b. Apply the simplicity ladder from CLAUDE.md's `## Simplicity & Anti-Over-Engineering` — after understanding the task, not instead of: reuse before rewrite, stdlib/native before a new dependency, and write the least code that satisfies the AC.
2. Implement.
3. Check box: `[ ]` → `[x]`.
4. If problem found, log in Debug Log; continue.

Don't ask for clarification (use Dev Notes; log ambiguous calls in Completion Notes).

**Manual steps belong to the user.** If Dev Notes has a `### Manual Steps` subsection (see create-story ▸ *Manual steps*), surface those steps to the user verbatim rather than attempting or silently skipping them. If the story didn't already verify a named GUI/console path, check it against the installed toolchain version first.

**Migration-shaped stories: the invariant test is Task 1.** If Dev Notes says `**Shape:** migration` — or the story's core is plainly a repeated mechanical change (a rename across N call sites, a type migration across N properties, a dependency swap) even when create-story didn't label it — write the invariant test **before** the edits, run it, and confirm it fails enumerating every violation. Then make the edits. At the end, re-prove it by reverting exactly one instance and confirming a failure that names that instance (DD-11). If the task list is ordered otherwise, reorder it.

**Keep testability current as you go** (Apple projects with `docs/setup/swift/testability.md`): a task that adds or changes a persisted model entity updates the `SeedScenario` registry (at minimum `.typical` and `.edge`) in the same task; new user-facing views get the exact `.accessibilityIdentifier`s the story's Design Contract names; a task that adds a screen adds its deep-link route in the same task. Canonical: `docs/setup/swift/testability.md` (DD-34).

**Keep files maintainable as you go.** If a file you create or touch crosses the file-size / decomposition target in the routed guidance (`docs/setup/swift/ui-composition.md` or `docs/setup/web/`), decompose it as part of the task, not later. Split along responsibility seams (Swift: `extension TypeName {}` files for members, named `private struct` sub-views for layout) rather than by line count, and don't give a sub-view its own data access — cohesion decides the cut.

After major task groups, suggest `/compact` if context heavy. Don't block; continue.

## HALT Conditions
Stop if:
- Required file/dependency missing and can't infer
- AC contradictory or impossible
- Task requires out-of-scope changes risking breakage
- **Build or test suite cannot be made green** after 3 consecutive red runs (see Build & Test Gate escalation limit). Do not mark the story `review` over a red build — report the failing output and HALT.

## Story File Updates

Modify only:
- Tasks/Subtasks — check off `[ ]` → `[x]` as each task completes
- Acceptance Criteria — check off `[ ]` → `[x]` as each AC is satisfied; do this during implementation, not after
- Architecture Compliance Checklist (if present in Dev Notes) — check off each item before marking done
- Invariant Verification (stateful stories) — record evidence per Behavior Contract invariant on completion
- Debug Log — log issues
- Completion Notes — key decisions
- File List — files created/modified/deleted
- Change Log — one-line per session
- Status — set `status: review` in the **YAML frontmatter** when done (frontmatter is the source of truth; do not write a `**Status:**` body line)

Don't modify: User Story statement, Dev Notes prose, References.

## Definition of Done

Before review, verify all items in `checklist.md` pass. Fix any failures first.

**Maintainer doc gate:** if the story introduced a new external dependency, a build-system change, or a new platform capability, its maintainer runbook/doc exists or is updated **before** the story is marked done, not left as an action item.

**Never reopen a `done` story.** `status: done` stories are immutable; new work becomes a new story `{N}.{last+1}` (canonical rule: `skills/harvest-findings/SKILL.md`).

## Build & Test Gate

**Verification is by running, not by reading.** Static reasoning is not a substitute for the toolchain — especially for Swift, where result builders, macros (`@Observable`/`@Model`), actor isolation, and `some View` produce errors that cannot be reliably predicted by reading. Before the story can leave `in-progress`, the project must **compile clean and its tests must pass this session** — verified by actually invoking the toolchain, not by inspection.

1. Detect the toolchain and run a real build + test:
   - **Apple / Swift** (`docs/setup/swift/` exists, or an `.xcodeproj`/`Package.swift` is present): mandatory.
     ```bash
     # Xcode project/workspace — quiet, full log on disk, last 40 lines in context
     mkdir -p .leanwheel/logs && [ -f .leanwheel/logs/.gitignore ] || echo '*' > .leanwheel/logs/.gitignore   # self-ignoring, like .leanwheel/sim/
     xcodebuild -quiet -scheme {scheme} -destination 'platform=iOS Simulator,name={device}' build test \
       2>&1 | tee .leanwheel/logs/build-test.log | tail -n 40
     # or SwiftPM
     swift build -q && swift test -q 2>&1 | tee .leanwheel/logs/build-test.log | tail -n 40
     ```
     (`{device}` = a simulator the installed Xcode ships — `scripts/sim.sh doctor` lists them; never hard-code an old model name.)
   - **Web / SSG** (`package.json` present): run the project's build + test scripts with their quiet reporter (e.g. `npm run build --silent && npx vitest run --reporter=dot`, or the lint/typecheck script if no tests), same `tee … | tail -n 40` pattern.
   - **Other toolchains:** run the project's documented build + test command — quiet flag if it has one, `tee` to `.leanwheel/logs/`, tail into context.
   - **Quiet output is the rule, not a preference:** toolchain output stays in the conversation for the rest of the session and is re-sent on every turn. Keep the full log on disk for the failing-line cite; only the tail enters context. If `CLAUDE.md` has a `## Quiet commands` section, use those invocations verbatim.
   - **No toolchain detected:** record a `Build & Test Gate: manual-required` note in the Debug Log with the exact command a human should run, and continue. Never fake a green result.
2. **Red build or failing test = not done.** Read the compiler/test output, fix the cause, and re-run. Loop until green. Do not patch the story file to `review` over a failure.
   **Escalation limit:** after **3 consecutive red runs** with no new fix succeeding, stop, report the failing output, and ask the user — do not keep retrying. Retry thrash burns tokens and, on GUI toolchains, ties up the device/UI. This is the HALT condition below.
2b. **A flaky or hanging test suite is a first-class bug — file it the moment it is observed**, via **LOG-AND-SCHEDULE** in `skills/deferred/SKILL.md`. Never "it passes in isolation, ignore it": a festering hang blocks the next epic-boundary gate and masquerades as agent or dev failures. Then continue with the targeted/known-good invocation.
3. **Run the cumulative eval set.** If `docs/evals/` exists, run `bash scripts/evals.sh --epic {n} --quiet` (zero-token; its last line is the RUN report, per evals → RUN). A failing case is a **regression** of an earlier story — treat it exactly like a red build: fix and re-run, or HALT.
4. **Update the eval set.** If `docs/evals/` exists and this story added tests that cover an AC or invariant, execute **BUILD** from `skills/evals/SKILL.md` to append (or flip `enabled: true` on) the corresponding `type: command` cases, so the next story inherits them.
4b. **A new gate is not done until it has been shown to fail.** For every test, eval case, or assertion **written this story** (not every run of the existing suite): break the thing it guards — revert the fix, reintroduce the defect, or corrupt the input — and confirm the gate fails **and names the specific item**. Restore, confirm green. A gate that has only ever been observed green is unverified, however many times it ran. Record the discriminating check in Completion Notes (`{gate} — sabotage: {what was broken} → failed naming {item}; restored green`) so a reviewer can see it happened.
   **Use `scripts/sabotage.sh` for the revert-the-fix mode** (zero-token, deterministic): `scripts/sabotage.sh --name {TestName} -- {filtered gate cmd}` stashes the non-test changes, runs the *filtered* gate expecting a red that names the item, restores, and re-runs expecting green — its last line (`SABOTAGE OK` / `GATE CANNOT FAIL` / `GATE RED BUT UNNAMED`) is the Completion Notes entry. Reserve model-driven sabotage (reintroduce a defect / corrupt an input) for gates the revert can't exercise.

4c. **Any gate that enumerates must assert it enumerated.** A test that walks a source tree, globs files, or greps the codebase must assert a plausible lower bound on what it found and fail if the count is implausible — otherwise it passes vacuously when a moved directory or broken path returns zero items.

4d. **Before accepting a zero as proof, prove the channel can see a one.** When verification takes the form "search output for X and find none," first produce an X deliberately and confirm it appears on the channel being searched. An unvalidated zero is not evidence of absence. Needed only for a **novel or assumed** channel (a log stream, a grep over runtime output) — not for ordinary test output.

5. Record the result in the Debug Log: the command run and `build+test green` (or the manual-required note), plus `evals: P/T`. This is the executable regression net that prevents a later story from silently reverting a prior fix — it only works if it actually runs every story.

## On Completion

When tasks done, DoD passes, **and the Build & Test Gate is green** (or manual-required is recorded):

**Verify reachability, not presence.** A string, control, or affordance existing in source is *not* evidence it renders — dev, review, and automated validation can all pass while only confirming the string is in the source. Confirm by driving the app or dumping the view hierarchy. **Corollary:** when a UI element can't be addressed in a UI test, dump the tree before blaming the test framework — the element may not exist.

1. **Invariant verification (stateful stories):** if the story's `### Behavior Contract` lists invariants, verify each one holds in the built code with **evidence** — a test that exercises it, or a cited assertion/guard in the source (`file:line`). Record results under `### Invariant Verification` in the story file: each invariant as `- [x] {invariant} — {test name | file:line}` or `- [ ] {invariant} — UNVERIFIED: {why}`. An invariant with no test and no enforcing code is **not** a pass (DD-14) — add a one-test cover if cheap, otherwise leave it `[ ]` — the reviewer turns it into a finding. Skip entirely for simple stories or stories with no invariants.
2. **Design verification (UI stories):** if the story changed user-visible UI, execute **VERIFY** from `skills/design-verify/SKILL.md` — render the changed surfaces (simulator or dev server + screenshots), compare against the Design Contract, and write results to `### Design Verification` in the story file. Unresolved mismatches stay in that section; the reviewer turns them into findings. If no rendering tooling is available, record the manual checklist and continue. Skip entirely for stories with no user-visible surface.
3. **Hand off for independent review.** You never review your own diff (DD-75): the reviewer is a fresh context that never saw your reasoning.
   1. Set `status: review` in the YAML frontmatter, then **TRANSITION** to `review` (skip if unavailable).
   2. **Operational doc sync** (routed to a cheap model, DD-20/DD-24): if you are running as the `lw-story-developer` subagent, do not run docs-sync yourself — set `INFRA TOUCHED: yes` in your report when this story's File List includes an infra-shaped file (dependency manifest, `.env`/config, migration/schema, script, Dockerfile/CI/deploy, or a new service entrypoint); the orchestrator spawns `lw-docs-sync` (Haiku) to do it. If you are running **standalone** (not under a flywheel), call the Agent tool yourself — `subagent_type: "lw-docs-sync"`, prompt naming op **OPERATIONAL** and this story's path — or, if subagents are unavailable, execute **OPERATIONAL** from `skills/docs-sync/SKILL.md` inline as a fallback. Record any `DOCS UPDATED` in the Debug Log.
   3. **Ledger:** append one `dev-story` line via `scripts/ledger.sh` (never hand-write the JSON — the script owns the schema, normalizes the model name, stamps the timestamp, and no-ops if `docs/metrics/` is absent):
      `bash scripts/ledger.sh dev-story --story {id} --model {model} --build-test green|red|manual-required --bt-iterations {n} --evals P/T --invariants V/T [--docs-updated a,b] [--duration-min n]`
      Status qualifiers ("233/233", "doc-only") go in `--build-detail`, never in `--build-test`.
   4. **Report:** "{epic}.{story} ready for review.{ Docs: {list} if any}", then the **TESTING PLAN** and **REVIEW HANDOFF** blocks below.
   5. **Start the review.** Under a flywheel (you are `lw-story-developer`): stop here — the orchestrator spawns the reviewer. Standalone: call the Agent tool with `subagent_type: "lw-story-reviewer"` and the REVIEW HANDOFF block as the prompt, and relay its report. It cannot ask the user, so put its `DECISIONS NEEDED` to the user and send the answers back to the same reviewer. If subagents are unavailable, tell the user to `/clear` and run `/code-review {story path}` — never review in this context.

### Review Handoff (required report field)

Pointers only. Never summarize what you did or suggest what to check — the reviewer's independence is what it is for.

```
REVIEW HANDOFF
STORY: {story file path}
BASE: {the base ref recorded at Activation}
LOG: .leanwheel/logs/build-test.log | none
```

### Testing Plan (required report field)

Every dev-story report ends with a `TESTING PLAN` split into two named sub-fields. **Both are mandatory** — a `TESTING PLAN` missing either sub-field is an incomplete report (under a flywheel, a non-return: the orchestrator resumes you rather than advancing). The split exists so the epic-boundary test plan can subtract what automation already proves instead of asking a human to re-walk it.

```
TESTING PLAN
AUTOMATED: {flow/suite/eval names that now pin this story's behavior — names only, e.g. `UpgradeSheetFlow`, `CustomerLimitServiceTests`, `docs/evals/epic-12.md#E12-03`. Not assertions, not prose. "none" if nothing automated.}
MANUAL: {only what no test can exercise. One line per item, each tagged with WHY it is manual:
  - {step} → {expected} [visual-judgment | device-only | sandbox-only | setup-unreachable]
  Include the exact setup command when a step needs one (e.g. `scripts/sim.sh launch --uitest --seed heavy --route settings`, an `xcrun simctl` call) — carry every flag the app needs to honour it. "none — fully automated" or "none — no user-visible surface changed" when empty.}
```

Rules:
- An item belongs in `MANUAL` only if you can name the reason a test can't see it. If you can't, it belongs in a test — write the test and list it under `AUTOMATED`.
- `AUTOMATED` is the set of names the boundary greps for; cite the identifier as it appears in the test target / eval file so the grep hits.
- Unknown-reachability ("I'm not sure a test could reach this") is **setup-unreachable**, and says so — it is still a gap worth closing, not a free pass.

Files in this skill

  • SKILL.md19.6 KB
  • checklist.md2.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.