If multiple capabilities are affected, create multiple delta files under `changes/[change-id]/specs/<capability>/spec.md`—one per capability.
4.**Create tasks.md:**
```markdown
## 1. Implementation
- [ ] 1.1 Create database schema
- [ ] 1.2 Implement API endpoint
- [ ] 1.3 Add frontend component
- [ ] 1.4 Write tests
```
5.**Create design.md when needed:**
Create `design.md` if any of the following apply; otherwise omit it:
- Cross-cutting change (multiple services/modules) or a new architectural pattern
- New external dependency or significant data model changes
- Security, performance, or migration complexity
- Ambiguity that benefits from technical decisions before coding
Minimal `design.md` skeleton:
```markdown
## Context
[Background, constraints, stakeholders]
## Goals / Non-Goals
- Goals: [...]
- Non-Goals: [...]
## Decisions
- Decision: [What and why]
- Alternatives considered: [Options + rationale]
## Risks / Trade-offs
- [Risk] → Mitigation
## Migration Plan
[Steps, rollback]
## Open Questions
- [...]
```
## Spec File Format
### Critical: Scenario Formatting
**CORRECT** (use #### headers):
```markdown
#### Scenario: User login success
-**WHEN** valid credentials provided
-**THEN** return JWT token
```
**WRONG** (don't use bullets or bold):
```markdown
-**Scenario: User login** ❌
**Scenario**: User login ❌
### Scenario: User login ❌
```
Every requirement MUST have at least one scenario.
### Requirement Wording
- Use SHALL/MUST for normative requirements (avoid should/may unless intentionally non-normative)
### Delta Operations
-`## ADDED Requirements` - New capabilities
-`## MODIFIED Requirements` - Changed behavior
-`## REMOVED Requirements` - Deprecated features
-`## RENAMED Requirements` - Name changes
Headers matched with `trim(header)` - whitespace ignored.
#### When to use ADDED vs MODIFIED
- ADDED: Introduces a new capability or sub-capability that can stand alone as a requirement. Prefer ADDED when the change is orthogonal (e.g., adding "Slash Command Configuration") rather than altering the semantics of an existing requirement.
- MODIFIED: Changes the behavior, scope, or acceptance criteria of an existing requirement. Always paste the full, updated requirement content (header + all scenarios). The archiver will replace the entire requirement with what you provide here; partial deltas will drop previous details.
- RENAMED: Use when only the name changes. If you also change behavior, use RENAMED (name) plus MODIFIED (content) referencing the new name.
Common pitfall: Using MODIFIED to add a new concern without including the previous text. This causes loss of detail at archive time. If you aren’t explicitly changing the existing requirement, add a new requirement under ADDED instead.
Authoring a MODIFIED requirement correctly:
1) Locate the existing requirement in `openspec/specs/<capability>/spec.md`.
2) Copy the entire requirement block (from `### Requirement: ...` through its scenarios).
3) Paste it under `## MODIFIED Requirements` and edit to reflect the new behavior.
4) Ensure the header text matches exactly (whitespace-insensitive) and keep at least one `#### Scenario:`.
Example for RENAMED:
```markdown
## RENAMED Requirements
- FROM: `### Requirement: Login`
- TO: `### Requirement: User Authentication`
```
## Troubleshooting
### Common Errors
**"Change must have at least one delta"**
- Check `changes/[name]/specs/` exists with .md files
- Verify files have operation prefixes (## ADDED Requirements)
**"Requirement must have at least one scenario"**
- Check scenarios use `#### Scenario:` format (4 hashtags)
- Don't use bullet points or bold for scenario headers
**Silent scenario parsing failures**
- Exact format required: `#### Scenario: Name`
- Debug with: `openspec show [change] --json --deltas-only`
### Validation Tips
```bash
# Always use strict mode for comprehensive checks
openspec validate [change] --strict
# Debug delta parsing
openspec show [change] --json | jq '.deltas'
# Check specific requirement
openspec show [spec] --json-r 1
```
## Happy Path Script
```bash
# 1) Explore current state
openspec spec list --long
openspec list
# Optional full-text search:
# rg -n "Requirement:|Scenario:" openspec/specs
# rg -n "^#|Requirement:" openspec/changes
# 2) Choose change id and scaffold
CHANGE=add-two-factor-auth
mkdir-p openspec/changes/$CHANGE/{specs/auth}
printf"## Why\n...\n\n## What Changes\n- ...\n\n## Impact\n- ...\n"> openspec/changes/$CHANGE/proposal.md
-**表名**:使用 `users`、`api_keys`、`groups`、`accounts`、`account_groups`、`proxies`、`redeem_codes`、`settings`、`user_subscriptions`、`usage_logs` 等现有名称(不要让 Ent 默认命名生成新表)。
-**ID 类型**:现有主键是 `BIGSERIAL`,建议 Ent 中统一用 `int64`(避免 Go 的 `int` 在 32-bit 环境或跨系统时产生隐性问题)。
-**时间字段**:`created_at/updated_at/deleted_at` 均为 `TIMESTAMPTZ`,schema 中应显式声明 DB 类型,避免生成 `timestamp without time zone` 导致行为变化。
### A.2 代码生成与 feature flags(必须写死)
建议在 `backend/ent/generate.go` 固化生成命令(示例):
```go
//go:build ignore
packageent
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature intercept --feature sql/upsert ./schema
The system MUST manage database schema changes via versioned SQL migration files under `backend/migrations/*.sql` and MUST record applied migrations in the database for auditability and idempotency.
#### Scenario: Migrations are applied idempotently
-**GIVEN** an empty PostgreSQL database
-**WHEN** the backend initializes its database connection
-**THEN** it MUST apply all SQL migrations in lexicographic filename order
-**AND** it MUST record each applied migration in `schema_migrations` with a checksum
-**AND** a subsequent initialization MUST NOT re-apply already-recorded migrations
### Requirement: Soft Delete Semantics
For entities that support soft delete, the system MUST preserve the existing semantics: soft-deleted rows are excluded from queries by default, and delete operations are idempotent.
#### Scenario: Soft-deleted rows are hidden by default
-**GIVEN** a row has `deleted_at` set
-**WHEN** the backend performs a standard "list" or "get" query
-**THEN** the row MUST NOT be returned by default
### Requirement: Allowed Groups Data Model
The system MUST migrate `users.allowed_groups` from a PostgreSQL array column to a normalized join table for type safety and maintainability.
#### Scenario: Allowed groups are represented as relationships
-**GIVEN** a user is allowed to bind a group
-**WHEN** the user/group association is stored
-**THEN** it MUST be stored as a `(user_id, group_id)` relationship row
-**AND** removing an association MUST hard-delete that relationship row