Writing Specs
Create and maintain specification items — the source of truth for what to build. This skill covers spec structure, writing good acceptance criteria, using traits, and organizing specs in the hierarchy.
When to Use
- Creating a new feature, requirement, or constraint spec
- Adding or refining acceptance criteria
- Applying traits for cross-cutting behaviors
- Organizing specs under the right module/parent
- Reviewing spec quality before deriving tasks
Not for: Task management (use $kspec-task-work), plan-to-spec translation (use $kspec-plan), or triage (use $kspec-triage).
Finding Things
Use CLI commands to discover and inspect specs. Do NOT search .kspec/ YAML files manually.
| Need | Command |
|---|---|
| View spec + all ACs | kspec item get @ref |
| Search by keyword | kspec search "keyword" |
| List by type | kspec item list --type feature |
| All modules | kspec item list --type module |
| All traits | kspec trait list |
| Trait details + ACs | kspec item get @trait-slug |
| Items under a parent | kspec item list --under @parent |
Core Principles
- Spec defines WHAT, not HOW — Describe the desired behavior, not the implementation
- Every spec needs AC — A spec without acceptance criteria is incomplete
- Given/When/Then is testable — Each AC should map to at least one test
- Traits eliminate duplication — Cross-cutting concerns belong in traits, not copied across specs
- Use CLI, not YAML — All changes through
kspeccommands for auto-commit
Spec Hierarchy
Specs live in modules and form a tree:
module (organizational grouping)
├── feature (user-facing capability)
│ ├── requirement (specific testable behavior)
│ └── constraint (limitation or boundary)
├── feature
│ └── requirement
└── decision (architectural choice, ADR-style)
Choosing the Right Type
| Type | Use when | Example |
|---|---|---|
module |
Grouping related features | "CLI Commands", "Web UI", "Schema" |
feature |
User-facing capability | "JSON Export", "Inbox Triage", "Shadow Sync" |
requirement |
Specific testable behavior within a feature | "Export validates output format", "Triage records audit trail" |
constraint |
Non-functional limit or boundary | "Response time < 200ms", "Max 1000 items per module" |
decision |
Architectural choice with rationale | "Use YAML over JSON for spec files" |
trait |
Reusable AC bundle for cross-cutting behaviors | "JSON output mode", "Confirmation prompts" |
Rule of thumb: If it has acceptance criteria that a user could verify, it's a feature or requirement. If it constrains how something works, it's a constraint. If multiple specs need the same behavior, extract a trait.
Writing Acceptance Criteria
AC are the heart of a spec. They define what "done" means.
Format
Given: precondition (state before the action)
When: action (what triggers the behavior)
Then: outcome (observable, verifiable result)
Good AC Patterns
Specific and testable:
kspec item ac add @json-export \
--given "user has 3 tasks in project" \
--when "user runs 'kspec tasks list --json'" \
--then "stdout contains valid JSON array with 3 task objects"
Covers error cases:
kspec item ac add @json-export \
--given "project has no tasks" \
--when "user runs 'kspec tasks list --json'" \
--then "stdout contains empty JSON array []"
Boundary behavior:
kspec item ac add @bulk-delete \
--given "user passes 50 refs (maximum supported)" \
--when "user runs bulk delete" \
--then "all 50 items deleted in single operation"
AC Anti-patterns
| Anti-pattern | Problem | Better |
|---|---|---|
| "System works correctly" | Not testable | Describe specific observable outcome |
| "User is happy" | Subjective | Describe what they can do or see |
| "Fast performance" | Not measurable | "Response returns within 200ms" |
| "Handles errors" | Vague | Specific error scenario + expected behavior |
| Duplicating trait AC | Maintenance burden | Apply the trait instead |
| Implementation details | Not behavioral | Describe observable outcome, not internal mechanism |
| Rationale or commentary | Not testable | Move to description or implementation notes |
Behavioral Language Rules
Specs are standalone behavioral contracts. They must read as timeless descriptions of what the system does, not how it's built. A spec should make sense to someone who has never seen the codebase.
ACs describe observable behavior, not implementation: ACs should describe what happens from the outside — what a user or consumer observes. They should not reveal how the system achieves the behavior internally. This includes internal field/variable names, protocol details, internal function calls, file formats, library names, and architectural terms. Use natural language to describe the same concept.
Examples of implementation leaking into ACs:
- Internal field names (e.g.
record_id,event_count) → "record identifier", "event count" - Protocol or transport names (e.g. specific RPC, message-bus, or socket protocol names) → describe the behavior they enable
- Internal function or method calls (e.g.
client.action(),session.close()) → "the action is delivered", "the session closes" - Internal file paths (e.g.
events.jsonl) → "the recorded event history"
ACs contain only assertions, not rationale: Each AC should state what happens, not why it happens or how it relates to other concerns. Rationale, design context, backward compatibility notes, and cross-references to other specs belong in the description or implementation notes — not in the given/when/then.
Examples of commentary leaking into ACs:
- "the request completes; this preserves backward compatibility" → "the request completes"
- "per @other-spec" → use
depends_onorrelates_tofields - "unlike the previous behavior" → just state the current behavior
Descriptions follow the same principles — describe behavior and purpose, not implementation approach. Implementation guidance belongs in task descriptions or implementation notes.
Good:
Given: A user has an existing record
When: The user runs the delete command
Then: The record is removed and the deletion is confirmed
Bad:
Given: store.records[id].state === "active" and no pending writes target it
When: deleteRecord() resolves
Then: store.records[id] is removed via the standard teardown sequence; this preserves backward compatibility
AC Naming Convention
AC IDs are auto-generated (ac-1, ac-2, ...) or can be explicit:
# Auto-generated
kspec item ac add @feature --given "..." --when "..." --then "..."
# Explicit ID for clarity
kspec item ac add @feature --id ac-json-valid --given "..." --when "..." --then "..."
How Many ACs?
- Minimum 1 — Every spec needs at least one
- Typical: 2-5 — Happy path + key error cases
- If 8+ — Consider splitting the spec into smaller requirements
- Each AC = one behavior — Don't combine multiple verifiable outcomes
Working with Traits
Traits are reusable bundles of acceptance criteria. When a spec implements a trait, it inherits all the trait's ACs.
Traits are not shipped by kspec init — each project defines its own trait catalog. Always start by listing the project's traits before applying or extracting one.
When to Use Traits
Apply a trait when a spec needs a cross-cutting behavior that is already represented in the project's catalog:
# Discover the project's trait catalog
kspec trait list
# View trait details (shows ACs that will be inherited)
kspec trait get @<trait-slug>
# Apply trait to spec
kspec item trait add @my-spec @<trait-slug>
# Apply multiple traits
kspec item trait add @my-spec @<trait-slug-a> @<trait-slug-b>
Picking Traits
Scan the catalog returned by kspec trait list and look for traits whose cross-cutting behavior the spec implements. Typical categories you might see in a project's catalog include things like output formatting, destructive-operation prompts, filterable lists, error guidance, batch references, or shadow-state mutation. Use whatever traits the project defines — the catalog is project-specific.
Creating New Traits
If 3+ specs need the same cross-cutting behavior and no existing trait covers it, consider extracting a trait:
# Create the trait
kspec trait add "Trait Name" --description "Brief description of the cross-cutting behavior" --slug <trait-slug>
# Add ACs to the trait
kspec item ac add @<trait-slug> --given "..." --when "..." --then "..."
Trait AC Coverage
When implementing specs with traits, all inherited ACs must be covered by tests:
// AC: @<trait-slug> ac-1
it('exhibits the trait-defined behavior', () => { ... });
Run kspec validate to check for uncovered trait ACs.
Creating Specs
New Feature Under a Module
# 1. Find the right parent module
kspec item list --type module
# 2. Create the feature
kspec item add --under @cli-module --title "Bulk Operations" --type feature --slug bulk-ops
# 3. Add description
kspec item set @bulk-ops --description "Support batch operations on multiple items in a single command"
# 4. Add acceptance criteria
kspec item ac add @bulk-ops \
--given "user provides 3 item refs" \
--when "user runs bulk delete" \
--then "all 3 items deleted and confirmation shown"
kspec item ac add @bulk-ops \
--given "one of 3 refs is invalid" \
--when "user runs bulk delete" \
--then "error reported for invalid ref, valid refs still processed"
# 5. Apply relevant traits from the project's catalog (kspec trait list)
kspec item trait add @bulk-ops @<trait-slug-a> @<trait-slug-b>
# 6. Validate
kspec validate
Requirement Under a Feature
kspec item add --under @bulk-ops --title "Ref validation in batch mode" --type requirement --slug bulk-ref-validation
kspec item ac add @bulk-ref-validation \
--given "batch contains mix of valid and invalid refs" \
--when "batch executes" \
--then "report lists each ref with success/failure status"
Updating Existing Specs
# View current state
kspec item get @feature-slug
# Update description
kspec item set @feature-slug --description "Updated description"
# Add missing AC
kspec item ac add @feature-slug --given "..." --when "..." --then "..."
# Update existing AC
kspec item ac set @feature-slug ac-2 --then "updated expected outcome"
# Mark implementation status
kspec item set @feature-slug --status implemented
# Add relationships
kspec item set @feature-slug --depends-on @other-feature
kspec item set @feature-slug --relates-to @related-item
Spec Quality Checklist
Before deriving a task from a spec, verify:
- Description — Explains what and why (not how)
- AC coverage — At least happy path + primary error case
- AC testability — Each AC maps to a concrete test
- Traits applied — Cross-cutting behaviors use traits, not duplicated AC
- Correct parent — Placed under the right module/feature
- No implementation details — AC describes behavior, not code structure
- Validation passes —
kspec validatereports no errors for this item
Validation
# Full validation
kspec validate
# Completeness check
kspec validate --completeness
# Spec-task alignment
kspec validate --alignment
# Strict mode (warnings → errors)
kspec validate --strict
Exit codes: 0 = success, 4 = errors, 6 = warnings only.
Validation catches:
- Missing acceptance criteria
- Broken references (
@slugpointing to nonexistent items) - Missing descriptions
- Orphaned specs (no linked tasks)
- Uncovered trait ACs
Command Reference
Item Management
kspec item list [--type <type>] # List items
kspec item get <ref> # Get item details with ACs and traits
kspec item add --under <parent> --title "..." --type <type> [--slug <slug>]
kspec item set <ref> --title "..." # Update fields
kspec item set <ref> --description "..."
kspec item set <ref> --status <status> # implementation status
kspec item set <ref> --depends-on <ref>
kspec item set <ref> --relates-to <ref>
kspec item patch <ref> --data '{...}' # Complex updates
kspec item delete <ref> [--force]
Acceptance Criteria
kspec item ac list <ref> # List ACs for item
kspec item ac add <ref> --given "..." --when "..." --then "..."
kspec item ac add <ref> --id <id> --given "..." --when "..." --then "..."
kspec item ac set <ref> <ac-id> --then "updated"
kspec item ac remove <ref> <id> [--force]
Traits
kspec trait list # All traits with AC counts
kspec trait get <ref> # Trait details
kspec trait add "Name" --description "..." [--slug <slug>]
kspec item trait add <spec> <trait> [<trait2> ...]
kspec item trait remove <spec> <trait> [<trait2> ...]
Deriving Tasks
Once a spec is ready, derive a task to track implementation:
kspec derive @feature-slug # Create task linked to spec
kspec derive @feature-slug --priority 2
The derived task gets spec_ref: @feature-slug automatically.
Integration
$kspec-plan— Plans create specs via import or manual creation$kspec-task-work— Tasks reference specs; AC guides implementation$kspec-triage— Inbox items may reveal spec gaps$kspec-observe— Friction may indicate missing specs$kspec-review— Reviews check AC coverage