From spec-tests-first
Pre-cycle skill for spec-tests-first. Use when the user invokes /spec-tests-first:init to set up STF on a repository — auto-detects whether to run Case A (fresh repo, fresh project), Case B (existing codebase, no specs), or Case C (existing repo with flat specs requiring migration). Seeds CLAUDE.md (## Test commands +
How this skill is triggered — by the user, by Claude, or both
Slash command
/spec-tests-first:init <project-name><project-name>This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Announce at start:** Say to the user: "I'm using /spec-tests-first:init to scan `$1` and decide which of three init paths fits (fresh / existing-codebase / migrate-flat-specs), then run only what's needed." Then proceed.
Announce at start: Say to the user: "I'm using /spec-tests-first:init to scan $1 and decide which of three init paths fits (fresh / existing-codebase / migrate-flat-specs), then run only what's needed." Then proceed.
You are running the pre-cycle initializer for project $1. Inputs: the working directory state. Outputs: CLAUDE.md config (test commands + test layout), seeded docs/codebase-map.md, and — only in the migration case — restructured docs/specs// subdirectories with per-feature spec-status.md.
Idempotent or nothing. Every action checks if the artifact already exists; if it does, skip with a printed reason. /spec-tests-first:init re-runs must be safe — never overwrite spec.md, spec-status.md, or completed CLAUDE.md sections.
The whole point of init is to bring an existing project into SDD without destroying anything. Overwriting a live spec-status.md (which may contain in-progress phase tracking) is the worst-case failure mode.
git status --porcelain. If dirty AND the changes are unrelated to init artifacts (docs/specs/, docs/codebase-map.md, CLAUDE.md), AskUserQuestion: stash WIP (recommended) / proceed anyway / cancel. On stash → git stash push -u -m "sdd:init pre-start stash". On cancel → stop.$1 is the project's friendly name (used in next-step pointer + commit messages). If $1 is empty, AskUserQuestion to capture one (default: the repo's root directory name).Determine which case applies by scanning the working directory.
docs/specs/*.md file (NOT inside a subdirectory). Use Glob: docs/specs/*.md.docs/specs/*/spec.md. Use Glob: docs/specs/*/spec.md.package.json, pom.xml, build.gradle, build.gradle.kts, Cargo.toml, go.mod, pyproject.toml, setup.py, Gemfile, composer.json, *.csproj.## Test commands present = boolean, from grep.Flat specs present?
├─ YES → Case C (migration)
└─ NO → Source manifests present?
├─ YES → Case B (existing codebase, no specs)
└─ NO → Case A (fresh repo, fresh project)
If BOTH flat specs AND subdirectory specs are present (someone migrated by hand for some features), this is Case C — partial. Process only the flat files; existing subdirectory specs are preserved untouched.
If subdirectory specs are present AND no flat specs, the project is already initialized. Print a summary of detected features and Phase progress (read each spec-status.md), then stop without changes.
After detection, print:
/spec-tests-first:init for project `<name>`.
Scanning...
Source manifests detected: <comma-separated list with their subdirectories>
docs/specs/ contains: <C_flat> flat files, <C_sub> subdirectories
CLAUDE.md ## Test commands present: <yes / no>
Plan: Case <A | B | C> — <one-line description>
Continue?
(a, recommended) Yes — run Case <X>
(b) Cancel
AskUserQuestion. On (a): proceed to Step 2 (case-specific path). On (b): stop.
Triggered when: no source manifests detected AND no docs/specs/ directory or it's empty.
AskUserQuestion:
Detected an empty project — no source code, no existing specs. Is this the intended state for
<name>?
- (a, recommended) Yes — set up CLAUDE.md + empty codebase-map.md and exit cleanly
- (b) No — let me cancel and check what's missing
/spec-tests-first:build's language-specific version)In /spec-tests-first:build, the gitignore scaffold is language-specific because by build time the stack is known. In /spec-tests-first:init Case A there is no source yet, so the gitignore stays language-agnostic. The user (or a later /spec-tests-first:spec) refines it after the stack is chosen.
git init (recommended) / proceed without git / cancel..gitignore at the project root, scaffold a minimal language-agnostic one:# Local scratch
*.log
*.tmp
.env
.env.*
# OS junk
.DS_Store
Thumbs.db
desktop.ini
# IDE
.vscode/
.idea/
*.swp
# Claude Code session artifacts
chat-session/
AskUserQuestion:
Want to pick a test framework + layout profile now, or leave that for the first
/spec-tests-first:specto detect?
- (a) Pick now — I'll save it to CLAUDE.md
- (b, recommended) Leave for later — /spec-tests-first:spec will detect on first invocation
On (a): present the 12 built-in profiles + custom. Save to CLAUDE.md ## Test commands + ## Test layout using the single-service or multi-service shape from /spec-tests-first:build's Step 2/3.
On (b): skip — /spec-tests-first:spec will handle this when invoked.
Write docs/codebase-map.md (only if it doesn't exist):
# codebase-map
Project-wide map of source files and their roles. Updated by `/spec-tests-first:build` after each spec.
| File | Role |
|------|------|
## Key invariants
<!-- TODO: add invariants as the project takes shape -->
/spec-tests-first:init complete — project `<name>` ready for SDD.
What's set up:
- .gitignore scaffolded (if missing)
- CLAUDE.md ## Test commands + ## Test layout (if you picked A.3 option (a))
- docs/codebase-map.md (empty, ready for first build)
Next: /spec-tests-first:spec <feature> — write your first feature spec
Stop. Skip downstream sections.
This section is invoked by Case B (Step B.1) and Case C (Step C.2). It writes CLAUDE.md ## Test commands + ## Test layout once per init invocation; subsequent calls within the same run reuse the cached resolution.
Glob for stack manifests in subdirectories (not the repo root):
**/package.json (excluding root, excluding node_modules)**/pom.xml**/build.gradle***/Cargo.toml**/go.mod**/pyproject.toml**/setup.py**/*.csproj**/Gemfile**/composer.jsonIf TWO OR MORE distinct subdirectories contain manifests → monorepo signal. AskUserQuestion:
Detected monorepo with services:
- () at
<subdir1>/- () at
<subdir2>/How should /spec-tests-first:init configure CLAUDE.md?
- (a, recommended) Multi-service — per-service test commands + layout profiles
- (b) Single-service — pick one to configure; ignore the others
- (c) Cancel
On (a): record the service list, run S.2 + S.3 per service. On (b): record the chosen service, run S.2 + S.3 once. On (c): stop the entire init invocation.
If only the repo root has a manifest (or 0/1 in subdirectories), single-service — run S.2 + S.3 once.
For each service (or the single service):
Derive a candidate command from the manifest:
pyproject.toml / setup.py → python -m pytestpackage.json with a scripts.test entry → use that script (npm test); else detect a jest / vitest binary in node_modules/.bin/Cargo.toml → cargo testgo.mod → go test ./...pom.xml → mvn testbuild.gradle / build.gradle.kts → ./gradlew test*.csproj → dotnet testGemfile with rspec → bundle exec rspeccomposer.json with phpunit → vendor/bin/phpunitAskUserQuestion (per service if multi-service):
Service: — detected command:
<cmd>. Save?
- (a, recommended) Yes — save and proceed
- (b) Override — type a different command
- (c) Skip this service
On (a)/(b): write to CLAUDE.md ## Test commands. Use the multi-service or single-service format from /spec-tests-first:build's Step 2.
Derive a candidate profile from the resolved command (use the table from /spec-tests-first:build Step 3 — pytest → python-pytest, jest → js-jest/react-jest-rtl, etc.).
If multiple candidates fit (e.g. jest could be plain or React), AskUserQuestion. If no candidate matches, ask for custom (4 fields: tests_root, files, fixtures, feature_anchor).
Save to CLAUDE.md ## Test layout using the same single-service / multi-service shape as ## Test commands.
After both S.2 and S.3 finish for every service, the CLAUDE.md sections are complete. Subsequent steps (codebase-map seeding, spec migration) read these without re-prompting.
This section is invoked by Case B (Step B.2) and Case C (Step C.4). Reads ${CLAUDE_PLUGIN_ROOT}/skills/init/references/codebase-map-patterns.md for each resolved profile, applies the globs, scaffolds docs/codebase-map.md.
If docs/codebase-map.md already exists:
On append: skip files already present as rows; only add new ones.
Use the Read tool on ${CLAUDE_PLUGIN_ROOT}/skills/init/references/codebase-map-patterns.md. Locate the section matching the profile name (e.g. ## profile: spring-boot-junit5). Parse the three blocks:
For multi-service: repeat per service profile.
For each Include pattern:
frontend/src/**/*.tsx).For each Exclude pattern:
For each remaining file path:
<noun> placeholder appears, try to derive it from the file name (e.g. LoginController.java → <noun> = "login"). If derivation isn't obvious, use the placeholder.<!-- TODO: describe role -->.For single-service, write a flat table:
# codebase-map
Project-wide map of source files and their roles. Updated by `/spec-tests-first:build` after each spec.
## Source files
| File | Role |
|------|------|
| `src/controllers/auth_controller.py` | REST controller for auth |
| `src/services/auth_service.py` | <!-- TODO: describe role --> |
## Key invariants
<!-- TODO: add invariants -->
For multi-service, group by service:
# codebase-map
Project-wide map of source files and their roles. Updated by `/spec-tests-first:build` after each spec.
## Server (service: backend)
| File | Role |
|------|------|
| `backend/src/main/java/com/ex/auth/LoginController.java` | REST controller for login |
## Client (service: frontend)
| File | Role |
|------|------|
| `frontend/src/features/login/LoginForm.tsx` | <!-- TODO: describe role --> |
## Key invariants
<!-- TODO: add invariants -->
After writing, print:
docs/codebase-map.md seeded:
Total files: <N>
By service (if multi-service):
backend: <X> files
frontend: <Y> files
TODO role descriptions: <K> (fill in before /spec-tests-first:spec / /spec-tests-first:build)
Triggered when: source manifests detected AND no docs/specs/*.md flat files AND no docs/specs/*/spec.md subdirectories.
Run the Shared step — Profile detection + CLAUDE.md seeding above (Steps S.1, S.2, S.3). After this completes, CLAUDE.md has ## Test commands + ## Test layout for every service.
Run the Shared step — Codebase-map seeding above (Steps M.1 through M.6). After this completes, docs/codebase-map.md is scaffolded.
/spec-tests-first:init complete — project `<name>` ready for SDD.
What's set up:
- CLAUDE.md ## Test commands ↓ <command(s)>
- CLAUDE.md ## Test layout ↓ <profile(s)>
- docs/codebase-map.md seeded with <N> files
→ fill in <!-- TODO --> role descriptions before /spec-tests-first:spec
Existing source is untouched.
Next: /spec-tests-first:spec <feature> — write your first feature spec against the existing baseline
Stop.
Triggered when: at least one docs/specs/*.md flat file exists.
This is the most complex path. Run in this order: C.1 catalog → C.2 profile (shared) → C.3 restructure → C.4 codebase-map (shared) → C.5 scan + confirm → C.6 final warnings.
Use Glob: docs/specs/*.md. For each match:
NN-) and .md suffix: 02-auth.md → auth. Files without numeric prefix use the bare stem.## User Stories / ## 3. User Stories / ## Acceptance Criteria / ## 3. Acceptance Criteria. Presence → feature spec.00-overview.md).docs/specs/<feature>/spec.md already exists, log Skipping <feature> — already migrated. Print after the catalog finishes.Build two lists: features_to_migrate, project_docs_to_move.
Print the catalog summary:
Catalog:
Feature specs to migrate (<N>): <list>
Project-level docs to move (<M>): <list>
Already-migrated (skipped): <list>
AskUserQuestion to confirm:
Proceed with migration?
- (a, recommended) Yes
- (b) Cancel — keep flat files as-is
Run the Shared step — Profile detection + CLAUDE.md seeding (Steps S.1, S.2, S.3). Same as Case B.
Initialize a global US-N counter at 1. Process features_to_migrate in filename sort order (so 01-...'s stories get the first IDs).
For each feature:
docs/specs/<feature>/ directory.# Spec NN — Title / # <stuff> with # Spec: <feature> (matching the v2 template).## 1. Goal / ## Goal → keep as ## 1. Goal.## 2. Requirements → ## 2. Requirements.## 3. User Stories / ## User Stories → renumber to ## 4. User stories (move down — Section 4 in v2 template).## Tables / ## Schema / ## Design / ## Technical content → consolidate under ## 5. Technical details.## Out of scope → ## 6. Out of scope.## Edge cases / ## Open questions → ## 7. Edge cases / open questions.## Done-when sub-bullets per story → become ## 8. Validation steps with VS-N IDs.## 3. User Stories (now ## 4. User stories) section, find every - As a <role>, ... bullet.- **US-N**: As a <role>, .... Increment the counter.## 3. Acceptance Criteria section above it:
**AC-N.1:** <action paraphrased> succeeds — <observable success state from the story's "so that <benefit>" clause OR a TODO placeholder>.**AC-N.2:** <invalid input> is rejected. TODO(needs discriminating signal — see /spec-tests-first:init warning at end).todos_found list for the C.6 final warning.docs/specs/<feature>/spec.md with all sections in v2 order.After this step, all feature specs are restructured but spec-status.md files don't exist yet — those come in C.5 (two-signal scan).
For each entry in project_docs_to_move:
docs/<slug>.md (top-level docs, not under specs/).git mv if the file is tracked (preserves history); otherwise Write + delete the source.Print: Moved <count> project-level docs to docs/.
Run the Shared step — Codebase-map seeding (Steps M.1 through M.6). Same as Case B.
For each migrated feature, determine proposed Phase 2 status using two concrete signals.
Signal 1 — Source-file presence.
Read the feature's spec.md Section 5 (Technical details). Extract any file paths or class/module names mentioned (e.g. LoginController.java, auth/login.js, UserService). For each:
present vs not present.If Section 5 doesn't name any files → record Section 5 silent — files unknown.
Signal 2 — Test-file presence.
Per the resolved test layout profile:
feature_anchor: directory — Glob the tests_root substituted with this feature name (e.g. tests/auth/**). Count files.feature_anchor: co-located — Grep across the codebase for test files (*_test.go, *.spec.ts, *Test.java) whose names contain any of this feature's US-N or AC-N.M IDs.Count test files found.
Build the proposed-status table:
Detected implementation status for migrated features:
| feature | source files | tests | proposed Phase 2 status |
|------------------|------------------|---------|--------------------------|
| auth | 3/3 present | 2 files | done |
| user-and-skills | 5/5 present | 4 files | done |
| billing | 1/3 present | 0 files | pending (partial impl) |
| admin-dashboard | 0/2 present | 0 files | pending (not started) |
| frontend | 8/8 present | 0 files | done — tests TBD |
| reports | Section 5 silent | 0 files | pending |
Threshold rules:
source_files >= 50% present AND tests > 0 → donesource_files >= 50% present AND tests == 0 → done — tests TBDsource_files < 50% present → pending (partial impl)AskUserQuestion (single batch confirmation):
Choose:
- (a, recommended) Accept all proposed statuses
- (b) Override per feature — I'll walk you through each
- (c) Mark all as pending (conservative — you'll /spec-tests-first:build each to verify)
- (d) Cancel migration (rollback all init changes)
- Optional: (e) Run tests for done-status features now (confirms test pass) — could be slow
On (a): apply proposed statuses; proceed to spec-status.md writing.
On (b): for each feature, AskUserQuestion individually with current proposed status as default.
On (c): override all to pending.
On (d): delete all created subdirectory specs + restore flat files (use git checkout -- docs/specs/ if tracked; else manual file deletion). Stop.
On (e): for each proposed-done feature, dispatch the test-runner subagent with that feature's tests. If failed/errored are empty → keep done. If non-empty → downgrade to stale and note the failure count.
After the confirmation, for each migrated feature, write docs/specs/<feature>/spec-status.md:
# spec-status: <feature>
Last updated: <YYYY-MM-DD>
Latest review: (none yet)
## Phase progress
| Phase | Status | Updated | Notes |
|-----------------|-------------|--------------|------------------------------------|
| 1. spec | done | <YYYY-MM-DD> | migrated by /spec-tests-first:init |
| 2. build | <X> | <YYYY-MM-DD> | <"existing impl detected" / "—"> |
| 3. review | pending | — | — |
| 4. fix | pending | — | — |
| 5. validate | pending | — | — |
| 6. ship | pending | — | — |
## Status per Acceptance Criterion
| AC-ID | Status | Notes |
|---|---|---|
Phase 2 Status comes from C.5's confirmation. Per-AC table:
done → all ACs = done.pending → all ACs = not-started.stale (from (e) option) → all ACs = stale.Print the consolidated warnings:
/spec-tests-first:init migration complete for `<project>`.
Migrated features (<N>): <list>
Project-level docs moved: <list>
Codebase-map.md seeded with <K> files (fill in <!-- TODO --> role descriptions)
⚠ <T> error-path ACs need discriminating signals before /spec-tests-first:build can RGR-iterate them:
- docs/specs/auth/spec.md: AC-1.2 — TODO(needs discriminating signal)
- docs/specs/billing/spec.md: AC-2.3 — TODO(needs discriminating signal)
...
Next steps (in order):
1. Open the migrated spec files and fill in TODO discriminating signals (or remove error-path ACs if not applicable).
2. Fill in <!-- TODO --> role descriptions in docs/codebase-map.md.
3. Run /spec-tests-first:status to confirm everything looks right.
4. For features marked Phase 2 = pending or stale: /spec-tests-first:build <feature> to implement / re-verify.
5. For features marked Phase 2 = done: /spec-tests-first:review <feature> to bring under SDD review.
Finally, AskUserQuestion:
Migration verified. Delete the original flat spec files at
docs/specs/*.md?
- (a) Yes — they're superseded by the subdirectory specs
- (b, recommended) No — keep them as
*.md.pre-initfor safety; I'll delete them when satisfied
On (a): delete the flat files.
On (b): rename them to *.md.pre-init (use git mv if tracked, else regular rename).
Done.
/spec-tests-first:init must be safe to re-run:
| Condition | Behavior |
|---|---|
docs/specs/<feature>/spec.md already exists | Skip; print Skipping <feature> — already migrated. |
docs/specs/<feature>/spec-status.md already exists | Skip; preserve all status data (it may contain live tracking from /spec-tests-first:build). |
docs/codebase-map.md already exists with rows | AskUserQuestion: append new files / skip / abort. Never overwrite. |
CLAUDE.md ## Test commands already exists | Skip Step S.2's prompt; use existing config. |
CLAUDE.md ## Test layout already exists | Skip Step S.3's prompt; use existing config. |
| US-N IDs already present in flat spec.md | Don't reassign; use as-is. |
| Mixed state (some specs migrated, others flat) | Process only the still-flat ones; skip migrated. |
| Project is already initialized (subdir specs + no flat specs) | Print status summary; stop without changes. |
| Thought | Reality |
|---|---|
| "I'll overwrite the existing spec-status.md to add the Phase progress block" | Never. It may contain live /spec-tests-first:build / /spec-tests-first:fix tracking. Skip the feature and move on; the user can add the Phase block manually if needed. |
| "The flat spec doesn't have a User Stories section — I'll just write an empty Section 4" | If a flat file has no stories, it's likely a project-level doc, not a feature spec. Move it to docs/<name>.md. |
| "I'll guess discriminating signals for error-path ACs" | Never. Use TODO(needs discriminating signal — see /spec-tests-first:init warning at end) and surface the TODO in C.6's final warnings. The user supplies the signal. |
| "The user said cancel but I've already written half the migration — I'll keep what's done" | On cancel (D in C.5), revert ALL changes from this run. Use git checkout -- docs/specs/ and delete created subdirectories. Restore the working tree. |
| "I'll auto-delete the flat files after migration without asking" | Never. AskUserQuestion in C.6. Default to renaming to *.md.pre-init (safer). |
| "Test files I find don't have AC-IDs — I'll skip the test signal" | If pre-init tests exist but lack AC-IDs (because they predate v2), count them as "test files present" but flag that they'll need renaming when /spec-tests-first:build iterates this feature. Don't drop the signal. |
spec.md, spec-status.md, or non-empty codebase-map.md without explicit append/replace approval./spec-tests-first:build can iterate the migrated specs./spec-tests-first:build, /spec-tests-first:review, /spec-tests-first:fix, /spec-tests-first:validate, or /spec-tests-first:ship from this skill. Init is preparatory; downstream phases are user-triggered.git is required; nothing else (this skill is fully self-contained).npx claudepluginhub akhiranandha/custom-claude-plugins --plugin spec-tests-firstGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.