mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-04 06:18:24 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
313afbb4c7 | ||
|
|
e4a02174f3 | ||
|
|
ce79ec0bab |
@@ -140,6 +140,8 @@ dist/
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# Internal Docs
|
||||
docs/
|
||||
|
||||
# Claude
|
||||
.claude/
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
# @fission-ai/openspec
|
||||
|
||||
## 0.17.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 455c65f: Fix `--no-interactive` flag in validate command to properly disable spinner, preventing hangs in pre-commit hooks and CI environments
|
||||
|
||||
## 0.17.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a2757e7: Fix pre-commit hook hang issue in config command by using dynamic import for @inquirer/prompts
|
||||
|
||||
The config command was causing pre-commit hooks to hang indefinitely due to stdin event listeners being registered at module load time. This fix converts the static import to a dynamic import that only loads inquirer when the `config reset` command is actually used interactively.
|
||||
|
||||
Also adds ESLint with a rule to prevent static @inquirer imports, avoiding future regressions.
|
||||
|
||||
## 0.17.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,597 +0,0 @@
|
||||
# POC-OpenSpec-Core Analysis
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions & Terminology
|
||||
|
||||
### Philosophy: Not a Workflow System
|
||||
|
||||
This system is **not** a workflow engine. It's an **artifact tracker with dependency awareness**.
|
||||
|
||||
| What it's NOT | What it IS |
|
||||
|---------------|------------|
|
||||
| Linear step-by-step progression | Exploratory, iterative planning |
|
||||
| Bureaucratic checkpoints | Enablers that unlock possibilities |
|
||||
| "You must complete step 1 first" | "Here's what you could create now" |
|
||||
| Form-filling | Fluid document creation |
|
||||
|
||||
**Key insight:** Dependencies are *enablers*, not *gates*. You can't meaningfully write a design document if there's no proposal to design from - that's not bureaucracy, it's logic.
|
||||
|
||||
### Terminology
|
||||
|
||||
| Term | Definition | Example |
|
||||
|------|------------|---------|
|
||||
| **Change** | A unit of work being planned (feature, refactor, migration) | `openspec/changes/add-auth/` |
|
||||
| **Schema** | An artifact graph definition (what artifacts exist, their dependencies) | `spec-driven.yaml` |
|
||||
| **Artifact** | A node in the graph (a document to create) | `proposal`, `design`, `specs` |
|
||||
| **Template** | Instructions/guidance for creating an artifact | `templates/proposal.md` |
|
||||
|
||||
### Hierarchy
|
||||
|
||||
```
|
||||
Schema (defines) ──→ Artifacts (guided by) ──→ Templates
|
||||
```
|
||||
|
||||
- **Schema** = the artifact graph (what exists, dependencies)
|
||||
- **Artifact** = a document to produce
|
||||
- **Template** = instructions for creating that artifact
|
||||
|
||||
### Schema Variations
|
||||
|
||||
Schemas can vary across multiple dimensions:
|
||||
|
||||
| Dimension | Examples |
|
||||
|-----------|----------|
|
||||
| Philosophy | `spec-driven`, `tdd`, `prototype-first` |
|
||||
| Version | `v1`, `v2`, `v3` |
|
||||
| Language | `en`, `zh`, `es` |
|
||||
| Custom | `team-alpha`, `experimental` |
|
||||
|
||||
### Schema Resolution (XDG Standard)
|
||||
|
||||
Schemas follow the XDG Base Directory Specification with a 2-level resolution:
|
||||
|
||||
```
|
||||
1. ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml # Global user override
|
||||
2. <package>/schemas/<name>/schema.yaml # Built-in defaults
|
||||
```
|
||||
|
||||
**Platform-specific paths:**
|
||||
- Unix/macOS: `~/.local/share/openspec/schemas/`
|
||||
- Windows: `%LOCALAPPDATA%/openspec/schemas/`
|
||||
- All platforms: `$XDG_DATA_HOME/openspec/schemas/` (when set)
|
||||
|
||||
**Why XDG?**
|
||||
- Schemas are workflow definitions (data), not user preferences (config)
|
||||
- Built-ins baked into package, never auto-copied
|
||||
- Users customize by creating files in global data dir
|
||||
- Consistent with modern CLI tooling standards
|
||||
|
||||
### Template Inheritance (2 Levels Max)
|
||||
|
||||
Templates are co-located with schemas in a `templates/` subdirectory:
|
||||
|
||||
```
|
||||
1. ${XDG_DATA_HOME}/openspec/schemas/<schema>/templates/<artifact>.md # User override
|
||||
2. <package>/schemas/<schema>/templates/<artifact>.md # Built-in
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- User overrides take precedence over package built-ins
|
||||
- A CLI command shows resolved paths (no guessing)
|
||||
- No inheritance between schemas (copy if you need to diverge)
|
||||
- Templates are always co-located with their schema
|
||||
|
||||
**Why this matters:**
|
||||
- Avoids "where does this come from?" debugging
|
||||
- No implicit magic that works until it doesn't
|
||||
- Schema + templates form a cohesive unit
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This is an **artifact tracker with dependency awareness** that guides iterative development through a structured artifact pipeline. The core innovation is using the **filesystem as a database** - artifact completion is detected by file existence, making the system stateless and version-control friendly.
|
||||
|
||||
The system answers:
|
||||
- "What artifacts exist for this change?"
|
||||
- "What could I create next?" (not "what must I create")
|
||||
- "What's blocking X?" (informational, not prescriptive)
|
||||
|
||||
---
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. ArtifactGraph (Slice 1 - COMPLETE)
|
||||
|
||||
The dependency graph engine with XDG-compliant schema resolution.
|
||||
|
||||
| Responsibility | Approach |
|
||||
|----------------|----------|
|
||||
| Model artifacts as a DAG | Artifact with `requires: string[]` |
|
||||
| Track completion state | `Set<string>` for completed artifacts |
|
||||
| Calculate build order | Kahn's algorithm (topological sort) |
|
||||
| Find ready artifacts | Check if all dependencies are in `completed` set |
|
||||
| Resolve schemas | XDG global → package built-ins |
|
||||
|
||||
**Key Data Structures (Zod-validated):**
|
||||
|
||||
```typescript
|
||||
// Zod schemas define types + validation
|
||||
const ArtifactSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
generates: z.string().min(1), // e.g., "proposal.md" or "specs/*.md"
|
||||
description: z.string(),
|
||||
template: z.string(), // path to template file
|
||||
requires: z.array(z.string()).default([]),
|
||||
});
|
||||
|
||||
const SchemaYamlSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
version: z.number().int().positive(),
|
||||
description: z.string().optional(),
|
||||
artifacts: z.array(ArtifactSchema).min(1),
|
||||
});
|
||||
|
||||
// Derived types
|
||||
type Artifact = z.infer<typeof ArtifactSchema>;
|
||||
type SchemaYaml = z.infer<typeof SchemaYamlSchema>;
|
||||
```
|
||||
|
||||
**Key Methods:**
|
||||
- `resolveSchema(name)` - Load schema with XDG fallback
|
||||
- `ArtifactGraph.fromSchema(schema)` - Build graph from schema
|
||||
- `detectState(graph, changeDir)` - Scan filesystem for completion
|
||||
- `getNextArtifacts(graph, completed)` - Find artifacts ready to create
|
||||
- `getBuildOrder(graph)` - Topological sort of all artifacts
|
||||
- `getBlocked(graph, completed)` - Artifacts with unmet dependencies
|
||||
|
||||
---
|
||||
|
||||
### 2. Change Utilities (Slice 2)
|
||||
|
||||
Simple utility functions for programmatic change creation. No class, no abstraction layer.
|
||||
|
||||
| Responsibility | Approach |
|
||||
|----------------|----------|
|
||||
| Create changes | Create dirs under `openspec/changes/<name>/` with README |
|
||||
| Name validation | Enforce kebab-case naming |
|
||||
|
||||
**Key Paths:**
|
||||
|
||||
```
|
||||
openspec/changes/<name>/ → Change instances with artifacts (project-level)
|
||||
```
|
||||
|
||||
**Key Functions** (`src/utils/change-utils.ts`):
|
||||
- `createChange(projectRoot, name, description?)` - Create new change directory + README
|
||||
- `validateChangeName(name)` - Validate kebab-case naming, returns `{ valid, error? }`
|
||||
|
||||
**Note:** Existing CLI commands (`ListCommand`, `ChangeCommand`) already handle listing, path resolution, and existence checks. No need to extract that logic - it works fine as-is.
|
||||
|
||||
---
|
||||
|
||||
### 3. InstructionLoader (Slice 3)
|
||||
|
||||
Template resolution and instruction enrichment.
|
||||
|
||||
| Responsibility | Approach |
|
||||
|----------------|----------|
|
||||
| Resolve templates | XDG 2-level fallback (schema-specific → shared → built-in) |
|
||||
| Build dynamic context | Gather dependency status, change info |
|
||||
| Enrich templates | Inject context into base templates |
|
||||
| Generate status reports | Formatted markdown with progress |
|
||||
|
||||
**Key Class - ChangeState:**
|
||||
|
||||
```
|
||||
ChangeState {
|
||||
changeName: string
|
||||
changeDir: string
|
||||
graph: ArtifactGraph
|
||||
completed: Set<string>
|
||||
|
||||
// Methods
|
||||
getNextSteps(): string[]
|
||||
getStatus(artifactId): ArtifactStatus
|
||||
isComplete(): boolean
|
||||
}
|
||||
```
|
||||
|
||||
**Key Functions:**
|
||||
- `getTemplatePath(artifactId, schemaName?)` - Resolve with 2-level fallback
|
||||
- `getEnrichedInstructions(artifactId, projectRoot, changeName?)` - Main entry point
|
||||
- `getChangeStatus(projectRoot, changeName?)` - Formatted status report
|
||||
|
||||
---
|
||||
|
||||
### 4. CLI (Slice 4)
|
||||
|
||||
User interface layer. **All commands are deterministic** - require explicit `--change` parameter.
|
||||
|
||||
| Command | Function | Status |
|
||||
|---------|----------|--------|
|
||||
| `status --change <id>` | Show change progress (artifact graph) | **NEW** |
|
||||
| `next --change <id>` | Show artifacts ready to create | **NEW** |
|
||||
| `instructions <artifact> --change <id>` | Get enriched instructions for artifact | **NEW** |
|
||||
| `list` | List all changes | EXISTS (`openspec change list`) |
|
||||
| `new <name>` | Create change | **NEW** (uses `createChange()`) |
|
||||
| `init` | Initialize structure | EXISTS (`openspec init`) |
|
||||
| `templates --change <id>` | Show resolved template paths | **NEW** |
|
||||
|
||||
**Note:** Commands that operate on a change require `--change`. Missing parameter → error with list of available changes. Agent infers the change from conversation and passes it explicitly.
|
||||
|
||||
**Existing CLI commands** (not part of this slice):
|
||||
- `openspec change list` / `openspec change show <id>` / `openspec change validate <id>`
|
||||
- `openspec list --changes` / `openspec list --specs`
|
||||
- `openspec view` (dashboard)
|
||||
- `openspec init` / `openspec archive <change>`
|
||||
|
||||
---
|
||||
|
||||
### 5. Claude Commands
|
||||
|
||||
Integration layer for Claude Code. **Operational commands only** - artifact creation via natural language.
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `/status` | Show change progress |
|
||||
| `/next` | Show what's ready to create |
|
||||
| `/run [artifact]` | Execute a specific step (power users) |
|
||||
| `/list` | List all changes |
|
||||
| `/new <name>` | Create a new change |
|
||||
| `/init` | Initialize structure |
|
||||
|
||||
**Artifact creation:** Users say "create the proposal" or "write the tests" in natural language. The agent:
|
||||
1. Infers change from conversation (confirms if uncertain)
|
||||
2. Infers artifact from request
|
||||
3. Calls CLI with explicit `--change` parameter
|
||||
4. Creates artifact following instructions
|
||||
|
||||
This works for ANY artifact in ANY schema - no new slash commands needed when schemas change.
|
||||
|
||||
**Note:** Legacy commands (`/openspec-proposal`, `/openspec-apply`, `/openspec-archive`) exist in the main project for backward compatibility but are separate from this architecture.
|
||||
|
||||
---
|
||||
|
||||
## Component Dependency Graph
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ PRESENTATION LAYER │
|
||||
│ ┌──────────────┐ ┌────────────────────┐ │
|
||||
│ │ CLI │ ←─shell exec───────│ Claude Commands │ │
|
||||
│ └──────┬───────┘ └────────────────────┘ │
|
||||
└─────────┼───────────────────────────────────────────────────┘
|
||||
│ imports
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ORCHESTRATION LAYER │
|
||||
│ ┌────────────────────┐ ┌──────────────────────────┐ │
|
||||
│ │ InstructionLoader │ │ change-utils (Slice 2) │ │
|
||||
│ │ (Slice 3) │ │ createChange() │ │
|
||||
│ └─────────┬──────────┘ │ validateChangeName() │ │
|
||||
│ │ └──────────────────────────┘ │
|
||||
└────────────┼────────────────────────────────────────────────┘
|
||||
│ uses
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ CORE LAYER │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ ArtifactGraph (Slice 1) │ │
|
||||
│ │ │ │
|
||||
│ │ Schema Resolution (XDG) ──→ Graph ──→ State Detection│ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
▲
|
||||
│ reads from
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ PERSISTENCE LAYER │
|
||||
│ ┌──────────────────┐ ┌────────────────────────────────┐ │
|
||||
│ │ XDG Schemas │ │ Project Artifacts │ │
|
||||
│ │ ~/.local/share/ │ │ openspec/changes/<name>/ │ │
|
||||
│ │ openspec/ │ │ - proposal.md, design.md │ │
|
||||
│ │ schemas/ │ │ - specs/*.md, tasks.md │ │
|
||||
│ └──────────────────┘ └────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Design Patterns
|
||||
|
||||
### 1. Filesystem as Database
|
||||
|
||||
No SQLite, no JSON state files. The existence of `proposal.md` means proposal is complete.
|
||||
|
||||
```
|
||||
// State detection is just file existence checking
|
||||
if (exists(artifactPath)) {
|
||||
completed.add(artifactId)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Deterministic CLI, Inferring Agent
|
||||
|
||||
**CLI layer:** Always deterministic - requires explicit `--change` parameter.
|
||||
|
||||
```
|
||||
openspec status --change add-auth # explicit, works
|
||||
openspec status # error: "No change specified"
|
||||
```
|
||||
|
||||
**Agent layer:** Infers from conversation, confirms if uncertain, passes explicit `--change`.
|
||||
|
||||
This separation means:
|
||||
- CLI is pure, testable, no state to corrupt
|
||||
- Agent handles all "smartness"
|
||||
- No config.yaml tracking of "active change"
|
||||
|
||||
### 3. XDG-Compliant Schema Resolution
|
||||
|
||||
```
|
||||
${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml # User override
|
||||
↓ (not found)
|
||||
<package>/schemas/<name>/schema.yaml # Built-in
|
||||
↓ (not found)
|
||||
Error (schema not found)
|
||||
```
|
||||
|
||||
### 4. Two-Level Template Fallback
|
||||
|
||||
```
|
||||
${XDG_DATA_HOME}/openspec/schemas/<schema>/templates/<artifact>.md # User override
|
||||
↓ (not found)
|
||||
<package>/schemas/<schema>/templates/<artifact>.md # Built-in
|
||||
↓ (not found)
|
||||
Error (no silent fallback to avoid confusion)
|
||||
```
|
||||
|
||||
### 5. Glob Pattern Support
|
||||
|
||||
`specs/*.md` allows multiple files to satisfy a single artifact:
|
||||
|
||||
```
|
||||
if (artifact.generates.includes("*")) {
|
||||
const parentDir = changeDir / patternParts[0]
|
||||
if (exists(parentDir) && hasFiles(parentDir)) {
|
||||
completed.add(artifactId)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Stateless State Detection
|
||||
|
||||
Every command re-scans the filesystem. No cached state to corrupt.
|
||||
|
||||
---
|
||||
|
||||
## Artifact Pipeline (Default Schema)
|
||||
|
||||
The default `spec-driven` schema:
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ proposal │ (no dependencies)
|
||||
└────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│ specs │ (requires: proposal)
|
||||
└────┬─────┘
|
||||
│
|
||||
├──────────────┐
|
||||
▼ ▼
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ design │ │ │
|
||||
│ │◄──┤ proposal │
|
||||
└────┬─────┘ └──────────┘
|
||||
│ (requires: proposal, specs)
|
||||
▼
|
||||
┌──────────┐
|
||||
│ tasks │ (requires: design)
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
Other schemas (TDD, prototype-first) would have different graphs.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
Structured as **vertical slices** - each slice is independently testable.
|
||||
|
||||
---
|
||||
|
||||
### Slice 1: "What's Ready?" (Core Query) ✅ COMPLETE
|
||||
|
||||
**Delivers:** Types + Graph + State Detection + Schema Resolution
|
||||
|
||||
**Implementation:** `src/core/artifact-graph/`
|
||||
- `types.ts` - Zod schemas and derived TypeScript types
|
||||
- `schema.ts` - YAML parsing with Zod validation
|
||||
- `graph.ts` - ArtifactGraph class with topological sort
|
||||
- `state.ts` - Filesystem-based state detection
|
||||
- `resolver.ts` - XDG-compliant schema resolution
|
||||
- `builtin-schemas.ts` - Package-bundled default schemas
|
||||
|
||||
**Key decisions made:**
|
||||
- Zod for schema validation (consistent with project)
|
||||
- XDG for global schema overrides
|
||||
- `Set<string>` for completion state (immutable, functional)
|
||||
- `inProgress` and `failed` states deferred (require external tracking)
|
||||
|
||||
---
|
||||
|
||||
### Slice 2: "Change Creation Utilities"
|
||||
|
||||
**Delivers:** Utility functions for programmatic change creation
|
||||
|
||||
**Scope:**
|
||||
- `createChange(projectRoot, name, description?)` → creates directory + README
|
||||
- `validateChangeName(name)` → kebab-case pattern enforcement
|
||||
|
||||
**Not in scope (already exists in CLI commands):**
|
||||
- `listChanges()` → exists in `ListCommand` and `ChangeCommand.getActiveChanges()`
|
||||
- `getChangePath()` → simple `path.join()` inline
|
||||
- `changeExists()` → simple `fs.access()` inline
|
||||
- `isInitialized()` → simple directory check inline
|
||||
|
||||
**Why simplified:** Extracting existing CLI logic into a class would require similar refactoring of `SpecCommand` for consistency. The existing code works fine (~15 lines each). Only truly new functionality is `createChange()` + name validation.
|
||||
|
||||
---
|
||||
|
||||
### Slice 3: "Get Instructions" (Enrichment)
|
||||
|
||||
**Delivers:** Template resolution + context injection
|
||||
|
||||
**Testable behaviors:**
|
||||
- Template fallback: schema-specific → shared → built-in → error
|
||||
- Context injection: completed deps show ✓, missing show ✗
|
||||
- Output path shown correctly based on change directory
|
||||
|
||||
---
|
||||
|
||||
### Slice 4: "CLI + Integration"
|
||||
|
||||
**Delivers:** New artifact graph commands (builds on existing CLI)
|
||||
|
||||
**New commands:**
|
||||
- `status --change <id>` - Show artifact completion state
|
||||
- `next --change <id>` - Show ready-to-create artifacts
|
||||
- `instructions <artifact> --change <id>` - Get enriched template
|
||||
- `templates --change <id>` - Show resolved paths
|
||||
- `new <name>` - Create change (wrapper for `createChange()`)
|
||||
|
||||
**Already exists (not in scope):**
|
||||
- `openspec change list/show/validate` - change management
|
||||
- `openspec list --changes/--specs` - listing
|
||||
- `openspec view` - dashboard
|
||||
- `openspec init` - initialization
|
||||
|
||||
**Testable behaviors:**
|
||||
- Each new command produces expected output
|
||||
- Commands compose correctly (status → next → instructions flow)
|
||||
- Error handling for missing changes, invalid artifacts, etc.
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
# Global (XDG paths - user overrides)
|
||||
~/.local/share/openspec/ # Unix/macOS ($XDG_DATA_HOME/openspec/)
|
||||
%LOCALAPPDATA%/openspec/ # Windows
|
||||
└── schemas/ # Schema overrides
|
||||
└── custom-workflow/ # User-defined schema directory
|
||||
├── schema.yaml # Schema definition
|
||||
└── templates/ # Co-located templates
|
||||
└── proposal.md
|
||||
|
||||
# Package (built-in defaults)
|
||||
<package>/
|
||||
└── schemas/ # Built-in schema definitions
|
||||
├── spec-driven/ # Default: proposal → specs → design → tasks
|
||||
│ ├── schema.yaml
|
||||
│ └── templates/
|
||||
│ ├── proposal.md
|
||||
│ ├── design.md
|
||||
│ ├── spec.md
|
||||
│ └── tasks.md
|
||||
└── tdd/ # TDD: tests → implementation → docs
|
||||
├── schema.yaml
|
||||
└── templates/
|
||||
├── test.md
|
||||
├── implementation.md
|
||||
├── spec.md
|
||||
└── docs.md
|
||||
|
||||
# Project (change instances)
|
||||
openspec/
|
||||
└── changes/ # Change instances
|
||||
├── add-auth/
|
||||
│ ├── README.md # Auto-generated on creation
|
||||
│ ├── proposal.md # Created artifacts
|
||||
│ ├── design.md
|
||||
│ └── specs/
|
||||
│ └── *.md
|
||||
├── refactor-db/
|
||||
│ └── ...
|
||||
└── archive/ # Completed changes
|
||||
└── 2025-01-01-add-auth/
|
||||
|
||||
.claude/
|
||||
├── settings.local.json # Permissions
|
||||
└── commands/ # Slash commands
|
||||
└── *.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schema YAML Format
|
||||
|
||||
```yaml
|
||||
# Built-in: <package>/schemas/spec-driven/schema.yaml
|
||||
# Or user override: ~/.local/share/openspec/schemas/spec-driven/schema.yaml
|
||||
name: spec-driven
|
||||
version: 1
|
||||
description: Specification-driven development
|
||||
|
||||
artifacts:
|
||||
- id: proposal
|
||||
generates: "proposal.md"
|
||||
description: "Create project proposal document"
|
||||
template: "proposal.md" # resolves from co-located templates/ directory
|
||||
requires: []
|
||||
|
||||
- id: specs
|
||||
generates: "specs/*.md" # glob pattern
|
||||
description: "Create technical specification documents"
|
||||
template: "specs.md"
|
||||
requires:
|
||||
- proposal
|
||||
|
||||
- id: design
|
||||
generates: "design.md"
|
||||
description: "Create design document"
|
||||
template: "design.md"
|
||||
requires:
|
||||
- proposal
|
||||
- specs
|
||||
|
||||
- id: tasks
|
||||
generates: "tasks.md"
|
||||
description: "Create tasks breakdown document"
|
||||
template: "tasks.md"
|
||||
requires:
|
||||
- design
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Layer | Component | Responsibility | Status |
|
||||
|-------|-----------|----------------|--------|
|
||||
| Core | ArtifactGraph | Pure dependency logic + XDG schema resolution | ✅ Slice 1 COMPLETE |
|
||||
| Utils | change-utils | Change creation + name validation only | Slice 2 (new functionality only) |
|
||||
| Core | InstructionLoader | Template resolution + enrichment | Slice 3 (all new) |
|
||||
| Presentation | CLI | New artifact graph commands | Slice 4 (new commands only) |
|
||||
| Integration | Claude Commands | AI assistant glue | Slice 4 |
|
||||
|
||||
**What already exists (not in this proposal):**
|
||||
- `getActiveChangeIds()` in `src/utils/item-discovery.ts` - list changes
|
||||
- `ChangeCommand.list/show/validate()` in `src/commands/change.ts`
|
||||
- `ListCommand.execute()` in `src/core/list.ts`
|
||||
- `ViewCommand.execute()` in `src/core/view.ts` - dashboard
|
||||
- `src/core/init.ts` - initialization
|
||||
- `src/core/archive.ts` - archiving
|
||||
|
||||
**Key Principles:**
|
||||
- **Filesystem IS the database** - stateless, version-control friendly
|
||||
- **Dependencies are enablers** - show what's possible, don't force order
|
||||
- **Deterministic CLI, inferring agent** - CLI requires explicit `--change`, agent infers from context
|
||||
- **XDG-compliant paths** - schemas and templates use standard user data directories
|
||||
- **2-level inheritance** - user override → package built-in (no deeper)
|
||||
- **Schemas are versioned** - support variations by philosophy, version, language
|
||||
@@ -1,926 +0,0 @@
|
||||
# OpenSpec Experimental Release Plan
|
||||
|
||||
This document outlines the plan to release the experimental artifact workflow system for user testing.
|
||||
|
||||
## Overview
|
||||
|
||||
The goal is to allow users to test the new artifact-driven workflow system alongside the existing OpenSpec commands. This experimental system (`opsx`) provides a more granular, step-by-step approach to creating change artifacts.
|
||||
|
||||
## Three Workflow Modes
|
||||
|
||||
### 1. Old Workflow (Current Production)
|
||||
- **Commands**: `/openspec:proposal`, `/openspec:apply`, `/openspec:archive`
|
||||
- **Behavior**: Hardcoded slash commands that generate all artifacts in one command
|
||||
- **Status**: Production, unchanged
|
||||
|
||||
### 2. New Artifact System - Batch Mode (Future)
|
||||
- **Commands**: Refactored `/openspec:proposal` using schemas
|
||||
- **Behavior**: Schema-driven but generates all artifacts at once (like legacy)
|
||||
- **Status**: Not in scope for this experimental release
|
||||
- **Note**: This is a future refactor to unify the old system with schemas
|
||||
|
||||
### 3. New Artifact System - Granular Mode (Experimental)
|
||||
- **Commands**: `/opsx:new`, `/opsx:continue`
|
||||
- **Behavior**: One artifact at a time, dependency-driven, iterative
|
||||
- **Status**: Target for this experimental release
|
||||
|
||||
---
|
||||
|
||||
## Work Items
|
||||
|
||||
### 1. Rename AWF to OPSX
|
||||
|
||||
**Current State:**
|
||||
- Commands: `/awf:start`, `/awf:continue`
|
||||
- Files: `.claude/commands/awf/start.md`, `.claude/commands/awf/continue.md`
|
||||
|
||||
**Target State:**
|
||||
- Commands: `/opsx:new`, `/opsx:continue`
|
||||
- Files: `.claude/commands/opsx/new.md`, `.claude/commands/opsx/continue.md`
|
||||
|
||||
**Tasks:**
|
||||
- [x] Create `.claude/commands/opsx/` directory
|
||||
- [x] Rename `start.md` → `new.md` and update content
|
||||
- [x] Copy `continue.md` with updated references
|
||||
- [x] Update all references from "awf" to "opsx" in command content
|
||||
- [x] Update frontmatter (name, description) to use "opsx" naming
|
||||
- [x] Remove `.claude/commands/awf/` directory
|
||||
|
||||
**CLI Commands:**
|
||||
The underlying CLI commands (`openspec status`, `openspec instructions`, etc.) remain unchanged. Only the slash command names change.
|
||||
|
||||
---
|
||||
|
||||
### 2. Remove WF Skill Files
|
||||
|
||||
**Current State:**
|
||||
- `.claude/commands/wf/start.md` - References non-existent `openspec wf` commands
|
||||
- `.claude/commands/wf/continue.md` - References non-existent `openspec wf` commands
|
||||
|
||||
**Target State:**
|
||||
- Directory and files removed
|
||||
|
||||
**Tasks:**
|
||||
- [x] Delete `.claude/commands/wf/start.md`
|
||||
- [x] Delete `.claude/commands/wf/continue.md`
|
||||
- [x] Delete `.claude/commands/wf/` directory
|
||||
|
||||
---
|
||||
|
||||
### 3. Add Agent Skills for Experimental Workflow
|
||||
|
||||
**Purpose:**
|
||||
Generate experimental workflow skills using the [Agent Skills](https://agentskills.io/specification) open standard.
|
||||
|
||||
**Why Skills Instead of Slash Commands:**
|
||||
- **Cross-editor compatibility**: Skills work in Claude Code, Cursor, Windsurf, and other compatible editors automatically
|
||||
- **Simpler implementation**: Single directory (`.claude/skills/`) instead of 18+ editor-specific configurators
|
||||
- **Standard format**: Open standard with simple YAML frontmatter + markdown
|
||||
- **User invocation**: Users explicitly invoke skills when they want to use them
|
||||
|
||||
**Behavior:**
|
||||
1. Create `.claude/skills/` directory if it doesn't exist
|
||||
2. Generate two skills using the Agent Skills specification:
|
||||
- `openspec-new-change/SKILL.md` - Start a new change with artifact workflow
|
||||
- `openspec-continue-change/SKILL.md` - Continue working on a change (create next artifact)
|
||||
3. Skills are added **alongside** existing `/openspec:*` commands (not replacing)
|
||||
|
||||
**Supported Editors:**
|
||||
- Claude Code (native support)
|
||||
- Cursor (native support via Settings → Rules → Import Settings)
|
||||
- Windsurf (imports `.claude` configs)
|
||||
- Cline, Codex, and other Agent Skills-compatible editors
|
||||
|
||||
**Tasks:**
|
||||
- [x] Create skill template content for `openspec-new-change` (based on current opsx:new)
|
||||
- [x] Create skill template content for `openspec-continue-change` (based on current opsx:continue)
|
||||
- [x] Add temporary `artifact-experimental-setup` command to CLI
|
||||
- [x] Implement skill file generation (YAML frontmatter + markdown body)
|
||||
- [x] Add success message with usage instructions
|
||||
|
||||
**Note:** The `artifact-experimental-setup` command is temporary and will be merged into `openspec init` once the experimental workflow is promoted to stable.
|
||||
|
||||
**Skill Format:**
|
||||
Each skill is a directory with a `SKILL.md` file:
|
||||
```
|
||||
.claude/skills/
|
||||
├── openspec-new-change/
|
||||
│ └── SKILL.md # name, description, instructions
|
||||
├── openspec-continue-change/
|
||||
│ └── SKILL.md # name, description, instructions
|
||||
└── openspec-apply-change/
|
||||
└── SKILL.md # name, description, instructions
|
||||
```
|
||||
|
||||
**CLI Interface:**
|
||||
```bash
|
||||
openspec artifact-experimental-setup
|
||||
|
||||
# Output:
|
||||
# 🧪 Experimental Artifact Workflow Skills Created
|
||||
#
|
||||
# ✓ .claude/skills/openspec-new-change/SKILL.md
|
||||
# ✓ .claude/skills/openspec-continue-change/SKILL.md
|
||||
# ✓ .claude/skills/openspec-apply-change/SKILL.md
|
||||
#
|
||||
# 📖 Usage:
|
||||
#
|
||||
# Skills work automatically in compatible editors:
|
||||
# • Claude Code - Auto-detected, ready to use
|
||||
# • Cursor - Enable in Settings → Rules → Import Settings
|
||||
# • Windsurf - Auto-imports from .claude directory
|
||||
#
|
||||
# Ask Claude naturally:
|
||||
# • "I want to start a new OpenSpec change to add <feature>"
|
||||
# • "Continue working on this change"
|
||||
#
|
||||
# Claude will automatically use the appropriate skill.
|
||||
#
|
||||
# 💡 This is an experimental feature.
|
||||
# Feedback welcome at: https://github.com/Fission-AI/OpenSpec/issues
|
||||
```
|
||||
|
||||
**Implementation Notes:**
|
||||
- Simple file writing: Create directories and write templated `SKILL.md` files (no complex logic)
|
||||
- Use existing `FileSystemUtils.writeFile()` pattern like slash command configurators
|
||||
- Template structure: YAML frontmatter + markdown body
|
||||
- Keep existing `/opsx:*` slash commands for now (manual cleanup later)
|
||||
- Skills use invocation model (user explicitly asks Claude to use them)
|
||||
- Skill `description` field guides when Claude suggests using the skill
|
||||
- Each `SKILL.md` has required fields: `name` (matches directory) and `description`
|
||||
|
||||
---
|
||||
|
||||
### 4. Update `/opsx:new` Command Content
|
||||
|
||||
**Current Behavior (awf:start):**
|
||||
1. Ask user what they want to build (if no input)
|
||||
2. Create change directory
|
||||
3. Show artifact status
|
||||
4. Show what's ready
|
||||
5. Get instructions for proposal
|
||||
6. STOP and wait
|
||||
|
||||
**New Behavior (opsx:new):**
|
||||
Same flow but with updated naming:
|
||||
- References to "awf" → "opsx"
|
||||
- References to `/awf:continue` → `/opsx:continue`
|
||||
- Update frontmatter name/description
|
||||
|
||||
**Tasks:**
|
||||
- [x] Update all "awf" references to "opsx"
|
||||
- [x] Update command references in prompt text
|
||||
- [x] Verify CLI commands still work (they use `openspec`, not `awf`)
|
||||
|
||||
---
|
||||
|
||||
### 5. Update `/opsx:continue` Command Content
|
||||
|
||||
**Current Behavior (awf:continue):**
|
||||
1. Prompt for change selection (if not provided)
|
||||
2. Check current status
|
||||
3. Create ONE artifact based on what's ready
|
||||
4. Show progress and what's unlocked
|
||||
5. STOP
|
||||
|
||||
**New Behavior (opsx:continue):**
|
||||
Same flow with updated naming.
|
||||
|
||||
**Tasks:**
|
||||
- [x] Update all "awf" references to "opsx"
|
||||
- [x] Update command references in prompt text
|
||||
|
||||
---
|
||||
|
||||
### 6. End-to-End Testing
|
||||
|
||||
**Objective:**
|
||||
Run through a complete workflow with Claude using the new skills to create a real feature, validating the entire flow works.
|
||||
|
||||
**Test Scenario:**
|
||||
Use a real OpenSpec feature as the test case (dog-fooding).
|
||||
|
||||
**Test Flow:**
|
||||
1. Run `openspec artifact-experimental-setup` to create skills
|
||||
2. Verify `.claude/skills/openspec-new-change/SKILL.md` created
|
||||
3. Verify `.claude/skills/openspec-continue-change/SKILL.md` created
|
||||
4. Verify `.claude/skills/openspec-apply-change/SKILL.md` created
|
||||
5. Ask Claude: "I want to start a new OpenSpec change to add feature X"
|
||||
6. Verify Claude invokes the `openspec-new-change` skill
|
||||
7. Verify change directory created at `openspec/changes/add-feature-x/`
|
||||
8. Verify proposal template shown
|
||||
9. Ask Claude: "Continue working on this change"
|
||||
10. Verify Claude invokes the `openspec-continue-change` skill
|
||||
11. Verify `proposal.md` created with content
|
||||
12. Ask Claude: "Continue" (create specs)
|
||||
13. Verify `specs/*.md` created
|
||||
14. Ask Claude: "Continue" (create design)
|
||||
15. Verify `design.md` created
|
||||
16. Ask Claude: "Continue" (create tasks)
|
||||
17. Verify `tasks.md` created
|
||||
18. Verify status shows 4/4 complete
|
||||
19. Implement the feature based on tasks
|
||||
20. Run `/openspec:archive` to archive the change
|
||||
|
||||
**Validation Checklist:**
|
||||
- [ ] `openspec artifact-experimental-setup` creates correct directory structure
|
||||
- [ ] Skills are auto-detected in Claude Code
|
||||
- [ ] Skill descriptions trigger appropriate invocations
|
||||
- [ ] Skills create change directory and show proposal template
|
||||
- [ ] Skills correctly identify ready artifacts
|
||||
- [ ] Skills create artifacts with meaningful content
|
||||
- [ ] Dependency detection works (specs requires proposal, etc.)
|
||||
- [ ] Progress tracking is accurate
|
||||
- [ ] Template content is useful and well-structured
|
||||
- [ ] Error handling works (invalid names, missing changes, etc.)
|
||||
- [ ] Works with different schemas (spec-driven, tdd)
|
||||
- [ ] Test in Cursor (Settings → Rules → Import Settings)
|
||||
|
||||
**Document Results:**
|
||||
- Create test log documenting what worked and what didn't
|
||||
- Note any friction points or confusing UX
|
||||
- Identify bugs or improvements needed before user release
|
||||
|
||||
---
|
||||
|
||||
### 7. Documentation for Users
|
||||
|
||||
**Create user-facing documentation explaining:**
|
||||
|
||||
1. **What is the experimental workflow?**
|
||||
- A new way to create OpenSpec changes step-by-step using Agent Skills
|
||||
- One artifact at a time with dependency tracking
|
||||
- More interactive and iterative than the batch approach
|
||||
- Works across Claude Code, Cursor, Windsurf, and other compatible editors
|
||||
|
||||
2. **How to set up experimental workflow**
|
||||
```bash
|
||||
openspec artifact-experimental-setup
|
||||
```
|
||||
|
||||
Note: This is a temporary command that will be integrated into `openspec init` once promoted to stable.
|
||||
|
||||
3. **Available skills**
|
||||
- `openspec-new-change` - Start a new change with artifact workflow
|
||||
- `openspec-continue-change` - Continue working (create next artifact)
|
||||
|
||||
4. **How to use**
|
||||
- **Claude Code**: Skills are auto-detected, just ask Claude naturally
|
||||
- "I want to start a new OpenSpec change to add X"
|
||||
- "Continue working on this change"
|
||||
- **Cursor**: Enable in Settings → Rules → Import Settings
|
||||
- **Windsurf**: Auto-imports `.claude` directory
|
||||
|
||||
5. **Example workflow**
|
||||
- Step-by-step walkthrough with natural language interactions
|
||||
- Show how Claude invokes skills based on user requests
|
||||
|
||||
6. **Feedback mechanism**
|
||||
- GitHub issue template for feedback
|
||||
- What to report (bugs, UX issues, suggestions)
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Create `docs/experimental-workflow.md` user guide
|
||||
- [ ] Add GitHub issue template for experimental feedback
|
||||
- [ ] Update README with mention of experimental features
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
1. Remove WF skill files
|
||||
└── (no dependencies)
|
||||
|
||||
2. Rename AWF to OPSX
|
||||
└── (no dependencies)
|
||||
|
||||
3. Add Agent Skills
|
||||
└── Depends on: Rename AWF to OPSX (uses opsx content as templates)
|
||||
|
||||
4. Update opsx:new content
|
||||
└── Depends on: Rename AWF to OPSX
|
||||
|
||||
5. Update opsx:continue content
|
||||
└── Depends on: Rename AWF to OPSX
|
||||
|
||||
6. E2E Testing
|
||||
└── Depends on: Add Agent Skills (tests the skills workflow)
|
||||
|
||||
7. User Documentation
|
||||
└── Depends on: E2E Testing (need to know final behavior)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Out of Scope
|
||||
|
||||
The following are explicitly NOT part of this experimental release:
|
||||
|
||||
1. **Batch mode refactor** - Making legacy `/openspec:proposal` use schemas
|
||||
2. **New schemas** - Only shipping with existing `spec-driven` and `tdd`
|
||||
3. **Schema customization UI** - No `openspec schema list` or similar
|
||||
4. **Multiple editor support in CLI** - Skills work cross-editor automatically via `.claude/skills/`
|
||||
5. **Replacing existing commands** - Skills are additive, not replacing `/openspec:*` or `/opsx:*`
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
The experimental release is ready when:
|
||||
|
||||
1. `openspec-new-change`, `openspec-continue-change`, and `openspec-apply-change` skills work end-to-end
|
||||
2. `openspec artifact-experimental-setup` creates skills in `.claude/skills/`
|
||||
3. Skills work in Claude Code and are compatible with Cursor/Windsurf
|
||||
4. At least one complete workflow has been tested manually
|
||||
5. User documentation exists explaining how to generate and use skills
|
||||
6. Feedback mechanism is in place
|
||||
7. WF skill files are removed
|
||||
8. No references to "awf" remain in user-facing content
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Schema selection** - Should `opsx:new` allow selecting a schema, or always use `spec-driven`?
|
||||
- Current: Always uses `spec-driven` as default
|
||||
- Consider: Add `--schema tdd` option or prompt
|
||||
|
||||
2. **Namespace in CLI** - Should experimental CLI commands be namespaced?
|
||||
- Current: `openspec status`, `openspec instructions` (no namespace)
|
||||
- Alternative: `openspec opsx status` (explicit experimental namespace)
|
||||
- Recommendation: Keep current, less typing for users
|
||||
|
||||
3. **Deprecation path** - If opsx becomes the default, how do we migrate?
|
||||
- Not needed for experimental release
|
||||
- Document that command names may change
|
||||
|
||||
---
|
||||
|
||||
## Estimated Work Breakdown
|
||||
|
||||
| Item | Complexity | Notes |
|
||||
|------|------------|-------|
|
||||
| Remove WF files | Trivial | Just delete 2 files + directory |
|
||||
| Rename AWF → OPSX | Low | File renames + content updates |
|
||||
| Add Agent Skills | **Low** | **Simple: 3-4 files, single output directory, standard format** |
|
||||
| Update opsx:new content | Low | Text replacements |
|
||||
| Update opsx:continue content | Low | Text replacements |
|
||||
| E2E Testing | Medium | Manual testing, documenting results |
|
||||
| User Documentation | Medium | New docs, issue template |
|
||||
|
||||
**Key Improvement:** Switching to Agent Skills reduces complexity significantly:
|
||||
- **Before:** 20+ files (type definitions, 18+ editor configurators, editor selection UI)
|
||||
- **After:** 3-4 files (skill templates, simple CLI command)
|
||||
- **Cross-editor:** Works automatically in Claude Code, Cursor, Windsurf without extra code
|
||||
|
||||
---
|
||||
|
||||
## User Feedback from E2E Testing
|
||||
|
||||
### What Worked Well
|
||||
|
||||
1. **Clear dependency graph** ⭐ HIGH PRIORITY - KEEP
|
||||
- The status command showing blocked/unblocked artifacts was intuitive:
|
||||
```
|
||||
[x] proposal
|
||||
[ ] design
|
||||
[-] tasks (blocked by: design, specs)
|
||||
```
|
||||
- Users always knew what they could work on next
|
||||
- **Relevance**: Core UX strength to preserve
|
||||
|
||||
2. **Structured instructions output** ⭐ HIGH PRIORITY - KEEP
|
||||
- `openspec instructions <artifact>` gave templates, output paths, and context in one call
|
||||
- Very helpful for understanding what to create
|
||||
- **Relevance**: Essential for agent-driven workflow
|
||||
|
||||
3. **Simple scaffolding** ✅ WORKS WELL
|
||||
- `openspec new change "name"` just worked - created directory structure without fuss
|
||||
- **Relevance**: Good baseline, room for improvement (see pain points)
|
||||
|
||||
---
|
||||
|
||||
### Pain Points & Confusion
|
||||
|
||||
1. **Redundant CLI calls** ⚠️ MEDIUM PRIORITY
|
||||
- Users called both `status` AND `next` every time, but they overlap significantly
|
||||
- `status` already shows what's blocked
|
||||
- **Recommendation**: Consider merging or making `next` give actionable guidance beyond just listing names
|
||||
- **Relevance**: Reduces friction in iterative workflow
|
||||
|
||||
2. **Specs directory structure was ambiguous** 🔥 HIGH PRIORITY - FIX
|
||||
- Instructions said: `Write to: .../specs/**/*.md`
|
||||
- Users had to guess: `specs/spec.md`? `specs/game/spec.md`? `specs/tic-tac-toe/spec.md`?
|
||||
- Users ended up doing manual `mkdir -p .../specs/tic-tac-toe` then writing `spec.md` inside
|
||||
- **Recommendation**: CLI should scaffold this directory structure automatically
|
||||
- **Relevance**: Critical agent UX - ambiguous paths cause workflow friction
|
||||
|
||||
3. **Repetitive --change flag** ⚠️ MEDIUM PRIORITY
|
||||
- Every command needed `--change "tic-tac-toe-game"`
|
||||
- After 10+ calls, this felt verbose
|
||||
- **Recommendation**: `openspec use "tic-tac-toe-game"` to set context, then subsequent commands assume that change
|
||||
- **Relevance**: Quality of life improvement for iterative sessions
|
||||
|
||||
4. **No validation feedback** 🔥 HIGH PRIORITY - ADD
|
||||
- After writing each artifact, users just ran `status` hoping it would show `[x]`
|
||||
- Questions raised:
|
||||
- How did it know the artifact was "done"? File existence?
|
||||
- What if spec format was wrong (e.g., wrong heading levels)?
|
||||
- **Recommendation**: Add `openspec validate --change "name"` to check content quality
|
||||
- **Relevance**: Critical for user confidence and catching errors early
|
||||
|
||||
5. **Query-heavy, action-light CLI** 🔥 HIGH PRIORITY - ENHANCE
|
||||
- Most commands retrieve info. The only "action" is `new change`
|
||||
- Artifact creation is manual Write to guessed paths
|
||||
- **Recommendation**: `openspec create proposal --change "name"` could scaffold the file with template pre-filled, then user just edits
|
||||
- **Relevance**: Directly impacts agent productivity - reduce manual file writing
|
||||
|
||||
6. **Instructions output was verbose** ⚠️ LOW PRIORITY
|
||||
- XML-style output (`<artifact>`, `<template>`, `<instruction>`) was parseable but long
|
||||
- Key info (output path, template) was buried in ~50 lines
|
||||
- **Recommendation**: Add compact mode or structured JSON output for agents
|
||||
- **Relevance**: Nice-to-have for agent parsing efficiency
|
||||
|
||||
---
|
||||
|
||||
### Workflow Friction
|
||||
|
||||
1. **Mandatory "STOP and wait" after showing proposal template** ⚠️ MEDIUM PRIORITY
|
||||
- The skill said "STOP and wait" after showing the proposal template
|
||||
- This felt overly cautious when user had already provided enough context (e.g., "tic tac toe, single player vs AI, minimal aesthetics")
|
||||
- **Recommendation**: Make the pause optional or conditional based on context clarity
|
||||
- **Relevance**: Reduces unnecessary round-trips in agent conversations
|
||||
|
||||
2. **No connection to implementation** 🔥 HIGH PRIORITY - ROADMAP ITEM
|
||||
- After 4/4 artifacts complete, then what? The workflow ends at planning
|
||||
- No `openspec apply` or guidance on how to execute the tasks
|
||||
- User asked "would you like me to implement?" but that's outside OpenSpec's scope currently
|
||||
- **Recommendation**: Add implementation bridge - either:
|
||||
- `openspec apply` command to start execution phase
|
||||
- Clear handoff to existing `/openspec:apply` workflow
|
||||
- Documentation on next steps after planning completes
|
||||
- **Relevance**: Critical missing piece - users expect end-to-end workflow
|
||||
|
||||
---
|
||||
|
||||
### Priority Summary
|
||||
|
||||
**MUST FIX (High Priority):**
|
||||
1. Specs directory structure ambiguity (#2)
|
||||
2. Add validation feedback (#4)
|
||||
3. Make CLI more action-oriented (#5)
|
||||
4. Bridge to implementation phase (#2 in Workflow Friction)
|
||||
5. Keep clear dependency graph (#1 in What Worked)
|
||||
6. Keep structured instructions (#2 in What Worked)
|
||||
|
||||
**SHOULD FIX (Medium Priority):**
|
||||
1. Reduce redundant CLI calls (#1)
|
||||
2. Repetitive `--change` flag (#3)
|
||||
3. Mandatory STOP behavior (#1 in Workflow Friction)
|
||||
|
||||
**NICE TO HAVE (Low Priority):**
|
||||
1. Compact instructions output mode (#6)
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions (from E2E Testing Feedback)
|
||||
|
||||
Based on dev testing and analysis of agent workflow friction, we identified three blockers for experimental release and made the following decisions.
|
||||
|
||||
### Blockers Identified
|
||||
|
||||
From the pain points in E2E testing, three issues are blocking the experimental release:
|
||||
|
||||
1. **Specs directory ambiguity** - Agents don't know where to write spec files or how to name capabilities
|
||||
2. **CLI is query-heavy** - Most commands retrieve info, artifact creation is manual
|
||||
3. **Apply integration missing** - After 4/4 artifacts complete, no guidance on implementation phase
|
||||
|
||||
### Decision 1: Capability Discovery in Proposal (RESOLVED)
|
||||
|
||||
**Problem:** The specs artifact instruction says "Create one spec file per capability in `specs/<name>/spec.md`" but:
|
||||
- Agent doesn't know what `<name>` should be
|
||||
- Capability identification requires research (existing specs, codebase)
|
||||
- Proposal template asks for "Affected specs" but doesn't structure it
|
||||
- Research happens implicitly, output isn't captured
|
||||
|
||||
**Decision:** Enrich the proposal template to explicitly capture capability discovery.
|
||||
|
||||
**Current proposal template:**
|
||||
```markdown
|
||||
## Why
|
||||
## What Changes
|
||||
## Impact
|
||||
- Affected specs: List capabilities... ← vague, easy to skip
|
||||
- Affected code: ...
|
||||
```
|
||||
|
||||
**New proposal template:**
|
||||
```markdown
|
||||
## Why
|
||||
## What Changes
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
<!-- Capabilities being introduced (will create new specs/<name>/spec.md) -->
|
||||
- `<name>`: <brief description of what this capability covers>
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- Existing capabilities being changed (will update existing specs) -->
|
||||
- `<existing-name>`: <what's changing>
|
||||
|
||||
## Impact
|
||||
<!-- Affected code, APIs, dependencies, systems -->
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
- Proposal already asks for capabilities (just poorly) - this makes it explicit
|
||||
- Captured output is reviewable (vs implicit research that can't be verified)
|
||||
- Creates clear contract between proposal and specs phases
|
||||
- Distinguishes NEW vs MODIFIED upfront (critical for specs phase)
|
||||
- Agent can't skip research - it's part of the deliverable
|
||||
|
||||
**Implementation:**
|
||||
- Update `schemas/spec-driven/templates/proposal.md`
|
||||
- Update proposal instruction in `schemas/spec-driven/schema.yaml`
|
||||
- Update skill instructions to guide capability discovery
|
||||
|
||||
### Decision 2: CLI Action Commands (IN PROGRESS)
|
||||
|
||||
**Problem:** CLI is mostly query-oriented. Agents run `openspec status`, `openspec next`, `openspec instructions` but then must manually write files.
|
||||
|
||||
#### Decision 2a: Remove `openspec next` command (RESOLVED)
|
||||
|
||||
**Problem:** The `next` command is redundant. It only shows which artifacts are ready, but `status` already shows this information (artifacts with status "ready" vs "blocked" vs "done").
|
||||
|
||||
**Current behavior:**
|
||||
```bash
|
||||
openspec status --change "X" # Shows: proposal (done), specs (ready), design (blocked), tasks (blocked)
|
||||
openspec next --change "X" # Shows: ["specs"] ← redundant
|
||||
```
|
||||
|
||||
**Decision:** Remove the `next` command. Agents should use `status` which provides the same info plus more context.
|
||||
|
||||
**Implementation:**
|
||||
- Remove `next` command from CLI
|
||||
- Update skill instructions to use `status` instead of `next`
|
||||
- Update AGENTS.md references
|
||||
|
||||
#### Decision 2b: CLI Scaffolding (RESOLVED - NO)
|
||||
|
||||
**Problem:** After getting instructions, agents manually write files. Should CLI scaffold artifacts instead?
|
||||
|
||||
**Options considered:**
|
||||
- Add `openspec create <artifact>` commands that scaffold files with templates
|
||||
- Keep current approach where agent writes files directly from instructions
|
||||
- Hybrid: CLI can scaffold, agent can also write directly
|
||||
|
||||
**Decision:** Keep current flow. No scaffolding commands.
|
||||
|
||||
**Rationale (from agent ergonomics perspective):**
|
||||
- One Write is better than multiple Edits - agent composes full content atomically
|
||||
- `instructions` already provides template in context - scaffolding just moves it to a file
|
||||
- Fewer tool calls: `instructions` + Write (2) vs `create` + `instructions` + Read + Edit×N (4+)
|
||||
- Scaffolding doesn't solve the real problem (not knowing WHAT to write)
|
||||
- Real problem solved by proposal template change (capability discovery)
|
||||
|
||||
**For multi-file artifacts (specs):** Scaffolding can't help because CLI doesn't know capability names until proposal is complete. The capability discovery in proposal solves this.
|
||||
|
||||
### Decision 3: Apply Integration (RESOLVED)
|
||||
|
||||
**Original problem:** After planning completes (4/4 artifacts), the experimental workflow ends. No guidance on implementation.
|
||||
|
||||
**Key insight: No phases, just actions.**
|
||||
|
||||
Through discussion, we realized phases (planning → implementation → archive) are an artificial constraint. Work is fluid:
|
||||
- You might start implementing, realize the design is wrong → update design.md
|
||||
- You're halfway through tasks, discover a new requirement → update specs
|
||||
- You bounce between "planning" and "implementing" constantly
|
||||
|
||||
**The better model: Actions on a Change**
|
||||
|
||||
A change is a thing (with artifacts). Actions are verbs you perform on a change. Actions aren't phases - they're fluid operations you can perform anytime.
|
||||
|
||||
| Action | What it does | Skill | CLI Command |
|
||||
|--------|--------------|-------|-------------|
|
||||
| `new` | Create a change (scaffold directory) | `opsx:new` | `openspec new change` |
|
||||
| `continue` | Create next artifact (dependency-aware) | `opsx:continue` | `openspec instructions` |
|
||||
| `apply` | Implement tasks (execute, check off) | `opsx:apply` (NEW) | TBD |
|
||||
| `update` | Refresh/update artifacts based on learnings | `opsx:update` (NEW) | TBD |
|
||||
| `explore` | Research, ask questions, understand | `opsx:explore` (NEW) | TBD |
|
||||
| `validate` | Check artifacts are correct/complete | TBD | `openspec validate` |
|
||||
| `archive` | Finalize and move to archive | existing | `openspec archive` |
|
||||
|
||||
**Key principles:**
|
||||
- Actions are modeled as skills (primary interface for agents)
|
||||
- Some skills have matching CLI commands for convenience
|
||||
- Skills and CLI commands are decoupled - not everything needs both
|
||||
- Actions can be performed in any order (with soft prerequisites)
|
||||
- No linear phase gates
|
||||
|
||||
**What the schema defines:**
|
||||
- Artifacts (what they are, where they go)
|
||||
- Dependencies (what must exist first)
|
||||
- Required vs optional
|
||||
- Templates + instructions
|
||||
|
||||
**What the schema does NOT define:**
|
||||
- Phases
|
||||
- When you can modify things
|
||||
- Linear workflow
|
||||
|
||||
**Progress tracking:**
|
||||
- tasks.md checkboxes = implementation progress
|
||||
- Artifact existence = planning progress
|
||||
- Archive readiness = user decides (or all tasks done)
|
||||
|
||||
**For experimental release:**
|
||||
- Create `opsx:apply` skill (guidance for implementing tasks)
|
||||
- Document the "actions on a change" model
|
||||
- Other actions (update, explore) can come later
|
||||
|
||||
---
|
||||
|
||||
### Design: `openspec-apply-change` Skill
|
||||
|
||||
#### Overview
|
||||
|
||||
The apply skill guides agents through implementing tasks from a completed (or in-progress) change. Unlike the old `/openspec:apply` command, this skill:
|
||||
- Is **fluid** - can be invoked anytime, not just after all artifacts are done
|
||||
- Allows **artifact updates** - if implementation reveals issues, update design/specs
|
||||
- Works **until done** - keeps going through tasks until complete or blocked
|
||||
- Tracks **progress via checkboxes** - tasks.md is the source of truth
|
||||
|
||||
#### Skill Metadata
|
||||
|
||||
```yaml
|
||||
name: openspec-apply-change
|
||||
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
|
||||
```
|
||||
|
||||
#### When to Invoke
|
||||
|
||||
The skill should be invoked when:
|
||||
- User says "implement this change" or "start implementing"
|
||||
- User says "work on the tasks" or "do the next task"
|
||||
- User says "apply this change"
|
||||
- All artifacts are complete and user wants to proceed
|
||||
- User wants to continue implementation after a break
|
||||
|
||||
#### Input
|
||||
|
||||
- Optionally: change name
|
||||
- Optionally: specific task number to work on
|
||||
- If omitted: prompt for change selection (same pattern as continue-change)
|
||||
|
||||
#### Steps
|
||||
|
||||
```markdown
|
||||
**Steps**
|
||||
|
||||
1. **If no change name provided, prompt for selection**
|
||||
|
||||
Run `openspec list --json` to get available changes. Use **AskUserQuestion** to let user select.
|
||||
|
||||
Show changes that have tasks.md (implementation-ready).
|
||||
Mark changes with incomplete tasks as "(In Progress)".
|
||||
|
||||
2. **Get apply instructions**
|
||||
|
||||
```bash
|
||||
openspec instructions apply --change "<name>" --json
|
||||
```
|
||||
|
||||
This returns:
|
||||
- Context file paths (proposal, specs, design, tasks)
|
||||
- Progress (total, complete, remaining)
|
||||
- Task list with status
|
||||
- Dynamic instruction based on current state
|
||||
|
||||
**Handle states:**
|
||||
- If blocked (missing artifacts): show message, suggest `openspec-continue-change`
|
||||
- If all done: congratulate, suggest archive
|
||||
- Otherwise: proceed to implementation
|
||||
|
||||
3. **Read context files**
|
||||
|
||||
Read the files listed in the instructions:
|
||||
- `proposal.md` - why and what
|
||||
- `specs/*.md` - requirements and scenarios
|
||||
- `design.md` - technical approach (if exists)
|
||||
- `tasks.md` - the implementation checklist
|
||||
|
||||
4. **Show current progress**
|
||||
|
||||
Display:
|
||||
- Progress: "N/M tasks complete"
|
||||
- Remaining tasks overview
|
||||
- Dynamic instruction from CLI
|
||||
|
||||
5. **Implement tasks (loop until done or blocked)**
|
||||
|
||||
For each pending task:
|
||||
- Show which task is being worked on
|
||||
- Make the code changes required
|
||||
- Keep changes minimal and focused
|
||||
- Mark task complete in tasks.md: `- [ ]` → `- [x]`
|
||||
- Continue to next task
|
||||
|
||||
**Pause if:**
|
||||
- Task is unclear → ask for clarification
|
||||
- Implementation reveals a design issue → suggest updating artifacts
|
||||
- Error or blocker encountered → report and wait for guidance
|
||||
- User interrupts
|
||||
|
||||
6. **On completion or pause, show status**
|
||||
|
||||
Display:
|
||||
- Tasks completed this session
|
||||
- Overall progress: "N/M tasks complete"
|
||||
- If all done: suggest archive
|
||||
- If paused: explain why and wait for guidance
|
||||
```
|
||||
|
||||
#### Output Format
|
||||
|
||||
**During implementation:**
|
||||
```
|
||||
## Implementing: add-user-auth
|
||||
|
||||
Working on task 3/7: Create UserAuth service class
|
||||
[...implementation happening...]
|
||||
✓ Task complete
|
||||
|
||||
Working on task 4/7: Add login endpoint to AuthController
|
||||
[...implementation happening...]
|
||||
✓ Task complete
|
||||
|
||||
Working on task 5/7: Add JWT token generation
|
||||
[...implementation happening...]
|
||||
```
|
||||
|
||||
**On completion:**
|
||||
```
|
||||
## Implementation Complete
|
||||
|
||||
**Change:** add-user-auth
|
||||
**Progress:** 7/7 tasks complete ✓
|
||||
|
||||
### Completed This Session
|
||||
- [x] Create UserAuth service class
|
||||
- [x] Add login endpoint to AuthController
|
||||
- [x] Add JWT token generation
|
||||
- [x] Add logout endpoint
|
||||
- [x] Add auth middleware
|
||||
- [x] Write unit tests
|
||||
- [x] Update API documentation
|
||||
|
||||
All tasks complete! Ready to archive this change.
|
||||
```
|
||||
|
||||
**On pause (issue encountered):**
|
||||
```
|
||||
## Implementation Paused
|
||||
|
||||
**Change:** add-user-auth
|
||||
**Progress:** 4/7 tasks complete
|
||||
|
||||
### Issue Encountered
|
||||
Task 5 "Add JWT token generation" - the design specifies using RS256 but
|
||||
the existing auth library only supports HS256.
|
||||
|
||||
**Options:**
|
||||
1. Update design.md to use HS256 instead
|
||||
2. Add a new JWT library that supports RS256
|
||||
3. Other approach
|
||||
|
||||
What would you like to do?
|
||||
```
|
||||
|
||||
#### Guardrails
|
||||
|
||||
- Keep going through tasks until done or blocked
|
||||
- Always read context before starting (specs, design)
|
||||
- If task is ambiguous, pause and ask before implementing
|
||||
- If implementation reveals issues, pause and suggest artifact updates
|
||||
- Keep code changes minimal and scoped to each task
|
||||
- Update task checkbox immediately after completing each task
|
||||
- Pause on errors, blockers, or unclear requirements - don't guess
|
||||
|
||||
#### Fluid Workflow Integration
|
||||
|
||||
The apply skill supports the "actions on a change" model:
|
||||
|
||||
**Can be invoked anytime:**
|
||||
- Before all artifacts are done (if tasks.md exists)
|
||||
- After partial implementation
|
||||
- Interleaved with other actions (update, continue)
|
||||
|
||||
**Allows artifact updates:**
|
||||
- If implementation reveals design issues → suggest `opsx:update` or manual edit
|
||||
- If requirements need clarification → suggest updating specs
|
||||
- Not phase-locked - work fluidly
|
||||
|
||||
**Example fluid workflow:**
|
||||
```
|
||||
User: "Implement add-user-auth"
|
||||
→ openspec-apply-change: implements tasks 1, 2, 3, 4...
|
||||
→ Pauses at task 5: "Design says RS256 but library only supports HS256"
|
||||
|
||||
User: "Let's use HS256 instead, update the design"
|
||||
→ User edits design.md (or uses opsx:update in future)
|
||||
|
||||
User: "Continue implementing"
|
||||
→ openspec-apply-change: implements tasks 5, 6, 7
|
||||
→ "All tasks complete! Ready to archive."
|
||||
```
|
||||
|
||||
#### CLI Commands Used
|
||||
|
||||
```bash
|
||||
openspec list --json # List changes for selection
|
||||
openspec status --change "<name>" # Check artifact completion
|
||||
openspec instructions apply --change "<name>" # Get apply instructions (NEW)
|
||||
# File reads via Read tool for proposal, specs, design, tasks
|
||||
# File edits via Edit tool for checking off tasks
|
||||
```
|
||||
|
||||
#### New CLI Command: `openspec instructions apply`
|
||||
|
||||
For consistency with artifact instructions.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
openspec instructions apply --change "<name>" [--json]
|
||||
```
|
||||
|
||||
**Output (Markdown format):**
|
||||
```markdown
|
||||
## Apply: add-user-auth
|
||||
|
||||
### Context Files
|
||||
- proposal: openspec/changes/add-user-auth/proposal.md
|
||||
- specs: openspec/changes/add-user-auth/specs/**/*.md
|
||||
- design: openspec/changes/add-user-auth/design.md
|
||||
- tasks: openspec/changes/add-user-auth/tasks.md
|
||||
|
||||
### Progress
|
||||
2/7 complete
|
||||
|
||||
### Tasks
|
||||
- [x] Create UserAuth service class
|
||||
- [x] Add login endpoint
|
||||
- [ ] Add JWT token generation
|
||||
- [ ] Add logout endpoint
|
||||
- [ ] Add auth middleware
|
||||
- [ ] Write unit tests
|
||||
- [ ] Update API documentation
|
||||
|
||||
### Instruction
|
||||
Read context files, work through pending tasks, mark complete as you go.
|
||||
Pause if you hit blockers or need clarification.
|
||||
```
|
||||
|
||||
**Benefits of CLI command:**
|
||||
- **Consistency** - same pattern as `openspec instructions <artifact>`
|
||||
- **Structured output** - progress, tasks, context paths in one call
|
||||
- **Clean format** - markdown is readable and compact (vs verbose XML)
|
||||
- **Extensibility** - can add more sections later if needed
|
||||
- **JSON option** - `--json` flag available for programmatic use
|
||||
|
||||
#### Differences from Old `/openspec:apply`
|
||||
|
||||
| Aspect | Old `/openspec:apply` | New `openspec-apply-change` |
|
||||
|--------|----------------------|----------------------------|
|
||||
| Invocation | After all artifacts done | Anytime (if tasks.md exists) |
|
||||
| Granularity | All tasks at once | All tasks, but pauses on issues |
|
||||
| Artifact updates | Not mentioned | Encouraged when needed |
|
||||
| Progress tracking | Update all at end | Update after each task |
|
||||
| Flow control | Push through everything | Pause on blockers, resume after |
|
||||
| Context loading | Read once at start | Read context, reference as needed |
|
||||
| Issue handling | Not specified | Pause, present options, wait for guidance |
|
||||
|
||||
#### Implementation Notes
|
||||
|
||||
1. **Add CLI command**: Add `openspec instructions apply` to artifact-workflow.ts
|
||||
- Parse tasks.md for progress (count done/pending)
|
||||
- Return context paths, progress, task list, simple instruction
|
||||
2. **Add to skill-templates.ts**: Create `getApplyChangeSkillTemplate()` function
|
||||
3. **Update artifact-experimental-setup**: Generate this skill alongside new/continue
|
||||
4. **Update skills list**: Add to `.claude/skills/` directory
|
||||
5. **Test the flow**: Verify it works with existing changes that have tasks.md
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ~~Review this plan and confirm scope~~ (Done - blockers identified)
|
||||
2. ~~Design decisions~~ (Done - all 3 blockers resolved)
|
||||
3. ~~Design apply skill~~ (Done - documented above)
|
||||
4. ~~Implement proposal template change (Decision 1 - capability discovery)~~ (Done)
|
||||
5. ~~Remove `openspec next` command (Decision 2a)~~ (Done)
|
||||
6. ~~Add `openspec instructions apply` CLI command~~ (Done)
|
||||
7. ~~Create `openspec-apply-change` skill~~ (Done)
|
||||
8. Conduct E2E testing with updated workflow
|
||||
9. Write user docs (document "actions on a change" model)
|
||||
10. Release to test users
|
||||
@@ -1,107 +0,0 @@
|
||||
# Experimental Workflow (OPSX)
|
||||
|
||||
> **Status:** Experimental. Things might break. Feedback welcome on [Discord](https://discord.gg/BYjPaKbqMt).
|
||||
>
|
||||
> **Compatibility:** Claude Code only (for now)
|
||||
|
||||
## What Is It?
|
||||
|
||||
OPSX is a new way to work with OpenSpec changes. Instead of one big proposal, you build **artifacts** step-by-step:
|
||||
|
||||
```
|
||||
proposal → specs → design → tasks → implementation → archive
|
||||
```
|
||||
|
||||
Each artifact has dependencies. Can't write tasks until you have specs. Can't implement until you have tasks. The system tracks what's ready and what's blocked.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# 1. Make sure you have openspec installed and initialized
|
||||
openspec init
|
||||
|
||||
# 2. Generate the experimental skills
|
||||
openspec artifact-experimental-setup
|
||||
```
|
||||
|
||||
This creates skills in `.claude/skills/` that Claude Code auto-detects.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | What it does |
|
||||
|---------|--------------|
|
||||
| `/opsx:new` | Start a new change |
|
||||
| `/opsx:continue` | Create the next artifact |
|
||||
| `/opsx:ff` | Fast-forward (create all artifacts at once) |
|
||||
| `/opsx:apply` | Implement the tasks |
|
||||
| `/opsx:sync` | Sync delta specs to main specs |
|
||||
| `/opsx:archive` | Archive when done |
|
||||
|
||||
## Usage
|
||||
|
||||
### Start a new change
|
||||
```
|
||||
/opsx:new
|
||||
```
|
||||
You'll be asked what you want to build and which workflow schema to use.
|
||||
|
||||
### Build artifacts step-by-step
|
||||
```
|
||||
/opsx:continue
|
||||
```
|
||||
Creates one artifact at a time. Good for reviewing each step.
|
||||
|
||||
### Or fast-forward
|
||||
```
|
||||
/opsx:ff add-dark-mode
|
||||
```
|
||||
Creates all artifacts in one go. Good when you know what you want.
|
||||
|
||||
### Implement
|
||||
```
|
||||
/opsx:apply
|
||||
```
|
||||
Works through tasks, checking them off as you go.
|
||||
|
||||
### Sync specs and archive
|
||||
```
|
||||
/opsx:sync # Update main specs with your delta specs
|
||||
/opsx:archive # Move to archive when done
|
||||
```
|
||||
|
||||
## What's Different?
|
||||
|
||||
**Standard workflow** (`/openspec:proposal`):
|
||||
- One big proposal document
|
||||
- Linear phases: plan → implement → archive
|
||||
- All-or-nothing artifact creation
|
||||
|
||||
**Experimental workflow** (`/opsx:*`):
|
||||
- Discrete artifacts with dependencies
|
||||
- Fluid actions (not phases) - update artifacts anytime
|
||||
- Step-by-step or fast-forward
|
||||
- Schema-driven (can customize the workflow)
|
||||
|
||||
The key insight: work isn't linear. You implement, realize the design is wrong, update it, continue. OPSX supports this.
|
||||
|
||||
## Schemas
|
||||
|
||||
Schemas define what artifacts exist and their dependencies. Currently available:
|
||||
|
||||
- **spec-driven** (default): proposal → specs → design → tasks
|
||||
- **tdd**: tests → implementation → docs
|
||||
|
||||
Run `openspec schemas` to see available schemas.
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `/opsx:ff` when you have a clear idea, `/opsx:continue` when exploring
|
||||
- Tasks track progress via checkboxes in `tasks.md`
|
||||
- Delta specs (in `specs/`) get synced to main specs with `/opsx:sync`
|
||||
- If you get stuck, the status command shows what's blocked: `openspec status --change "name"`
|
||||
|
||||
## Feedback
|
||||
|
||||
This is rough. That's intentional - we're learning what works.
|
||||
|
||||
Found a bug? Have ideas? Join us on [Discord](https://discord.gg/BYjPaKbqMt) or open an issue on [GitHub](https://github.com/Fission-AI/openspec/issues).
|
||||
@@ -1,211 +0,0 @@
|
||||
# Schema Customization
|
||||
|
||||
This document describes how users can customize OpenSpec schemas and templates, the current manual process, and the gap that needs to be addressed.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
OpenSpec uses a 2-level schema resolution system following the XDG Base Directory Specification:
|
||||
|
||||
1. **User override**: `${XDG_DATA_HOME}/openspec/schemas/<name>/`
|
||||
2. **Package built-in**: `<npm-package>/schemas/<name>/`
|
||||
|
||||
When a schema is requested (e.g., `spec-driven`), the resolver checks the user directory first. If found, that entire schema directory is used. Otherwise, it falls back to the package's built-in schema.
|
||||
|
||||
---
|
||||
|
||||
## Current Manual Process
|
||||
|
||||
To override the default `spec-driven` schema, a user must:
|
||||
|
||||
### 1. Determine the correct directory path
|
||||
|
||||
| Platform | Path |
|
||||
|----------|------|
|
||||
| macOS/Linux | `~/.local/share/openspec/schemas/` |
|
||||
| Windows | `%LOCALAPPDATA%\openspec\schemas\` |
|
||||
| All (if set) | `$XDG_DATA_HOME/openspec/schemas/` |
|
||||
|
||||
### 2. Create the directory structure
|
||||
|
||||
```bash
|
||||
# macOS/Linux example
|
||||
mkdir -p ~/.local/share/openspec/schemas/spec-driven/templates
|
||||
```
|
||||
|
||||
### 3. Find and copy the default schema files
|
||||
|
||||
The user must locate the installed npm package to copy the defaults:
|
||||
|
||||
```bash
|
||||
# Find the package location (varies by install method)
|
||||
npm list -g openspec --parseable
|
||||
# or
|
||||
which openspec && readlink -f $(which openspec)
|
||||
|
||||
# Copy files from the package's schemas/ directory
|
||||
cp <package-path>/schemas/spec-driven/schema.yaml ~/.local/share/openspec/schemas/spec-driven/
|
||||
cp <package-path>/schemas/spec-driven/templates/*.md ~/.local/share/openspec/schemas/spec-driven/templates/
|
||||
```
|
||||
|
||||
### 4. Modify the copied files
|
||||
|
||||
Edit `schema.yaml` to change the workflow structure:
|
||||
|
||||
```yaml
|
||||
name: spec-driven
|
||||
version: 1
|
||||
description: My custom workflow
|
||||
artifacts:
|
||||
- id: proposal
|
||||
generates: proposal.md
|
||||
description: Initial proposal
|
||||
template: proposal.md
|
||||
requires: []
|
||||
# Add, remove, or modify artifacts...
|
||||
```
|
||||
|
||||
Edit templates in `templates/` to customize the content guidance.
|
||||
|
||||
### 5. Verify the override is active
|
||||
|
||||
Currently there's no command to verify which schema is being used. Users must trust that the file exists in the right location.
|
||||
|
||||
---
|
||||
|
||||
## Gap Analysis
|
||||
|
||||
The current process has several friction points:
|
||||
|
||||
| Issue | Impact |
|
||||
|-------|--------|
|
||||
| **Path discovery** | Users must know XDG conventions and platform-specific paths |
|
||||
| **Package location** | Finding the npm package path varies by install method (global, local, pnpm, yarn, volta, etc.) |
|
||||
| **No scaffolding** | Users must manually create directories and copy files |
|
||||
| **No verification** | No way to confirm which schema is actually being resolved |
|
||||
| **No diffing** | When upgrading openspec, users can't see what changed in built-in templates |
|
||||
| **Full copy required** | Must copy entire schema even to change one template |
|
||||
|
||||
### User Stories Not Currently Supported
|
||||
|
||||
1. *"I want to add a `research` artifact before `proposal`"* — requires manual copy and edit
|
||||
2. *"I want to customize just the proposal template"* — must copy entire schema
|
||||
3. *"I want to see what the default schema looks like"* — must find package path
|
||||
4. *"I want to revert to defaults"* — must delete files and hope paths are correct
|
||||
5. *"I upgraded openspec, did the templates change?"* — no way to diff
|
||||
|
||||
---
|
||||
|
||||
## Proposed Solution: Schema Configurator
|
||||
|
||||
A CLI command (or set of commands) that handles path resolution and file operations for users.
|
||||
|
||||
### Option A: Single `openspec schema` command
|
||||
|
||||
```bash
|
||||
# List available schemas (built-in and user overrides)
|
||||
openspec schema list
|
||||
|
||||
# Show where a schema resolves from
|
||||
openspec schema which spec-driven
|
||||
# Output: /Users/me/.local/share/openspec/schemas/spec-driven/ (user override)
|
||||
# Output: /usr/local/lib/node_modules/openspec/schemas/spec-driven/ (built-in)
|
||||
|
||||
# Copy a built-in schema to user directory for customization
|
||||
openspec schema copy spec-driven
|
||||
# Creates ~/.local/share/openspec/schemas/spec-driven/ with all files
|
||||
|
||||
# Show diff between user override and built-in
|
||||
openspec schema diff spec-driven
|
||||
|
||||
# Remove user override (revert to built-in)
|
||||
openspec schema reset spec-driven
|
||||
|
||||
# Validate a schema
|
||||
openspec schema validate spec-driven
|
||||
```
|
||||
|
||||
### Option B: Dedicated `openspec customize` command
|
||||
|
||||
```bash
|
||||
# Interactive schema customization
|
||||
openspec customize
|
||||
# Prompts: Which schema? What do you want to change? etc.
|
||||
|
||||
# Copy and open for editing
|
||||
openspec customize spec-driven
|
||||
# Copies to user dir, prints path, optionally opens in $EDITOR
|
||||
```
|
||||
|
||||
### Option C: Init-time schema selection
|
||||
|
||||
```bash
|
||||
# During project init, offer schema customization
|
||||
openspec init
|
||||
# ? Select a workflow schema:
|
||||
# > spec-driven (default)
|
||||
# tdd
|
||||
# minimal
|
||||
# custom (copy and edit)
|
||||
```
|
||||
|
||||
### Recommended Approach
|
||||
|
||||
**Option A** provides the most flexibility and follows Unix conventions (subcommands for discrete operations). Key commands in priority order:
|
||||
|
||||
1. `openspec schema list` — see what's available
|
||||
2. `openspec schema which <name>` — debug resolution
|
||||
3. `openspec schema copy <name>` — scaffold customization
|
||||
4. `openspec schema diff <name>` — compare with built-in
|
||||
5. `openspec schema reset <name>` — revert to defaults
|
||||
|
||||
---
|
||||
|
||||
## Implementation Considerations
|
||||
|
||||
### Path Resolution
|
||||
|
||||
The resolver already exists in `src/core/artifact-graph/resolver.ts`:
|
||||
|
||||
```typescript
|
||||
export function getPackageSchemasDir(): string { ... }
|
||||
export function getUserSchemasDir(): string { ... }
|
||||
export function getSchemaDir(name: string): string | null { ... }
|
||||
export function listSchemas(): string[] { ... }
|
||||
```
|
||||
|
||||
New commands would leverage these existing functions.
|
||||
|
||||
### File Operations
|
||||
|
||||
- Copy should preserve file permissions
|
||||
- Copy should not overwrite existing user files without `--force`
|
||||
- Reset should prompt for confirmation
|
||||
|
||||
### Template-Only Overrides
|
||||
|
||||
A future enhancement could support overriding individual templates without copying the entire schema. This would require changes to the resolution logic:
|
||||
|
||||
```
|
||||
Current: schema dir (user) OR schema dir (built-in)
|
||||
Future: schema.yaml from user OR built-in
|
||||
+ each template from user OR built-in (independent fallback)
|
||||
```
|
||||
|
||||
This adds complexity but enables the "I just want to change one template" use case.
|
||||
|
||||
---
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [Schema Workflow Gaps](./schema-workflow-gaps.md) — End-to-end workflow analysis and phased implementation plan
|
||||
|
||||
## Related Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/core/artifact-graph/resolver.ts` | Schema resolution logic |
|
||||
| `src/core/artifact-graph/instruction-loader.ts` | Template loading |
|
||||
| `src/core/global-config.ts` | XDG path helpers |
|
||||
| `schemas/spec-driven/` | Default schema and templates |
|
||||
@@ -1,378 +0,0 @@
|
||||
# Schema Workflow: End-to-End Analysis
|
||||
|
||||
This document analyzes the complete user journey for working with schemas in OpenSpec, identifies gaps, and proposes a phased solution.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
### What Exists
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| Schema resolution (XDG) | 2-level: user override → package built-in |
|
||||
| Built-in schemas | `spec-driven`, `tdd` |
|
||||
| Artifact workflow commands | `status`, `next`, `instructions`, `templates` with `--schema` flag |
|
||||
| Change creation | `openspec new change <name>` — no schema binding |
|
||||
|
||||
### What's Missing
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| Schema bound to change | Not stored — must pass `--schema` every time |
|
||||
| Project-local schemas | Not supported — can't version control with repo |
|
||||
| Schema management CLI | None — manual path discovery required |
|
||||
| Project default schema | None — hardcoded to `spec-driven` |
|
||||
|
||||
---
|
||||
|
||||
## User Journey Analysis
|
||||
|
||||
### Scenario 1: Using a Non-Default Schema
|
||||
|
||||
**Goal:** User wants to use TDD workflow for a new feature.
|
||||
|
||||
**Today's experience:**
|
||||
```bash
|
||||
openspec new change add-auth
|
||||
# Creates directory, no schema info stored
|
||||
|
||||
openspec status --change add-auth
|
||||
# Shows spec-driven artifacts (WRONG - user wanted TDD)
|
||||
|
||||
# User realizes mistake...
|
||||
openspec status --change add-auth --schema tdd
|
||||
# Correct, but must remember --schema every time
|
||||
|
||||
# 6 months later...
|
||||
openspec status --change add-auth
|
||||
# Wrong again - nobody remembers this was TDD
|
||||
```
|
||||
|
||||
**Problems:**
|
||||
- Schema is a runtime argument, not persisted
|
||||
- Easy to forget `--schema` and get wrong results
|
||||
- No record of intended schema for future reference
|
||||
|
||||
---
|
||||
|
||||
### Scenario 2: Customizing a Schema
|
||||
|
||||
**Goal:** User wants to add a "research" artifact before "proposal".
|
||||
|
||||
**Today's experience:**
|
||||
```bash
|
||||
# Step 1: Figure out where to put overrides
|
||||
# Must know XDG conventions:
|
||||
# macOS/Linux: ~/.local/share/openspec/schemas/
|
||||
# Windows: %LOCALAPPDATA%\openspec\schemas/
|
||||
|
||||
# Step 2: Create directory structure
|
||||
mkdir -p ~/.local/share/openspec/schemas/my-workflow/templates
|
||||
|
||||
# Step 3: Find the npm package to copy defaults
|
||||
npm list -g openspec --parseable
|
||||
# Output varies by package manager:
|
||||
# npm: /usr/local/lib/node_modules/openspec
|
||||
# pnpm: ~/.local/share/pnpm/global/5/node_modules/openspec
|
||||
# volta: ~/.volta/tools/image/packages/openspec/...
|
||||
# yarn: ~/.config/yarn/global/node_modules/openspec
|
||||
|
||||
# Step 4: Copy files
|
||||
cp -r <package-path>/schemas/spec-driven/* \
|
||||
~/.local/share/openspec/schemas/my-workflow/
|
||||
|
||||
# Step 5: Edit schema.yaml and templates
|
||||
# No way to verify override is active
|
||||
# No way to diff against original
|
||||
```
|
||||
|
||||
**Problems:**
|
||||
- Must know XDG path conventions
|
||||
- Finding npm package path varies by install method
|
||||
- No tooling to scaffold or verify
|
||||
- No diff capability when upgrading openspec
|
||||
|
||||
---
|
||||
|
||||
### Scenario 3: Team Sharing Custom Workflow
|
||||
|
||||
**Goal:** Team wants everyone to use the same custom schema.
|
||||
|
||||
**Today's options:**
|
||||
1. Everyone manually sets up XDG override — error-prone, drift risk
|
||||
2. Document setup in README — still manual, easy to miss
|
||||
3. Publish separate npm package — overkill for most teams
|
||||
4. Check schema into repo — **not supported** (no project-local resolution)
|
||||
|
||||
**Problems:**
|
||||
- No project-local schema resolution
|
||||
- Can't version control custom schemas with the codebase
|
||||
- No single source of truth for team workflow
|
||||
|
||||
---
|
||||
|
||||
## Gap Summary
|
||||
|
||||
| Gap | Impact | Workaround |
|
||||
|-----|--------|------------|
|
||||
| Schema not bound to change | Wrong results, forgotten context | Remember to pass `--schema` |
|
||||
| No project-local schemas | Can't share via repo | Manual XDG setup per machine |
|
||||
| No schema management CLI | Manual path hunting | Know XDG + find npm package |
|
||||
| No project default schema | Must specify every time | Always pass `--schema` |
|
||||
| No init-time schema selection | Missed setup opportunity | Manual config |
|
||||
|
||||
---
|
||||
|
||||
## Proposed Architecture
|
||||
|
||||
### New File Structure
|
||||
|
||||
```
|
||||
openspec/
|
||||
├── config.yaml # Project config (NEW)
|
||||
├── schemas/ # Project-local schemas (NEW)
|
||||
│ └── my-workflow/
|
||||
│ ├── schema.yaml
|
||||
│ └── templates/
|
||||
│ ├── research.md
|
||||
│ ├── proposal.md
|
||||
│ └── ...
|
||||
└── changes/
|
||||
└── add-auth/
|
||||
├── change.yaml # Change metadata (NEW)
|
||||
├── proposal.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
### config.yaml (Project Config)
|
||||
|
||||
```yaml
|
||||
# openspec/config.yaml
|
||||
defaultSchema: spec-driven
|
||||
```
|
||||
|
||||
Sets the project-wide default schema. Used when:
|
||||
- Creating new changes without `--schema`
|
||||
- Running commands on changes without `change.yaml`
|
||||
|
||||
### change.yaml (Change Metadata)
|
||||
|
||||
```yaml
|
||||
# openspec/changes/add-auth/change.yaml
|
||||
schema: tdd
|
||||
created: 2025-01-15T10:30:00Z
|
||||
description: Add user authentication system
|
||||
```
|
||||
|
||||
Binds a specific schema to a change. Created automatically by `openspec new change`.
|
||||
|
||||
### Schema Resolution Order
|
||||
|
||||
```
|
||||
1. ./openspec/schemas/<name>/ # Project-local
|
||||
2. ~/.local/share/openspec/schemas/<name>/ # User global (XDG)
|
||||
3. <npm-package>/schemas/<name>/ # Built-in
|
||||
```
|
||||
|
||||
Project-local takes priority, enabling version-controlled custom schemas.
|
||||
|
||||
### Schema Selection Order (Per Command)
|
||||
|
||||
```
|
||||
1. --schema CLI flag # Explicit override
|
||||
2. change.yaml in change directory # Change-specific binding
|
||||
3. openspec/config.yaml defaultSchema # Project default
|
||||
4. "spec-driven" # Hardcoded fallback
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ideal User Experience
|
||||
|
||||
### Creating a Change
|
||||
|
||||
```bash
|
||||
# Uses project default (from config.yaml, or spec-driven)
|
||||
openspec new change add-auth
|
||||
# Creates openspec/changes/add-auth/change.yaml:
|
||||
# schema: spec-driven
|
||||
# created: 2025-01-15T10:30:00Z
|
||||
|
||||
# Explicit schema for this change
|
||||
openspec new change add-auth --schema tdd
|
||||
# Creates change.yaml with schema: tdd
|
||||
```
|
||||
|
||||
### Working with Changes
|
||||
|
||||
```bash
|
||||
# Auto-reads schema from change.yaml — no --schema needed
|
||||
openspec status --change add-auth
|
||||
# Output: "Change: add-auth (schema: tdd)"
|
||||
# Shows which artifacts are ready/blocked/done
|
||||
|
||||
# Explicit override still works (with informational message)
|
||||
openspec status --change add-auth --schema spec-driven
|
||||
# "Note: change.yaml specifies 'tdd', using 'spec-driven' per --schema flag"
|
||||
```
|
||||
|
||||
### Customizing Schemas
|
||||
|
||||
```bash
|
||||
# See what's available
|
||||
openspec schema list
|
||||
# Built-in:
|
||||
# spec-driven proposal → specs → design → tasks
|
||||
# tdd spec → tests → implementation → docs
|
||||
# Project: (none)
|
||||
# User: (none)
|
||||
|
||||
# Copy to project for customization
|
||||
openspec schema copy spec-driven my-workflow
|
||||
# Created ./openspec/schemas/my-workflow/
|
||||
# Edit schema.yaml and templates/ to customize
|
||||
|
||||
# Copy to global (user-level override)
|
||||
openspec schema copy spec-driven --global
|
||||
# Created ~/.local/share/openspec/schemas/spec-driven/
|
||||
|
||||
# See where a schema resolves from
|
||||
openspec schema which spec-driven
|
||||
# ./openspec/schemas/spec-driven/ (project)
|
||||
# or: ~/.local/share/openspec/schemas/spec-driven/ (user)
|
||||
# or: /usr/local/lib/node_modules/openspec/schemas/spec-driven/ (built-in)
|
||||
|
||||
# Compare override with built-in
|
||||
openspec schema diff spec-driven
|
||||
# Shows diff between user/project version and package built-in
|
||||
|
||||
# Remove override, revert to built-in
|
||||
openspec schema reset spec-driven
|
||||
# Removes ./openspec/schemas/spec-driven/ (or --global for user dir)
|
||||
```
|
||||
|
||||
### Project Setup
|
||||
|
||||
```bash
|
||||
openspec init
|
||||
# ? Select default workflow schema:
|
||||
# > spec-driven (proposal → specs → design → tasks)
|
||||
# tdd (spec → tests → implementation → docs)
|
||||
# (custom schemas if detected)
|
||||
#
|
||||
# Writes to openspec/config.yaml:
|
||||
# defaultSchema: spec-driven
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Change Metadata (change.yaml)
|
||||
|
||||
**Priority:** High
|
||||
**Solves:** "Forgot --schema", lost context, wrong results
|
||||
|
||||
**Scope:**
|
||||
- Create `change.yaml` when running `openspec new change`
|
||||
- Store `schema`, `created` timestamp
|
||||
- Modify workflow commands to read schema from `change.yaml`
|
||||
- `--schema` flag overrides (with informational message)
|
||||
- Backwards compatible: missing `change.yaml` → use default
|
||||
|
||||
**change.yaml format:**
|
||||
```yaml
|
||||
schema: tdd
|
||||
created: 2025-01-15T10:30:00Z
|
||||
```
|
||||
|
||||
**Migration:**
|
||||
- Existing changes without `change.yaml` continue to work
|
||||
- Default to `spec-driven` (current behavior)
|
||||
- Optional: `openspec migrate` to add `change.yaml` to existing changes
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Project-Local Schemas
|
||||
|
||||
**Priority:** High
|
||||
**Solves:** Team sharing, version control, no XDG knowledge needed
|
||||
|
||||
**Scope:**
|
||||
- Add `./openspec/schemas/` to resolution order (first priority)
|
||||
- `openspec schema copy <name> [new-name]` creates in project by default
|
||||
- `--global` flag for user-level XDG directory
|
||||
- Teams can commit `openspec/schemas/` to repo
|
||||
|
||||
**Resolution order:**
|
||||
```
|
||||
1. ./openspec/schemas/<name>/ # Project-local (NEW)
|
||||
2. ~/.local/share/openspec/schemas/<name>/ # User global
|
||||
3. <npm-package>/schemas/<name>/ # Built-in
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Schema Management CLI
|
||||
|
||||
**Priority:** Medium
|
||||
**Solves:** Path discovery, scaffolding, debugging
|
||||
|
||||
**Commands:**
|
||||
```bash
|
||||
openspec schema list # Show available schemas with sources
|
||||
openspec schema which <name> # Show resolution path
|
||||
openspec schema copy <name> [to] # Copy for customization
|
||||
openspec schema diff <name> # Compare with built-in
|
||||
openspec schema reset <name> # Remove override
|
||||
openspec schema validate <name> # Validate schema.yaml structure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Project Config + Init Enhancement
|
||||
|
||||
**Priority:** Low
|
||||
**Solves:** Project-wide defaults, streamlined setup
|
||||
|
||||
**Scope:**
|
||||
- Add `openspec/config.yaml` with `defaultSchema` field
|
||||
- `openspec init` prompts for schema selection
|
||||
- Store selection in `config.yaml`
|
||||
- Commands use as fallback when no `change.yaml` exists
|
||||
|
||||
**config.yaml format:**
|
||||
```yaml
|
||||
defaultSchema: spec-driven
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backwards Compatibility
|
||||
|
||||
| Scenario | Behavior |
|
||||
|----------|----------|
|
||||
| Existing change without `change.yaml` | Uses `--schema` flag or project default or `spec-driven` |
|
||||
| Existing project without `config.yaml` | Falls back to `spec-driven` |
|
||||
| `--schema` flag provided | Overrides `change.yaml` (with info message) |
|
||||
| No project-local schemas dir | Skipped in resolution, checks user/built-in |
|
||||
|
||||
All existing functionality continues to work. New features are additive.
|
||||
|
||||
---
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [Schema Customization](./schema-customization.md) — Details on manual override process and CLI gaps
|
||||
- [Artifact POC](./artifact_poc.md) — Core artifact graph architecture
|
||||
|
||||
## Related Code
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/core/artifact-graph/resolver.ts` | Schema resolution logic |
|
||||
| `src/core/artifact-graph/instruction-loader.ts` | Template loading |
|
||||
| `src/core/global-config.ts` | XDG path helpers |
|
||||
| `src/commands/artifact-workflow.ts` | CLI commands |
|
||||
| `src/utils/change-utils.ts` | Change creation utilities |
|
||||
@@ -0,0 +1,9 @@
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Configuration
|
||||
The init command SHALL generate slash command files for supported editors using shared templates.
|
||||
|
||||
#### Scenario: Generating slash commands for Antigravity
|
||||
- **WHEN** the user selects Antigravity during initialization
|
||||
- **THEN** create `.agent/workflows/openspec-proposal.md`, `.agent/workflows/openspec-apply.md`, and `.agent/workflows/openspec-archive.md`
|
||||
- **AND** ensure each file begins with YAML frontmatter that contains only a `description: <stage summary>` field followed by the shared OpenSpec workflow instructions wrapped in managed markers
|
||||
- **AND** populate the workflow body with the same proposal/apply/archive guidance used for other tools so Antigravity behaves like Windsurf while pointing to the `.agent/workflows/` directory
|
||||
@@ -0,0 +1,8 @@
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Updates
|
||||
The update command SHALL refresh existing slash command files for configured tools without creating new ones, and ensure the OpenCode archive command accepts change ID arguments.
|
||||
|
||||
#### Scenario: Updating slash commands for Antigravity
|
||||
- **WHEN** `.agent/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh the OpenSpec-managed portion of each file so the workflow copy matches other tools while preserving the existing single-field `description` frontmatter
|
||||
- **AND** skip creating any missing workflow files during update, mirroring the behavior for Windsurf and other IDEs
|
||||
@@ -0,0 +1,11 @@
|
||||
## Why
|
||||
Manual setup for new changes leads to formatting mistakes in spec deltas and slows agents who must recreate the same file skeletons for every proposal. A built-in scaffold command will generate compliant templates so assistants can focus on the change content instead of structure.
|
||||
|
||||
## What Changes
|
||||
- Add an `openspec scaffold <change-id>` CLI command that creates a change directory (if it does not already exist) with validated `proposal.md`, `tasks.md`, and spec delta templates.
|
||||
- Update CLI documentation and quick-reference guidance so agents discover the scaffold workflow before drafting files manually, including reminders on when to create spec deltas.
|
||||
- Add automated coverage (unit/integ tests) to ensure the command respects naming rules, copies templates correctly, fails for existing directories, and produces output that passes `openspec validate --strict` untouched.
|
||||
|
||||
## Impact
|
||||
- Affected specs: `specs/cli-scaffold`
|
||||
- Affected code: `src/cli/index.ts`, `src/commands`, `docs/`
|
||||
@@ -0,0 +1,36 @@
|
||||
## ADDED Requirements
|
||||
### Requirement: Scaffolding Command Registration
|
||||
The CLI SHALL expose an `openspec scaffold <change-id>` command that validates the change identifier before generating files.
|
||||
|
||||
#### Scenario: Registering scaffold command
|
||||
- **WHEN** a user runs `openspec scaffold add-user-notifications`
|
||||
- **THEN** the CLI SHALL reject invalid identifiers (non kebab-case) before proceeding
|
||||
- **AND** display usage documentation via `openspec scaffold --help`
|
||||
- **AND** exit with code 0 after successful scaffolding
|
||||
|
||||
### Requirement: Change Directory Structure
|
||||
The scaffold command SHALL create the standard change workspace (if it does not already exist) with proposal, tasks, optional design, and `specs/` directories laid out according to OpenSpec conventions.
|
||||
|
||||
#### Scenario: Generating change workspace
|
||||
- **WHEN** scaffolding a new change with id `add-user-notifications`
|
||||
- **THEN** create `openspec/changes/add-user-notifications/` if it does not exist
|
||||
- **AND** copy the default template bundle (proposal, tasks, design placeholders) into that directory in a single operation
|
||||
- **AND** create an empty `openspec/changes/add-user-notifications/specs/` directory ready for capability-specific deltas that will be authored later
|
||||
|
||||
### Requirement: Template Content Guidance
|
||||
The scaffold command SHALL populate generated Markdown files with OpenSpec-compliant templates so authors can copy, edit, and pass validation without reformatting.
|
||||
|
||||
#### Scenario: Populating proposal and tasks templates
|
||||
- **WHEN** the scaffold command writes `proposal.md`
|
||||
- **THEN** include the `## Why`, `## What Changes`, and `## Impact` headings with placeholder guidance text
|
||||
- **AND** ensure `tasks.md` starts with `## 1. Implementation` and numbered checklist items using `- [ ]` syntax
|
||||
- **AND** annotate optional sections (like `design.md`) with inline TODO comments so users understand when to keep or delete them
|
||||
- **AND** include a short reminder inside `specs/README.md` (or similar) instructing authors to add deltas once they know the affected capability
|
||||
|
||||
### Requirement: Idempotent Execution
|
||||
The scaffold command SHALL be safe to rerun, preserving user edits while filling in any missing managed sections.
|
||||
|
||||
#### Scenario: Rerunning scaffold on existing change
|
||||
- **WHEN** the command is executed again for an existing change directory containing user-edited files
|
||||
- **THEN** leave existing content untouched except for managed placeholder regions or missing files that need creation
|
||||
- **AND** update the filesystem summary to highlight which files were skipped, created, or refreshed
|
||||
@@ -0,0 +1,12 @@
|
||||
## 1. CLI scaffolding command
|
||||
- [ ] 1.1 Register an `openspec scaffold` command in the CLI entrypoint with `change-id` argument validation.
|
||||
- [ ] 1.2 Implement generator logic that copies the default change template bundle (`proposal.md`, `tasks.md`, optional `design.md`, `specs/README.md`) into `openspec/changes/<id>/`, creating the directory tree in a single pass.
|
||||
- [ ] 1.3 Detect when `openspec/changes/<id>/` already exists and exit with a clear error instead of overwriting user files.
|
||||
|
||||
## 2. Templates and documentation
|
||||
- [ ] 2.1 Update `openspec/AGENTS.md` quick reference so agents see `openspec scaffold` before drafting files manually.
|
||||
- [ ] 2.2 Refresh CLI docs/README/help text to mention the scaffold workflow, template bundle contents, and when to add spec deltas manually.
|
||||
|
||||
## 3. Test coverage
|
||||
- [ ] 3.1 Add unit tests covering name validation, template copying, and existing-directory failures.
|
||||
- [ ] 3.2 Add integration coverage ensuring a freshly scaffolded change (without deltas) passes `openspec validate --strict` until the author customizes it.
|
||||
@@ -1,197 +0,0 @@
|
||||
## Context
|
||||
|
||||
This implements "Slice 1: What's Ready?" from the artifact POC analysis. The core insight is using the filesystem as a database - artifact completion is detected by file existence, making the system stateless and version-control friendly.
|
||||
|
||||
This module will coexist with the current OpenSpec system as a parallel capability, potentially enabling future migration or integration.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Pure dependency graph logic with no side effects
|
||||
- Stateless state detection (rescan filesystem each query)
|
||||
- Support glob patterns for multi-file artifacts (e.g., `specs/*.md`)
|
||||
- Load artifact definitions from YAML schemas
|
||||
- Calculate topological build order
|
||||
- Determine "ready" artifacts based on dependency completion
|
||||
|
||||
**Non-Goals:**
|
||||
- CLI commands (Slice 4)
|
||||
- Multi-change management (Slice 2)
|
||||
- Template resolution and enrichment (Slice 3)
|
||||
- Agent integration or Claude commands
|
||||
- Replacing existing OpenSpec functionality
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision: Filesystem as Database
|
||||
Use file existence for state detection rather than a separate state file.
|
||||
|
||||
**Rationale:**
|
||||
- Stateless - no state corruption possible
|
||||
- Git-friendly - state derived from committed files
|
||||
- Simple - no sync issues between state file and actual files
|
||||
|
||||
**Alternatives considered:**
|
||||
- JSON/SQLite state file: More complex, sync issues, not git-friendly
|
||||
- Git metadata: Too coupled to git, complex implementation
|
||||
|
||||
### Decision: Kahn's Algorithm for Topological Sort
|
||||
Use Kahn's algorithm for computing build order.
|
||||
|
||||
**Rationale:**
|
||||
- Well-understood, O(V+E) complexity
|
||||
- Naturally detects cycles during execution
|
||||
- Produces a stable, deterministic order
|
||||
|
||||
### Decision: Glob Pattern Support
|
||||
Support glob patterns like `specs/*.md` in artifact `generates` field.
|
||||
|
||||
**Rationale:**
|
||||
- Allows multiple files to satisfy a single artifact requirement
|
||||
- Common pattern for spec directories with multiple files
|
||||
- Uses standard glob syntax
|
||||
|
||||
### Decision: Immutable Completed Set
|
||||
Represent completion state as an immutable Set of completed artifact IDs.
|
||||
|
||||
**Rationale:**
|
||||
- Functional style, easier to reason about
|
||||
- State derived fresh each query, no mutation needed
|
||||
- Clear separation between graph structure and runtime state
|
||||
- Filesystem can only detect binary existence (complete vs not complete)
|
||||
|
||||
**Note:** `inProgress` and `failed` states are deferred to future slices. They would require external state tracking (e.g., a status file) since file existence alone cannot distinguish these states.
|
||||
|
||||
### Decision: Zod for Schema Validation
|
||||
Use Zod for validating YAML schema structure and deriving TypeScript types.
|
||||
|
||||
**Rationale:**
|
||||
- Already a project dependency (v4.0.17) used in `src/core/schemas/`
|
||||
- Type inference via `z.infer<>` - single source of truth for types
|
||||
- Runtime validation with detailed error messages
|
||||
- Consistent with existing project patterns (`base.schema.ts`, `config-schema.ts`)
|
||||
|
||||
**Alternatives considered:**
|
||||
- Manual validation: More code, error-prone, no type inference
|
||||
- JSON Schema: Would require additional dependency, less TypeScript integration
|
||||
- io-ts: Not already in project, steeper learning curve
|
||||
|
||||
### Decision: Two-Level Schema Resolution
|
||||
Schemas resolve from global user data directory, falling back to package built-ins.
|
||||
|
||||
**Resolution order:**
|
||||
1. `${XDG_DATA_HOME:-~/.local/share}/openspec/schemas/<name>.yaml` - Global user override
|
||||
2. `<package>/schemas/<name>.yaml` - Built-in defaults
|
||||
|
||||
**Rationale:**
|
||||
- Follows XDG Base Directory Specification (schemas are data, not config)
|
||||
- Mirrors existing `getGlobalConfigDir()` pattern in `src/core/global-paths.ts`
|
||||
- Built-ins baked into package, never auto-copied
|
||||
- Users customize by creating files in global data dir
|
||||
- Simple - no project-level overrides (can add later if needed)
|
||||
|
||||
**XDG compliance:**
|
||||
- Uses `XDG_DATA_HOME` env var when set (all platforms)
|
||||
- Unix/macOS fallback: `~/.local/share/openspec/`
|
||||
- Windows fallback: `%LOCALAPPDATA%/openspec/`
|
||||
|
||||
**Alternatives considered:**
|
||||
- Project-level overrides: Added complexity, not needed initially
|
||||
- Auto-copy to user space: Creates drift, harder to update defaults
|
||||
- Config directory (`XDG_CONFIG_HOME`): Schemas are workflow definitions (data), not user preferences (config)
|
||||
|
||||
### Decision: Template Field Parsed But Not Resolved
|
||||
The `template` field is required in schema YAML for completeness, but template resolution is deferred to Slice 3.
|
||||
|
||||
**Rationale:**
|
||||
- Slice 1 focuses on "What's Ready?" - dependency and completion queries only
|
||||
- Template paths are validated syntactically (non-empty string) but not resolved
|
||||
- Keeps Slice 1 focused and independently testable
|
||||
|
||||
### Decision: Cycle Error Format
|
||||
Cycle errors list all artifact IDs in the cycle for easy debugging.
|
||||
|
||||
**Format:** `"Cyclic dependency detected: A → B → C → A"`
|
||||
|
||||
**Rationale:**
|
||||
- Shows the full cycle path, not just that a cycle exists
|
||||
- Actionable - developer can see exactly which artifacts to fix
|
||||
- Consistent with Kahn's algorithm which naturally identifies cycle participants
|
||||
|
||||
## Data Structures
|
||||
|
||||
**Zod Schemas (source of truth):**
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
|
||||
// Artifact definition schema
|
||||
export const ArtifactSchema = z.object({
|
||||
id: z.string().min(1, 'Artifact ID is required'),
|
||||
generates: z.string().min(1), // e.g., "proposal.md" or "specs/*.md"
|
||||
description: z.string(),
|
||||
template: z.string(), // path to template file
|
||||
requires: z.array(z.string()).default([]),
|
||||
});
|
||||
|
||||
// Full schema YAML structure
|
||||
export const SchemaYamlSchema = z.object({
|
||||
name: z.string().min(1, 'Schema name is required'),
|
||||
version: z.number().int().positive(),
|
||||
description: z.string().optional(),
|
||||
artifacts: z.array(ArtifactSchema).min(1, 'At least one artifact required'),
|
||||
});
|
||||
|
||||
// Derived TypeScript types
|
||||
export type Artifact = z.infer<typeof ArtifactSchema>;
|
||||
export type SchemaYaml = z.infer<typeof SchemaYamlSchema>;
|
||||
```
|
||||
|
||||
**Runtime State (not Zod - internal only):**
|
||||
|
||||
```typescript
|
||||
// Slice 1: Simple completion tracking via filesystem
|
||||
type CompletedSet = Set<string>;
|
||||
|
||||
// Return type for blocked query
|
||||
interface BlockedArtifacts {
|
||||
[artifactId: string]: string[]; // artifact → list of unmet dependencies
|
||||
}
|
||||
|
||||
interface ArtifactGraphResult {
|
||||
completed: string[];
|
||||
ready: string[];
|
||||
blocked: BlockedArtifacts;
|
||||
buildOrder: string[];
|
||||
}
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/core/artifact-graph/
|
||||
├── index.ts # Public exports
|
||||
├── types.ts # Zod schemas and type definitions
|
||||
├── graph.ts # ArtifactGraph class
|
||||
├── state.ts # State detection logic
|
||||
├── resolver.ts # Schema resolution (global → built-in)
|
||||
└── schemas/ # Built-in schema definitions (package level)
|
||||
├── spec-driven.yaml # Default: proposal → specs → design → tasks
|
||||
└── tdd.yaml # Alternative: tests → implementation → docs
|
||||
```
|
||||
|
||||
**Schema Resolution Paths:**
|
||||
- Global user override: `${XDG_DATA_HOME:-~/.local/share}/openspec/schemas/<name>.yaml`
|
||||
- Package built-in: `src/core/artifact-graph/schemas/<name>.yaml` (bundled with package)
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Glob pattern edge cases | Use well-tested glob library (fast-glob or similar) |
|
||||
| Cycle detection | Kahn's algorithm naturally fails on cycles; provide clear error |
|
||||
| Schema evolution | Version field in schema, validate on load |
|
||||
|
||||
## Open Questions
|
||||
|
||||
None - all questions resolved in Decisions section.
|
||||
@@ -1,18 +0,0 @@
|
||||
## Why
|
||||
|
||||
The current OpenSpec system relies on conventions and AI inference for artifact ordering. A formal artifact graph with dependency awareness would enable deterministic "what's ready?" queries, making the system more predictable and enabling future features like automated pipeline execution.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `ArtifactGraph` class to model artifacts as a DAG with dependency relationships
|
||||
- Add `ArtifactState` type to track completion status (completed, in_progress, failed)
|
||||
- Add filesystem-based state detection using file existence and glob patterns
|
||||
- Add schema YAML parser to load artifact definitions
|
||||
- Implement topological sort (Kahn's algorithm) for build order calculation
|
||||
- Add `getNextArtifacts()` to find artifacts ready for creation
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: New `artifact-graph` capability
|
||||
- Affected code: `src/core/artifact-graph/` (new directory)
|
||||
- No changes to existing functionality - this is a parallel module
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Schema Loading
|
||||
The system SHALL load artifact graph definitions from YAML schema files.
|
||||
|
||||
#### Scenario: Valid schema loaded
|
||||
- **WHEN** a valid schema YAML file is provided
|
||||
- **THEN** the system returns an ArtifactGraph with all artifacts and dependencies
|
||||
|
||||
#### Scenario: Invalid schema rejected
|
||||
- **WHEN** a schema YAML file is missing required fields
|
||||
- **THEN** the system throws an error with a descriptive message
|
||||
|
||||
#### Scenario: Cyclic dependencies detected
|
||||
- **WHEN** a schema contains cyclic artifact dependencies
|
||||
- **THEN** the system throws an error listing the artifact IDs in the cycle
|
||||
|
||||
#### Scenario: Invalid dependency reference
|
||||
- **WHEN** an artifact's `requires` array references a non-existent artifact ID
|
||||
- **THEN** the system throws an error identifying the invalid reference
|
||||
|
||||
#### Scenario: Duplicate artifact IDs rejected
|
||||
- **WHEN** a schema contains multiple artifacts with the same ID
|
||||
- **THEN** the system throws an error identifying the duplicate
|
||||
|
||||
### Requirement: Build Order Calculation
|
||||
The system SHALL compute a valid topological build order for artifacts.
|
||||
|
||||
#### Scenario: Linear dependency chain
|
||||
- **WHEN** artifacts form a linear chain (A → B → C)
|
||||
- **THEN** getBuildOrder() returns [A, B, C]
|
||||
|
||||
#### Scenario: Diamond dependency
|
||||
- **WHEN** artifacts form a diamond (A → B, A → C, B → D, C → D)
|
||||
- **THEN** getBuildOrder() returns A before B and C, and D last
|
||||
|
||||
#### Scenario: Independent artifacts
|
||||
- **WHEN** artifacts have no dependencies
|
||||
- **THEN** getBuildOrder() returns them in a stable order
|
||||
|
||||
### Requirement: State Detection
|
||||
The system SHALL detect artifact completion state by scanning the filesystem.
|
||||
|
||||
#### Scenario: Simple file exists
|
||||
- **WHEN** an artifact generates "proposal.md" and the file exists
|
||||
- **THEN** the artifact is marked as completed
|
||||
|
||||
#### Scenario: Simple file missing
|
||||
- **WHEN** an artifact generates "proposal.md" and the file does not exist
|
||||
- **THEN** the artifact is not marked as completed
|
||||
|
||||
#### Scenario: Glob pattern with files
|
||||
- **WHEN** an artifact generates "specs/*.md" and the specs/ directory contains .md files
|
||||
- **THEN** the artifact is marked as completed
|
||||
|
||||
#### Scenario: Glob pattern empty
|
||||
- **WHEN** an artifact generates "specs/*.md" and the specs/ directory is empty or missing
|
||||
- **THEN** the artifact is not marked as completed
|
||||
|
||||
#### Scenario: Missing change directory
|
||||
- **WHEN** the change directory does not exist
|
||||
- **THEN** all artifacts are marked as not completed (empty state)
|
||||
|
||||
### Requirement: Ready Artifact Query
|
||||
The system SHALL identify which artifacts are ready to be created based on dependency completion.
|
||||
|
||||
#### Scenario: Root artifacts ready initially
|
||||
- **WHEN** no artifacts are completed
|
||||
- **THEN** getNextArtifacts() returns artifacts with no dependencies
|
||||
|
||||
#### Scenario: Dependent artifact becomes ready
|
||||
- **WHEN** an artifact's dependencies are all completed
|
||||
- **THEN** getNextArtifacts() includes that artifact
|
||||
|
||||
#### Scenario: Blocked artifacts excluded
|
||||
- **WHEN** an artifact has uncompleted dependencies
|
||||
- **THEN** getNextArtifacts() does not include that artifact
|
||||
|
||||
### Requirement: Completion Check
|
||||
The system SHALL determine when all artifacts in a graph are complete.
|
||||
|
||||
#### Scenario: All complete
|
||||
- **WHEN** all artifacts in the graph are in the completed set
|
||||
- **THEN** isComplete() returns true
|
||||
|
||||
#### Scenario: Partially complete
|
||||
- **WHEN** some artifacts in the graph are not completed
|
||||
- **THEN** isComplete() returns false
|
||||
|
||||
### Requirement: Blocked Query
|
||||
The system SHALL identify which artifacts are blocked and return all their unmet dependencies.
|
||||
|
||||
#### Scenario: Artifact blocked by single dependency
|
||||
- **WHEN** artifact B requires artifact A and A is not complete
|
||||
- **THEN** getBlocked() returns `{ B: ['A'] }`
|
||||
|
||||
#### Scenario: Artifact blocked by multiple dependencies
|
||||
- **WHEN** artifact C requires A and B, and only A is complete
|
||||
- **THEN** getBlocked() returns `{ C: ['B'] }`
|
||||
|
||||
#### Scenario: Artifact blocked by all dependencies
|
||||
- **WHEN** artifact C requires A and B, and neither is complete
|
||||
- **THEN** getBlocked() returns `{ C: ['A', 'B'] }`
|
||||
@@ -1,61 +0,0 @@
|
||||
## 1. Type Definitions
|
||||
- [x] 1.1 Create `src/core/artifact-graph/types.ts` with Zod schemas (`ArtifactSchema`, `SchemaYamlSchema`) and inferred types via `z.infer<>`
|
||||
- [x] 1.2 Define `CompletedSet` (Set<string>), `BlockedArtifacts`, and `ArtifactGraphResult` types for runtime state
|
||||
|
||||
## 2. Schema Parser
|
||||
- [x] 2.1 Create `src/core/artifact-graph/schema.ts` with YAML loading and Zod validation via `.safeParse()`
|
||||
- [x] 2.2 Implement dependency reference validation (ensure `requires` references valid artifact IDs)
|
||||
- [x] 2.3 Implement duplicate artifact ID detection
|
||||
- [x] 2.4 Add cycle detection during schema load (error format: "Cyclic dependency detected: A → B → C → A")
|
||||
|
||||
## 3. Artifact Graph Core
|
||||
- [x] 3.1 Create `src/core/artifact-graph/graph.ts` with ArtifactGraph class
|
||||
- [x] 3.2 Implement `fromYaml(path)` - load graph from schema file
|
||||
- [x] 3.3 Implement `getBuildOrder()` - topological sort via Kahn's algorithm
|
||||
- [x] 3.4 Implement `getArtifact(id)` - retrieve single artifact definition
|
||||
- [x] 3.5 Implement `getAllArtifacts()` - list all artifacts
|
||||
|
||||
## 4. State Detection
|
||||
- [x] 4.1 Create `src/core/artifact-graph/state.ts` with state detection logic
|
||||
- [x] 4.2 Implement file existence checking for simple paths
|
||||
- [x] 4.3 Implement glob pattern matching for multi-file artifacts
|
||||
- [x] 4.4 Implement `detectCompleted(graph, changeDir)` - scan filesystem and return CompletedSet
|
||||
- [x] 4.5 Handle missing changeDir gracefully (return empty CompletedSet)
|
||||
|
||||
## 5. Ready Calculation
|
||||
- [x] 5.1 Implement `getNextArtifacts(graph, completed)` - find artifacts with all deps completed
|
||||
- [x] 5.2 Implement `isComplete(graph, completed)` - check if all artifacts done
|
||||
- [x] 5.3 Implement `getBlocked(graph, completed)` - return BlockedArtifacts map (artifact → unmet deps)
|
||||
|
||||
## 6. Schema Resolution
|
||||
- [x] 6.1 Create `src/core/artifact-graph/resolver.ts` with schema resolution logic
|
||||
- [x] 6.2 Add `getGlobalDataDir()` to `src/core/global-config.ts` (XDG_DATA_HOME with platform fallbacks)
|
||||
- [x] 6.3 Implement `resolveSchema(name)` - global (`${XDG_DATA_HOME}/openspec/schemas/`) → built-in fallback
|
||||
|
||||
## 7. Built-in Schemas
|
||||
- [x] 7.1 Create `src/core/artifact-graph/schemas/spec-driven.yaml` (default: proposal → specs → design → tasks)
|
||||
- [x] 7.2 Create `src/core/artifact-graph/schemas/tdd.yaml` (alternative: tests → implementation → docs)
|
||||
|
||||
## 8. Integration
|
||||
- [x] 8.1 Create `src/core/artifact-graph/index.ts` with public exports
|
||||
|
||||
## 9. Testing
|
||||
- [x] 9.1 Test: Parse valid schema YAML returns correct artifact graph
|
||||
- [x] 9.2 Test: Parse invalid schema (missing fields) throws descriptive error
|
||||
- [x] 9.3 Test: Duplicate artifact IDs throws error
|
||||
- [x] 9.4 Test: Invalid `requires` reference throws error identifying the invalid ID
|
||||
- [x] 9.5 Test: Cycle in schema throws error listing cycle path (e.g., "A → B → C → A")
|
||||
- [x] 9.6 Test: Compute build order returns correct topological ordering (linear chain)
|
||||
- [x] 9.7 Test: Compute build order handles diamond dependencies correctly
|
||||
- [x] 9.8 Test: Independent artifacts return in stable order
|
||||
- [x] 9.9 Test: Empty/missing changeDir returns empty CompletedSet
|
||||
- [x] 9.10 Test: File existence marks artifact as completed
|
||||
- [x] 9.11 Test: Glob pattern specs/*.md detected as complete when files exist
|
||||
- [x] 9.12 Test: Glob pattern with empty directory not marked complete
|
||||
- [x] 9.13 Test: getNextArtifacts returns only root artifacts when nothing completed
|
||||
- [x] 9.14 Test: getNextArtifacts includes artifact when all deps completed
|
||||
- [x] 9.15 Test: getBlocked returns artifact with all unmet dependencies listed
|
||||
- [x] 9.16 Test: isComplete() returns true when all artifacts completed
|
||||
- [x] 9.17 Test: isComplete() returns false when some artifacts incomplete
|
||||
- [x] 9.18 Test: Schema resolution finds global override before built-in
|
||||
- [x] 9.19 Test: Schema resolution falls back to built-in when no global
|
||||
@@ -1,74 +0,0 @@
|
||||
## Context
|
||||
|
||||
This is Slice 2 of the artifact tracker POC. The goal is to provide utilities for creating change directories programmatically.
|
||||
|
||||
**Current state:** No programmatic way to create changes. Users must manually create directories.
|
||||
|
||||
**Proposed state:** Utility functions for change creation with name validation.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
### Goals
|
||||
- **Add** `createChange()` function to create change directories
|
||||
- **Add** `validateChangeName()` function for kebab-case validation
|
||||
- **Enable** automation (Claude commands, scripts) to create changes
|
||||
|
||||
### Non-Goals
|
||||
- Refactor existing CLI commands (they work fine)
|
||||
- Create abstraction layers or manager classes
|
||||
- Change how `ListCommand` or `ChangeCommand` work
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: Simple Utility Functions
|
||||
|
||||
**Choice**: Add functions to `src/utils/change-utils.ts` - no class.
|
||||
|
||||
```typescript
|
||||
// src/utils/change-utils.ts
|
||||
|
||||
export function validateChangeName(name: string): { valid: boolean; error?: string }
|
||||
|
||||
export async function createChange(
|
||||
projectRoot: string,
|
||||
name: string
|
||||
): Promise<void>
|
||||
```
|
||||
|
||||
**Why**:
|
||||
- Simple, no abstraction overhead
|
||||
- Easy to test
|
||||
- Easy to import where needed
|
||||
- Matches existing utility patterns in `src/utils/`
|
||||
|
||||
**Alternatives considered**:
|
||||
- ChangeManager class: Rejected - over-engineered for 2 functions
|
||||
- Add to existing command: Rejected - mixes CLI with reusable logic
|
||||
|
||||
### Decision 2: Kebab-Case Validation Pattern
|
||||
|
||||
**Choice**: Validate names with `^[a-z][a-z0-9]*(-[a-z0-9]+)*$`
|
||||
|
||||
Valid: `add-auth`, `refactor-db`, `add-feature-2`, `refactor`
|
||||
Invalid: `Add-Auth`, `add auth`, `add_auth`, `-add-auth`, `add-auth-`, `add--auth`
|
||||
|
||||
**Why**:
|
||||
- Filesystem-safe (no special characters)
|
||||
- URL-safe (for future web UI)
|
||||
- Consistent with existing change naming in repo
|
||||
|
||||
## File Changes
|
||||
|
||||
### New Files
|
||||
- `src/utils/change-utils.ts` - Utility functions
|
||||
- `src/utils/change-utils.test.ts` - Unit tests
|
||||
|
||||
### Modified Files
|
||||
- None
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Function might not cover all use cases | Start simple, extend if needed |
|
||||
| Naming conflicts with future work | Using clear, specific function names |
|
||||
@@ -1,45 +0,0 @@
|
||||
## Why
|
||||
|
||||
There's no programmatic way to create a new change directory. Users must manually:
|
||||
1. Create `openspec/changes/<name>/` directory
|
||||
2. Create a `proposal.md` file
|
||||
3. Hope they got the naming right
|
||||
|
||||
This is error-prone and blocks automation (e.g., Claude commands, scripts).
|
||||
|
||||
**This proposal adds:**
|
||||
1. `createChange(projectRoot, name)` - Create change directories programmatically
|
||||
2. `validateChangeName(name)` - Enforce kebab-case naming conventions
|
||||
|
||||
## What Changes
|
||||
|
||||
### New Utilities
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `createChange(projectRoot, name)` | Creates `openspec/changes/<name>/` directory |
|
||||
| `validateChangeName(name)` | Returns `{ valid: boolean; error?: string }` |
|
||||
|
||||
### Name Validation Rules
|
||||
|
||||
Pattern: `^[a-z][a-z0-9]*(-[a-z0-9]+)*$`
|
||||
|
||||
| Valid | Invalid |
|
||||
|-------|---------|
|
||||
| `add-auth` | `Add-Auth` (uppercase) |
|
||||
| `refactor-db` | `add auth` (spaces) |
|
||||
| `add-feature-2` | `add_auth` (underscores) |
|
||||
| `refactor` | `-add-auth` (leading hyphen) |
|
||||
|
||||
### Location
|
||||
|
||||
New file: `src/utils/change-utils.ts`
|
||||
|
||||
Simple utility functions - no class, no abstraction layer.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected specs**: None
|
||||
- **Affected code**: None (new utilities only)
|
||||
- **New files**: `src/utils/change-utils.ts`
|
||||
- **Breaking changes**: None
|
||||
@@ -1,63 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Change Creation
|
||||
The system SHALL provide a function to create new change directories programmatically.
|
||||
|
||||
#### Scenario: Create change
|
||||
- **WHEN** `createChange(projectRoot, 'add-auth')` is called
|
||||
- **THEN** the system creates `openspec/changes/add-auth/` directory
|
||||
|
||||
#### Scenario: Duplicate change rejected
|
||||
- **WHEN** `createChange(projectRoot, 'add-auth')` is called and `openspec/changes/add-auth/` already exists
|
||||
- **THEN** the system throws an error indicating the change already exists
|
||||
|
||||
#### Scenario: Creates parent directories if needed
|
||||
- **WHEN** `createChange(projectRoot, 'add-auth')` is called and `openspec/changes/` does not exist
|
||||
- **THEN** the system creates the full path including parent directories
|
||||
|
||||
#### Scenario: Invalid change name rejected
|
||||
- **WHEN** `createChange(projectRoot, 'Add Auth')` is called with an invalid name
|
||||
- **THEN** the system throws a validation error
|
||||
|
||||
### Requirement: Change Name Validation
|
||||
The system SHALL validate change names follow kebab-case conventions.
|
||||
|
||||
#### Scenario: Valid kebab-case name accepted
|
||||
- **WHEN** a change name like `add-user-auth` is validated
|
||||
- **THEN** validation returns `{ valid: true }`
|
||||
|
||||
#### Scenario: Numeric suffixes accepted
|
||||
- **WHEN** a change name like `add-feature-2` is validated
|
||||
- **THEN** validation returns `{ valid: true }`
|
||||
|
||||
#### Scenario: Single word accepted
|
||||
- **WHEN** a change name like `refactor` is validated
|
||||
- **THEN** validation returns `{ valid: true }`
|
||||
|
||||
#### Scenario: Uppercase characters rejected
|
||||
- **WHEN** a change name like `Add-Auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Spaces rejected
|
||||
- **WHEN** a change name like `add auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Underscores rejected
|
||||
- **WHEN** a change name like `add_auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Special characters rejected
|
||||
- **WHEN** a change name like `add-auth!` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Leading hyphen rejected
|
||||
- **WHEN** a change name like `-add-auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Trailing hyphen rejected
|
||||
- **WHEN** a change name like `add-auth-` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Consecutive hyphens rejected
|
||||
- **WHEN** a change name like `add--auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
@@ -1,30 +0,0 @@
|
||||
## Phase 1: Implement Name Validation
|
||||
|
||||
- [x] 1.1 Create `src/utils/change-utils.ts`
|
||||
- [x] 1.2 Implement `validateChangeName()` with kebab-case pattern
|
||||
- [x] 1.3 Pattern: `^[a-z][a-z0-9]*(-[a-z0-9]+)*$`
|
||||
- [x] 1.4 Return `{ valid: boolean; error?: string }`
|
||||
- [x] 1.5 Add test: valid names accepted (`add-auth`, `refactor`, `add-feature-2`)
|
||||
- [x] 1.6 Add test: uppercase rejected
|
||||
- [x] 1.7 Add test: spaces rejected
|
||||
- [x] 1.8 Add test: underscores rejected
|
||||
- [x] 1.9 Add test: special characters rejected
|
||||
- [x] 1.10 Add test: leading/trailing hyphens rejected
|
||||
- [x] 1.11 Add test: consecutive hyphens rejected
|
||||
|
||||
## Phase 2: Implement Change Creation
|
||||
|
||||
- [x] 2.1 Implement `createChange(projectRoot, name)`
|
||||
- [x] 2.2 Validate name before creating
|
||||
- [x] 2.3 Create parent directories if needed (`openspec/changes/`)
|
||||
- [x] 2.4 Throw if change already exists
|
||||
- [x] 2.5 Add test: creates directory
|
||||
- [x] 2.6 Add test: duplicate change throws error
|
||||
- [x] 2.7 Add test: invalid name throws validation error
|
||||
- [x] 2.8 Add test: creates parent directories if needed
|
||||
|
||||
## Phase 3: Integration
|
||||
|
||||
- [x] 3.1 Export functions from `src/utils/index.ts`
|
||||
- [x] 3.2 Add JSDoc comments
|
||||
- [x] 3.3 Run all tests to verify no regressions
|
||||
@@ -1,112 +0,0 @@
|
||||
## Context
|
||||
|
||||
Slice 4 of the artifact workflow POC. The core functionality (ArtifactGraph, InstructionLoader, change-utils) is complete. This slice adds CLI commands to expose the artifact workflow to users.
|
||||
|
||||
**Key constraint**: This is experimental. Commands must be isolated for easy removal if the feature doesn't work out.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
- **Goals:**
|
||||
- Expose artifact workflow status and instructions via CLI
|
||||
- Provide fluid UX with top-level verb commands
|
||||
- Support both human-readable and JSON output
|
||||
- Enable agents to programmatically query workflow state
|
||||
- Keep implementation isolated for easy removal
|
||||
|
||||
- **Non-Goals:**
|
||||
- Interactive artifact creation wizards (future work)
|
||||
- Schema management commands (deferred)
|
||||
- Auto-detection of active change (CLI is deterministic, agents infer)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Command Structure: Top-Level Verbs
|
||||
|
||||
Commands are top-level for maximum fluidity:
|
||||
|
||||
```
|
||||
openspec status --change <id>
|
||||
openspec next --change <id>
|
||||
openspec instructions <artifact> --change <id>
|
||||
openspec templates [--schema <name>]
|
||||
openspec new change <name>
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
- Most fluid UX - fewest keystrokes
|
||||
- Commands are unique enough to avoid conflicts
|
||||
- Simple mental model for users
|
||||
|
||||
**Trade-off accepted:** Slight namespace pollution, but commands are distinct and can be removed cleanly.
|
||||
|
||||
### Experimental Isolation
|
||||
|
||||
All artifact workflow commands are implemented in a single file:
|
||||
|
||||
```
|
||||
src/commands/artifact-workflow.ts
|
||||
```
|
||||
|
||||
**To remove the feature:**
|
||||
1. Delete `src/commands/artifact-workflow.ts`
|
||||
2. Remove ~5 lines from `src/cli/index.ts`
|
||||
|
||||
No other files touched, no risk to stable functionality.
|
||||
|
||||
### Deterministic CLI with Explicit `--change`
|
||||
|
||||
All change-specific commands require `--change <id>`:
|
||||
|
||||
```bash
|
||||
openspec status --change add-auth # explicit, works
|
||||
openspec status # error: missing --change
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
- CLI is pure, testable, no hidden state
|
||||
- Agents infer change from conversation and pass explicitly
|
||||
- No config file tracking "active change"
|
||||
- Consistent with POC design philosophy
|
||||
|
||||
### New Change Command Structure
|
||||
|
||||
Creating changes uses explicit subcommand:
|
||||
|
||||
```bash
|
||||
openspec new change add-feature
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
- `openspec new <name>` is ambiguous (new what?)
|
||||
- `openspec new change <name>` is clear and extensible
|
||||
- Can add `openspec new spec <name>` later if needed
|
||||
|
||||
### Output Formats
|
||||
|
||||
- **Default**: Human-readable text with visual indicators
|
||||
- Status: `[x]` done, `[ ]` ready, `[-]` blocked
|
||||
- Colors: green (done), yellow (ready), red (blocked)
|
||||
- **JSON** (`--json`): Machine-readable for scripts and agents
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Missing `--change`: Error listing available changes
|
||||
- Unknown change: Error with suggestion
|
||||
- Unknown artifact: Error listing valid artifacts
|
||||
- Missing schema: Error with schema resolution details
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Top-level commands pollute namespace | Commands are distinct; isolated for easy removal |
|
||||
| `status` confused with git | Context (`--change`) makes it clear |
|
||||
| Feature doesn't work out | Single file deletion removes everything |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- All commands in `src/commands/artifact-workflow.ts`
|
||||
- Imports from `src/core/artifact-graph/` for all operations
|
||||
- Uses `getActiveChangeIds()` from `item-discovery.ts` for change listing
|
||||
- Follows existing CLI patterns (ora spinners, commander.js options)
|
||||
- Help text marks commands as "Experimental"
|
||||
@@ -1,33 +0,0 @@
|
||||
## Why
|
||||
|
||||
The ArtifactGraph (Slice 1) and InstructionLoader (Slice 3) provide programmatic APIs for artifact-based workflow management. Users currently have no CLI interface to:
|
||||
- See artifact completion status for a change
|
||||
- Discover what artifacts are ready to create
|
||||
- Get enriched instructions for creating artifacts
|
||||
- Create new changes with proper validation
|
||||
|
||||
This proposal adds CLI commands that expose the artifact workflow functionality to users and agents.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **NEW**: `openspec status --change <id>` shows artifact completion state
|
||||
- **NEW**: `openspec next --change <id>` shows artifacts ready to create
|
||||
- **NEW**: `openspec instructions <artifact> --change <id>` outputs enriched template
|
||||
- **NEW**: `openspec templates [--schema <name>]` shows resolved template paths
|
||||
- **NEW**: `openspec new change <name>` creates a new change directory
|
||||
|
||||
All commands are top-level for fluid UX. They integrate with existing core modules:
|
||||
- Uses `loadChangeContext()`, `formatChangeStatus()`, `generateInstructions()` from instruction-loader
|
||||
- Uses `ArtifactGraph`, `detectCompleted()` from artifact-graph
|
||||
- Uses `createChange()`, `validateChangeName()` from change-utils
|
||||
|
||||
**Experimental isolation**: All commands are implemented in a single file (`src/commands/artifact-workflow.ts`) for easy removal if the feature doesn't work out. Help text marks them as experimental.
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: NEW `cli-artifact-workflow` capability
|
||||
- Affected code:
|
||||
- `src/cli/index.ts` - register new commands
|
||||
- `src/commands/artifact-workflow.ts` - new command implementations
|
||||
- No changes to existing commands or specs
|
||||
- Builds on completed Slice 1, 2, and 3 implementations
|
||||
-153
@@ -1,153 +0,0 @@
|
||||
# cli-artifact-workflow Specification
|
||||
|
||||
## Purpose
|
||||
CLI commands for artifact workflow operations, exposing the artifact graph and instruction loader functionality to users and agents. Commands are top-level for fluid UX and implemented in isolation for easy removal.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Status Command
|
||||
The system SHALL display artifact completion status for a change.
|
||||
|
||||
#### Scenario: Show status with all states
|
||||
- **WHEN** user runs `openspec status --change <id>`
|
||||
- **THEN** the system displays each artifact with status indicator:
|
||||
- `[x]` for completed artifacts
|
||||
- `[ ]` for ready artifacts
|
||||
- `[-]` for blocked artifacts (with missing dependencies listed)
|
||||
|
||||
#### Scenario: Status shows completion summary
|
||||
- **WHEN** user runs `openspec status --change <id>`
|
||||
- **THEN** output includes completion percentage and count (e.g., "2/4 artifacts complete")
|
||||
|
||||
#### Scenario: Status JSON output
|
||||
- **WHEN** user runs `openspec status --change <id> --json`
|
||||
- **THEN** the system outputs JSON with changeName, schemaName, isComplete, and artifacts array
|
||||
|
||||
#### Scenario: Missing change parameter
|
||||
- **WHEN** user runs `openspec status` without `--change`
|
||||
- **THEN** the system displays an error with list of available changes
|
||||
|
||||
#### Scenario: Unknown change
|
||||
- **WHEN** user runs `openspec status --change unknown-id`
|
||||
- **THEN** the system displays an error indicating the change does not exist
|
||||
|
||||
### Requirement: Next Command
|
||||
The system SHALL show which artifacts are ready to be created.
|
||||
|
||||
#### Scenario: Show ready artifacts
|
||||
- **WHEN** user runs `openspec next --change <id>`
|
||||
- **THEN** the system lists artifacts whose dependencies are all satisfied
|
||||
|
||||
#### Scenario: No artifacts ready
|
||||
- **WHEN** all artifacts are either completed or blocked
|
||||
- **THEN** the system indicates no artifacts are ready (with explanation)
|
||||
|
||||
#### Scenario: All artifacts complete
|
||||
- **WHEN** all artifacts in the change are completed
|
||||
- **THEN** the system indicates the change is complete
|
||||
|
||||
#### Scenario: Next JSON output
|
||||
- **WHEN** user runs `openspec next --change <id> --json`
|
||||
- **THEN** the system outputs JSON array of ready artifact IDs
|
||||
|
||||
### Requirement: Instructions Command
|
||||
The system SHALL output enriched instructions for creating an artifact.
|
||||
|
||||
#### Scenario: Show enriched instructions
|
||||
- **WHEN** user runs `openspec instructions <artifact> --change <id>`
|
||||
- **THEN** the system outputs:
|
||||
- Artifact metadata (ID, output path, description)
|
||||
- Template content
|
||||
- Dependency status (done/missing)
|
||||
- Unlocked artifacts (what becomes available after completion)
|
||||
|
||||
#### Scenario: Instructions JSON output
|
||||
- **WHEN** user runs `openspec instructions <artifact> --change <id> --json`
|
||||
- **THEN** the system outputs JSON matching ArtifactInstructions interface
|
||||
|
||||
#### Scenario: Unknown artifact
|
||||
- **WHEN** user runs `openspec instructions unknown-artifact --change <id>`
|
||||
- **THEN** the system displays an error listing valid artifact IDs for the schema
|
||||
|
||||
#### Scenario: Artifact with unmet dependencies
|
||||
- **WHEN** user requests instructions for a blocked artifact
|
||||
- **THEN** the system displays instructions with a warning about missing dependencies
|
||||
|
||||
### Requirement: Templates Command
|
||||
The system SHALL show resolved template paths for all artifacts in a schema.
|
||||
|
||||
#### Scenario: List template paths with default schema
|
||||
- **WHEN** user runs `openspec templates`
|
||||
- **THEN** the system displays each artifact with its resolved template path using the default schema
|
||||
|
||||
#### Scenario: List template paths with custom schema
|
||||
- **WHEN** user runs `openspec templates --schema tdd`
|
||||
- **THEN** the system displays template paths for the specified schema
|
||||
|
||||
#### Scenario: Templates JSON output
|
||||
- **WHEN** user runs `openspec templates --json`
|
||||
- **THEN** the system outputs JSON mapping artifact IDs to template paths
|
||||
|
||||
#### Scenario: Template resolution source
|
||||
- **WHEN** displaying template paths
|
||||
- **THEN** the system indicates whether each template is from user override or package built-in
|
||||
|
||||
### Requirement: New Change Command
|
||||
The system SHALL create new change directories with validation.
|
||||
|
||||
#### Scenario: Create valid change
|
||||
- **WHEN** user runs `openspec new change add-feature`
|
||||
- **THEN** the system creates `openspec/changes/add-feature/` directory
|
||||
|
||||
#### Scenario: Invalid change name
|
||||
- **WHEN** user runs `openspec new change "Add Feature"` with invalid name
|
||||
- **THEN** the system displays validation error with guidance
|
||||
|
||||
#### Scenario: Duplicate change name
|
||||
- **WHEN** user runs `openspec new change existing-change` for an existing change
|
||||
- **THEN** the system displays an error indicating the change already exists
|
||||
|
||||
#### Scenario: Create with description
|
||||
- **WHEN** user runs `openspec new change add-feature --description "Add new feature"`
|
||||
- **THEN** the system creates the change directory with description in README.md
|
||||
|
||||
### Requirement: Schema Selection
|
||||
The system SHALL support custom schema selection for workflow commands.
|
||||
|
||||
#### Scenario: Default schema
|
||||
- **WHEN** user runs workflow commands without `--schema`
|
||||
- **THEN** the system uses the "spec-driven" schema
|
||||
|
||||
#### Scenario: Custom schema
|
||||
- **WHEN** user runs `openspec status --change <id> --schema tdd`
|
||||
- **THEN** the system uses the specified schema for artifact graph
|
||||
|
||||
#### Scenario: Unknown schema
|
||||
- **WHEN** user specifies an unknown schema
|
||||
- **THEN** the system displays an error listing available schemas
|
||||
|
||||
### Requirement: Output Formatting
|
||||
The system SHALL provide consistent output formatting.
|
||||
|
||||
#### Scenario: Color output
|
||||
- **WHEN** terminal supports colors
|
||||
- **THEN** status indicators use colors: green (done), yellow (ready), red (blocked)
|
||||
|
||||
#### Scenario: No color output
|
||||
- **WHEN** `--no-color` flag is used or NO_COLOR environment variable is set
|
||||
- **THEN** output uses text-only indicators without ANSI colors
|
||||
|
||||
#### Scenario: Progress indication
|
||||
- **WHEN** loading change state takes time
|
||||
- **THEN** the system displays a spinner during loading
|
||||
|
||||
### Requirement: Experimental Isolation
|
||||
The system SHALL implement artifact workflow commands in isolation for easy removal.
|
||||
|
||||
#### Scenario: Single file implementation
|
||||
- **WHEN** artifact workflow feature is implemented
|
||||
- **THEN** all commands are in `src/commands/artifact-workflow.ts`
|
||||
|
||||
#### Scenario: Help text marking
|
||||
- **WHEN** user runs `--help` on any artifact workflow command
|
||||
- **THEN** help text indicates the command is experimental
|
||||
@@ -1,48 +0,0 @@
|
||||
## 1. Core Command Implementation
|
||||
|
||||
- [x] 1.1 Create `src/commands/artifact-workflow.ts` with all commands
|
||||
- [x] 1.2 Implement `status` command with text output
|
||||
- [x] 1.3 Implement `next` command with text output
|
||||
- [x] 1.4 Implement `instructions` command with text output
|
||||
- [x] 1.5 Implement `templates` command with text output
|
||||
- [x] 1.6 Implement `new change` subcommand using createChange()
|
||||
|
||||
## 2. CLI Registration
|
||||
|
||||
- [x] 2.1 Register `status` command in `src/cli/index.ts`
|
||||
- [x] 2.2 Register `next` command in `src/cli/index.ts`
|
||||
- [x] 2.3 Register `instructions` command in `src/cli/index.ts`
|
||||
- [x] 2.4 Register `templates` command in `src/cli/index.ts`
|
||||
- [x] 2.5 Register `new` command group with `change` subcommand
|
||||
|
||||
## 3. Output Formatting
|
||||
|
||||
- [x] 3.1 Add `--json` flag support to all commands
|
||||
- [x] 3.2 Add color-coded status indicators (done/ready/blocked)
|
||||
- [x] 3.3 Add progress spinner for loading operations
|
||||
- [x] 3.4 Support `--no-color` flag
|
||||
|
||||
## 4. Error Handling
|
||||
|
||||
- [x] 4.1 Handle missing `--change` parameter with helpful error
|
||||
- [x] 4.2 Handle unknown change names with list of available changes
|
||||
- [x] 4.3 Handle unknown artifact names with valid options
|
||||
- [x] 4.4 Handle schema resolution errors
|
||||
|
||||
## 5. Options and Flags
|
||||
|
||||
- [x] 5.1 Add `--schema` option for custom schema selection
|
||||
- [x] 5.2 Add `--description` option to `new change` command
|
||||
- [x] 5.3 Ensure options follow existing CLI patterns
|
||||
|
||||
## 6. Testing
|
||||
|
||||
- [x] 6.1 Add smoke tests for each command
|
||||
- [x] 6.2 Test error cases (missing change, unknown artifact)
|
||||
- [x] 6.3 Test JSON output format
|
||||
- [x] 6.4 Test with different schemas
|
||||
|
||||
## 7. Documentation
|
||||
|
||||
- [x] 7.1 Add help text for all commands marked as "Experimental"
|
||||
- [ ] 7.2 Update AGENTS.md with new commands (post-archive)
|
||||
@@ -1,149 +0,0 @@
|
||||
## Context
|
||||
|
||||
This is Slice 3 of the artifact-graph POC. We have:
|
||||
- `ArtifactGraph` class with graph operations (Slice 1)
|
||||
- `detectCompleted()` for filesystem-based state detection (Slice 1)
|
||||
- `resolveSchema()` for XDG schema resolution (Slice 1)
|
||||
- `createChange()` and `validateChangeName()` utilities (Slice 2)
|
||||
|
||||
After `restructure-schema-directories` is implemented, schemas will be self-contained directories:
|
||||
```
|
||||
schemas/<name>/
|
||||
├── schema.yaml
|
||||
└── templates/
|
||||
└── *.md
|
||||
```
|
||||
|
||||
This proposal adds template loading and instruction enrichment on top of that structure.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Load templates from schema directories
|
||||
- Enrich templates with change-specific context (dependency status)
|
||||
- Format change status for CLI output
|
||||
|
||||
**Non-Goals:**
|
||||
- Template authoring UI
|
||||
- Dynamic template compilation/execution
|
||||
- Caching (keep it stateless like the rest)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Pure functions over classes
|
||||
|
||||
Follow the pattern in `resolver.ts` and `state.ts`. Use a simple `ChangeContext` interface with pure functions:
|
||||
|
||||
```typescript
|
||||
interface ChangeContext {
|
||||
changeName: string;
|
||||
changeDir: string;
|
||||
schemaName: string;
|
||||
graph: ArtifactGraph;
|
||||
completed: CompletedSet;
|
||||
}
|
||||
|
||||
function loadChangeContext(projectRoot: string, changeName: string, schemaName?: string): ChangeContext
|
||||
function loadTemplate(schemaName: string, templatePath: string): string
|
||||
function getInstructions(artifactId: string, context: ChangeContext): string
|
||||
function formatStatus(context: ChangeContext): string
|
||||
```
|
||||
|
||||
**Why:** Matches existing codebase patterns. Easier to test. No hidden state.
|
||||
|
||||
### 2. Template resolution from schema directory
|
||||
|
||||
Templates are loaded from the schema's `templates/` subdirectory:
|
||||
|
||||
```typescript
|
||||
function loadTemplate(schemaName: string, templatePath: string): string {
|
||||
const schemaDir = getSchemaDir(schemaName); // From resolver.ts
|
||||
const fullPath = path.join(schemaDir, 'templates', templatePath);
|
||||
return fs.readFileSync(fullPath, 'utf-8');
|
||||
}
|
||||
```
|
||||
|
||||
Resolution is handled by `getSchemaDir()` which already checks user override → package built-in.
|
||||
|
||||
**Why:** Leverages existing schema resolution. Templates are co-located with schemas.
|
||||
|
||||
### 3. Template path from artifact definition
|
||||
|
||||
The artifact's `template` field is a path relative to the schema's `templates/` directory:
|
||||
|
||||
```yaml
|
||||
artifacts:
|
||||
- id: proposal
|
||||
template: "proposal.md" # → schemas/<schema>/templates/proposal.md
|
||||
```
|
||||
|
||||
**Why:** Explicit, simple, no magic.
|
||||
|
||||
### 4. Minimal context injection
|
||||
|
||||
Templates are markdown. Injection prepends a header section with context:
|
||||
|
||||
```markdown
|
||||
---
|
||||
change: add-auth
|
||||
artifact: proposal
|
||||
schema: spec-driven
|
||||
output: openspec/changes/add-auth/proposal.md
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
- [x] (none - this is a root artifact)
|
||||
|
||||
## Next Steps
|
||||
After creating this artifact, you can work on: design, specs
|
||||
|
||||
---
|
||||
|
||||
[original template content...]
|
||||
```
|
||||
|
||||
**Why:** Simple string concatenation. No template engine dependency. Clear separation.
|
||||
|
||||
### 5. Status output format
|
||||
|
||||
```markdown
|
||||
## Change: add-auth (spec-driven)
|
||||
|
||||
| Artifact | Status | Output |
|
||||
|----------|--------|--------|
|
||||
| proposal | done | proposal.md |
|
||||
| specs | ready | specs/*.md |
|
||||
| design | blocked (needs: proposal) | design.md |
|
||||
| tasks | blocked (needs: specs, design) | tasks.md |
|
||||
```
|
||||
|
||||
**Why:** Markdown table is readable in terminal and docs. Matches CLI output style.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/core/artifact-graph/
|
||||
├── index.ts # Add new exports
|
||||
├── template.ts # NEW: Template loading
|
||||
├── context.ts # NEW: ChangeContext loading
|
||||
└── instructions.ts # NEW: Enrichment and formatting
|
||||
```
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**Dependency on restructure-schema-directories:**
|
||||
- This proposal requires the schema restructure to be done first
|
||||
- Mitigation: Clear dependency documented, implement in order
|
||||
|
||||
**No template engine:**
|
||||
- Pro: Zero dependencies, simple code
|
||||
- Con: Limited expressiveness
|
||||
- Mitigation: Current use case only needs static templates + header injection
|
||||
|
||||
## Migration Plan
|
||||
|
||||
N/A - new capability, no existing code to migrate.
|
||||
|
||||
## Open Questions
|
||||
|
||||
None.
|
||||
@@ -1,20 +0,0 @@
|
||||
## Why
|
||||
|
||||
Slice 1 (artifact-graph) provides graph operations and state detection. Slice 2 (change-utils) provides change creation. We now need the ability to load templates for artifacts and enrich them with change-specific context so users/agents know what to create next.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add template resolution from schema directories (uses structure from `restructure-schema-directories`)
|
||||
- Add instruction enrichment that injects change context into templates
|
||||
- Add status formatting for CLI output
|
||||
- New `instruction-loader` capability
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires `restructure-schema-directories` to be implemented first (schemas as directories with co-located templates)
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: New `instruction-loader` spec
|
||||
- Affected code: `src/core/artifact-graph/` (new files)
|
||||
- Builds on: `artifact-graph` (Slice 1), uses `ArtifactGraph`, `detectCompleted`, `resolveSchema`
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
# instruction-loader Specification
|
||||
|
||||
## Purpose
|
||||
Load templates from schema directories and enrich them with change-specific context for guiding artifact creation.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Template Loading
|
||||
The system SHALL load templates from schema directories.
|
||||
|
||||
#### Scenario: Load template from schema directory
|
||||
- **WHEN** `loadTemplate(schemaName, templatePath)` is called
|
||||
- **THEN** the system loads the template from `schemas/<schemaName>/templates/<templatePath>`
|
||||
|
||||
#### Scenario: Template file not found
|
||||
- **WHEN** a template file does not exist in the schema's templates directory
|
||||
- **THEN** the system throws an error with the template path
|
||||
|
||||
### Requirement: Change Context Loading
|
||||
The system SHALL load change context combining graph and completion state.
|
||||
|
||||
#### Scenario: Load context for existing change
|
||||
- **WHEN** `loadChangeContext(projectRoot, changeName)` is called for an existing change
|
||||
- **THEN** the system returns a context with graph, completed set, schema name, and change info
|
||||
|
||||
#### Scenario: Load context with custom schema
|
||||
- **WHEN** `loadChangeContext(projectRoot, changeName, schemaName)` is called
|
||||
- **THEN** the system uses the specified schema instead of default
|
||||
|
||||
#### Scenario: Load context for non-existent change directory
|
||||
- **WHEN** `loadChangeContext` is called for a non-existent change directory
|
||||
- **THEN** the system returns context with empty completed set
|
||||
|
||||
### Requirement: Template Enrichment
|
||||
The system SHALL enrich templates with change-specific context.
|
||||
|
||||
#### Scenario: Include artifact metadata
|
||||
- **WHEN** instructions are generated for an artifact
|
||||
- **THEN** the output includes change name, artifact ID, schema name, and output path
|
||||
|
||||
#### Scenario: Include dependency status
|
||||
- **WHEN** an artifact has dependencies
|
||||
- **THEN** the output shows each dependency with completion status (done/missing)
|
||||
|
||||
#### Scenario: Include unlocked artifacts
|
||||
- **WHEN** instructions are generated
|
||||
- **THEN** the output includes which artifacts become available after this one
|
||||
|
||||
#### Scenario: Root artifact indicator
|
||||
- **WHEN** an artifact has no dependencies
|
||||
- **THEN** the dependency section indicates this is a root artifact
|
||||
|
||||
### Requirement: Status Formatting
|
||||
The system SHALL format change status as readable output.
|
||||
|
||||
#### Scenario: All artifacts completed
|
||||
- **WHEN** all artifacts are completed
|
||||
- **THEN** status shows all artifacts as "done"
|
||||
|
||||
#### Scenario: Mixed completion status
|
||||
- **WHEN** some artifacts are completed
|
||||
- **THEN** status shows completed as "done", ready as "ready", blocked as "blocked"
|
||||
|
||||
#### Scenario: Blocked artifact details
|
||||
- **WHEN** an artifact is blocked
|
||||
- **THEN** status shows which dependencies are missing
|
||||
|
||||
#### Scenario: Include output paths
|
||||
- **WHEN** status is formatted
|
||||
- **THEN** each artifact shows its output path pattern
|
||||
@@ -1,13 +0,0 @@
|
||||
# Tasks
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- [x] Create `instruction-loader` spec in `openspec/specs/instruction-loader/spec.md`
|
||||
- [x] Implement `loadTemplate` function to load templates from schema directories
|
||||
- [x] Implement `loadChangeContext` function to combine graph and completion state
|
||||
- [x] Implement `generateInstructions` function to enrich templates with change context
|
||||
- [x] Implement `formatChangeStatus` function for readable status output
|
||||
- [x] Export new functions from `src/core/artifact-graph/index.ts`
|
||||
- [x] Add comprehensive tests in `test/core/artifact-graph/instruction-loader.test.ts`
|
||||
- [x] Verify build passes
|
||||
- [x] Verify all tests pass
|
||||
@@ -1,129 +0,0 @@
|
||||
## Context
|
||||
|
||||
Built-in schemas are currently embedded as TypeScript objects:
|
||||
|
||||
```typescript
|
||||
// src/core/artifact-graph/builtin-schemas.ts
|
||||
export const SPEC_DRIVEN_SCHEMA: SchemaYaml = {
|
||||
name: 'spec-driven',
|
||||
version: 1,
|
||||
artifacts: [...]
|
||||
};
|
||||
```
|
||||
|
||||
This doesn't support templates co-located with schemas. The instruction loader (Slice 3) needs templates, and the cleanest approach is self-contained schema directories.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Schemas as self-contained directories (schema.yaml + templates/)
|
||||
- User overrides via XDG data directory
|
||||
- Simple 2-level resolution (user → package)
|
||||
- Templates co-located with their schema
|
||||
|
||||
**Non-Goals:**
|
||||
- Shared template fallback (intentionally avoiding complexity)
|
||||
- Runtime schema compilation
|
||||
- Schema inheritance
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Directory structure
|
||||
|
||||
Each schema is a directory containing `schema.yaml` and `templates/`:
|
||||
|
||||
```
|
||||
<package>/schemas/
|
||||
├── spec-driven/
|
||||
│ ├── schema.yaml
|
||||
│ └── templates/
|
||||
│ ├── proposal.md
|
||||
│ ├── design.md
|
||||
│ ├── spec.md
|
||||
│ └── tasks.md
|
||||
└── tdd/
|
||||
├── schema.yaml
|
||||
└── templates/
|
||||
├── spec.md
|
||||
├── test.md
|
||||
├── implementation.md
|
||||
└── docs.md
|
||||
```
|
||||
|
||||
**Why:** Self-contained like Helm charts. No cross-schema dependencies. Each schema owns its templates.
|
||||
|
||||
### 2. Resolution order (2 levels)
|
||||
|
||||
```
|
||||
1. ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml # User override
|
||||
2. <package>/schemas/<name>/schema.yaml # Built-in
|
||||
3. Error (not found)
|
||||
```
|
||||
|
||||
**Why:** Simple mental model. User can override entire schema directory or just parts.
|
||||
|
||||
### 3. Template path in schema.yaml
|
||||
|
||||
The `template` field is relative to the schema's `templates/` directory:
|
||||
|
||||
```yaml
|
||||
# schemas/spec-driven/schema.yaml
|
||||
artifacts:
|
||||
- id: proposal
|
||||
template: "proposal.md" # → schemas/spec-driven/templates/proposal.md
|
||||
```
|
||||
|
||||
**Why:** Paths are relative to the schema, not a global templates directory.
|
||||
|
||||
### 4. Resolve package directory via import.meta.url
|
||||
|
||||
```typescript
|
||||
function getPackageSchemasDir(): string {
|
||||
const currentFile = fileURLToPath(import.meta.url);
|
||||
// Navigate from src/core/artifact-graph/ to package root
|
||||
return path.join(path.dirname(currentFile), '..', '..', '..', 'schemas');
|
||||
}
|
||||
```
|
||||
|
||||
**Why:** Works in ESM. No hardcoded paths.
|
||||
|
||||
### 5. Keep schema.yaml format unchanged
|
||||
|
||||
The YAML format stays the same - only the storage location changes:
|
||||
|
||||
```yaml
|
||||
name: spec-driven
|
||||
version: 1
|
||||
description: Specification-driven development
|
||||
artifacts:
|
||||
- id: proposal
|
||||
generates: "proposal.md"
|
||||
template: "proposal.md"
|
||||
requires: []
|
||||
```
|
||||
|
||||
**Why:** No breaking changes to schema format. Just moving from TS to YAML files.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Create `schemas/` directory at package root
|
||||
2. Convert `SPEC_DRIVEN_SCHEMA` to `schemas/spec-driven/schema.yaml`
|
||||
3. Convert `TDD_SCHEMA` to `schemas/tdd/schema.yaml`
|
||||
4. Update `resolveSchema()` to load from directories
|
||||
5. Remove `builtin-schemas.ts`
|
||||
6. Update `listSchemas()` to scan directories
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**File I/O at runtime:**
|
||||
- Previously schemas were in-memory objects
|
||||
- Now requires reading YAML files
|
||||
- Mitigation: Schemas are small, loaded once per operation
|
||||
|
||||
**Package distribution:**
|
||||
- Must ensure `schemas/` directory is included in npm package
|
||||
- Add to `files` in package.json
|
||||
|
||||
## Open Questions
|
||||
|
||||
None.
|
||||
@@ -1,20 +0,0 @@
|
||||
## Why
|
||||
|
||||
Currently, built-in schemas are embedded as TypeScript objects in `builtin-schemas.ts`. This works for schemas but doesn't support co-located templates. To enable self-contained schema packages (schema + templates together), we need to restructure schemas as directories.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING (internal):** Move built-in schemas from embedded TS objects to actual directory structure
|
||||
- Schemas become directories containing `schema.yaml` + `templates/`
|
||||
- Update `resolveSchema()` to load from directory structure
|
||||
- Remove `builtin-schemas.ts` (replaced by file-based schemas)
|
||||
- Update resolution to check user dir → package dir
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: `artifact-graph` (schema resolution changes)
|
||||
- Affected code:
|
||||
- Remove `src/core/artifact-graph/builtin-schemas.ts`
|
||||
- Update `src/core/artifact-graph/resolver.ts`
|
||||
- Add `schemas/` directory at package root
|
||||
- No external API changes (resolution still returns `SchemaYaml`)
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Schema Loading
|
||||
The system SHALL load artifact graph definitions from YAML schema files within schema directories.
|
||||
|
||||
#### Scenario: Valid schema loaded
|
||||
- **WHEN** a schema directory contains a valid `schema.yaml` file
|
||||
- **THEN** the system returns an ArtifactGraph with all artifacts and dependencies
|
||||
|
||||
#### Scenario: Invalid schema rejected
|
||||
- **WHEN** a schema YAML file is missing required fields
|
||||
- **THEN** the system throws an error with a descriptive message
|
||||
|
||||
#### Scenario: Cyclic dependencies detected
|
||||
- **WHEN** a schema contains cyclic artifact dependencies
|
||||
- **THEN** the system throws an error listing the artifact IDs in the cycle
|
||||
|
||||
#### Scenario: Invalid dependency reference
|
||||
- **WHEN** an artifact's `requires` array references a non-existent artifact ID
|
||||
- **THEN** the system throws an error identifying the invalid reference
|
||||
|
||||
#### Scenario: Duplicate artifact IDs rejected
|
||||
- **WHEN** a schema contains multiple artifacts with the same ID
|
||||
- **THEN** the system throws an error identifying the duplicate
|
||||
|
||||
#### Scenario: Schema directory not found
|
||||
- **WHEN** resolving a schema name that has no corresponding directory
|
||||
- **THEN** the system throws an error listing available schemas
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Schema Directory Structure
|
||||
The system SHALL support self-contained schema directories with co-located templates.
|
||||
|
||||
#### Scenario: Schema with templates
|
||||
- **WHEN** a schema directory contains `schema.yaml` and `templates/` subdirectory
|
||||
- **THEN** artifacts can reference templates relative to the schema's templates directory
|
||||
|
||||
#### Scenario: User schema override
|
||||
- **WHEN** a schema directory exists at `${XDG_DATA_HOME}/openspec/schemas/<name>/`
|
||||
- **THEN** the system uses that directory instead of the built-in
|
||||
|
||||
#### Scenario: Built-in schema fallback
|
||||
- **WHEN** no user override exists for a schema
|
||||
- **THEN** the system uses the package built-in schema directory
|
||||
|
||||
#### Scenario: List available schemas
|
||||
- **WHEN** listing schemas
|
||||
- **THEN** the system returns schema names from both user and package directories
|
||||
@@ -1,32 +0,0 @@
|
||||
## 1. Create Schema Directories
|
||||
|
||||
- [ ] 1.1 Create `schemas/` directory at package root
|
||||
- [ ] 1.2 Create `schemas/spec-driven/schema.yaml` from `SPEC_DRIVEN_SCHEMA`
|
||||
- [ ] 1.3 Create `schemas/spec-driven/templates/` with placeholder templates
|
||||
- [ ] 1.4 Create `schemas/tdd/schema.yaml` from `TDD_SCHEMA`
|
||||
- [ ] 1.5 Create `schemas/tdd/templates/` with placeholder templates
|
||||
|
||||
## 2. Update Schema Resolution
|
||||
|
||||
- [ ] 2.1 Add `getPackageSchemasDir()` function using `import.meta.url`
|
||||
- [ ] 2.2 Add `getSchemaDir(name)` to resolve schema directory path
|
||||
- [ ] 2.3 Update `resolveSchema()` to load from directory structure
|
||||
- [ ] 2.4 Update `listSchemas()` to scan directories instead of object keys
|
||||
- [ ] 2.5 Add tests for user override resolution
|
||||
- [ ] 2.6 Add tests for built-in fallback
|
||||
|
||||
## 3. Cleanup
|
||||
|
||||
- [ ] 3.1 Remove `builtin-schemas.ts`
|
||||
- [ ] 3.2 Update `index.ts` exports (remove `BUILTIN_SCHEMAS`, `SPEC_DRIVEN_SCHEMA`, `TDD_SCHEMA`)
|
||||
- [ ] 3.3 Update any code that imports removed exports
|
||||
|
||||
## 4. Package Distribution
|
||||
|
||||
- [ ] 4.1 Add `schemas/` to `files` array in `package.json`
|
||||
- [ ] 4.2 Verify schemas are included in built package
|
||||
|
||||
## 5. Fix Template Paths
|
||||
|
||||
- [ ] 5.1 Update `template` field in schema.yaml files (remove `templates/` prefix)
|
||||
- [ ] 5.2 Ensure template paths are relative to schema's templates directory
|
||||
@@ -1,151 +0,0 @@
|
||||
# Design: Unify Change State Model
|
||||
|
||||
## Overview
|
||||
|
||||
This change fixes two bugs with minimal disruption to the existing system:
|
||||
|
||||
1. **View bug**: Empty changes incorrectly shown as "Completed"
|
||||
2. **Artifact workflow bug**: Commands fail on scaffolded changes
|
||||
|
||||
## Key Design Decision: Two Systems, Two Purposes
|
||||
|
||||
The task-based and artifact-based systems serve **different purposes** and should coexist:
|
||||
|
||||
| System | Purpose | Used By |
|
||||
|--------|---------|---------|
|
||||
| **Task Progress** | Track implementation work | `openspec view`, `openspec list` |
|
||||
| **Artifact Progress** | Track planning/spec work | `openspec status`, `openspec next` |
|
||||
|
||||
We do NOT merge these systems. Instead, we fix each to work correctly in its domain.
|
||||
|
||||
## Change 1: Fix View Command
|
||||
|
||||
### Current Logic (Buggy)
|
||||
|
||||
```typescript
|
||||
// view.ts line 90
|
||||
if (progress.total === 0 || progress.completed === progress.total) {
|
||||
completed.push({ name: entry.name });
|
||||
}
|
||||
```
|
||||
|
||||
Problem: `total === 0` means "no tasks defined yet", not "all tasks done".
|
||||
|
||||
### New Logic
|
||||
|
||||
```typescript
|
||||
if (progress.total === 0) {
|
||||
draft.push({ name: entry.name });
|
||||
} else if (progress.completed === progress.total) {
|
||||
completed.push({ name: entry.name });
|
||||
} else {
|
||||
active.push({ name: entry.name, progress });
|
||||
}
|
||||
```
|
||||
|
||||
### View Output Change
|
||||
|
||||
**Before:**
|
||||
```
|
||||
Completed Changes
|
||||
─────────────────
|
||||
✓ add-feature (all tasks done - correct)
|
||||
✓ test-workflow (no tasks - WRONG)
|
||||
```
|
||||
|
||||
**After:**
|
||||
```
|
||||
Draft Changes
|
||||
─────────────────
|
||||
○ test-workflow (no tasks yet)
|
||||
|
||||
Active Changes
|
||||
─────────────────
|
||||
◉ add-scaffold [████░░░░] 3/7 tasks
|
||||
|
||||
Completed Changes
|
||||
─────────────────
|
||||
✓ add-feature (all tasks done)
|
||||
```
|
||||
|
||||
## Change 2: Fix Artifact Workflow Discovery
|
||||
|
||||
### Current Logic (Buggy)
|
||||
|
||||
```typescript
|
||||
// artifact-workflow.ts - validateChangeExists()
|
||||
const activeChanges = await getActiveChangeIds(projectRoot);
|
||||
if (!activeChanges.includes(changeName)) {
|
||||
throw new Error(`Change '${changeName}' not found...`);
|
||||
}
|
||||
```
|
||||
|
||||
Problem: `getActiveChangeIds()` requires `proposal.md`, but artifact workflow should work on empty directories to help create the first artifact.
|
||||
|
||||
### New Logic
|
||||
|
||||
```typescript
|
||||
async function validateChangeExists(changeName: string, projectRoot: string): Promise<string> {
|
||||
const changePath = path.join(projectRoot, 'openspec', 'changes', changeName);
|
||||
|
||||
// Check directory existence directly, not proposal.md
|
||||
if (!fs.existsSync(changePath) || !fs.statSync(changePath).isDirectory()) {
|
||||
// List available changes for helpful error message
|
||||
const entries = await fs.promises.readdir(
|
||||
path.join(projectRoot, 'openspec', 'changes'),
|
||||
{ withFileTypes: true }
|
||||
);
|
||||
const available = entries
|
||||
.filter(e => e.isDirectory() && e.name !== 'archive' && !e.name.startsWith('.'))
|
||||
.map(e => e.name);
|
||||
|
||||
if (available.length === 0) {
|
||||
throw new Error('No changes found. Create one with: openspec new change <name>');
|
||||
}
|
||||
throw new Error(`Change '${changeName}' not found. Available:\n ${available.join('\n ')}`);
|
||||
}
|
||||
|
||||
return changeName;
|
||||
}
|
||||
```
|
||||
|
||||
### Behavior Change
|
||||
|
||||
```bash
|
||||
# Before
|
||||
$ openspec new change foo
|
||||
$ openspec status --change foo
|
||||
Error: Change 'foo' not found.
|
||||
|
||||
# After
|
||||
$ openspec new change foo
|
||||
$ openspec status --change foo
|
||||
Change: foo
|
||||
Progress: 0/4 artifacts complete
|
||||
|
||||
[ ] proposal
|
||||
[-] specs (blocked by: proposal)
|
||||
[-] design (blocked by: proposal)
|
||||
[-] tasks (blocked by: specs, design)
|
||||
```
|
||||
|
||||
## What Stays the Same
|
||||
|
||||
1. **`getActiveChangeIds()`** - Still requires `proposal.md` (used by validate, show)
|
||||
2. **`getArchivedChangeIds()`** - Unchanged
|
||||
3. **Active/Completed semantics** - Still based on task checkboxes
|
||||
4. **Validation** - Still requires `proposal.md` to have something to validate
|
||||
|
||||
## File Changes
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/core/view.ts` | Add draft category, fix completion logic |
|
||||
| `src/commands/artifact-workflow.ts` | Update `validateChangeExists()` to use directory existence |
|
||||
| `test/commands/artifact-workflow.test.ts` | Add tests for scaffolded changes |
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Unit test**: `validateChangeExists()` with scaffolded change
|
||||
2. **View test**: Verify three categories render correctly
|
||||
3. **Manual test**: Full workflow from `new change` → `status` → `view`
|
||||
@@ -1,101 +0,0 @@
|
||||
# Proposal: Unify Change State Model
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Two bugs create inconsistent behavior when working with changes:
|
||||
|
||||
### Bug 1: Empty changes shown as "Completed" in view
|
||||
|
||||
```typescript
|
||||
// view.ts line 90
|
||||
if (progress.total === 0 || progress.completed === progress.total) {
|
||||
completed.push({ name: entry.name }); // BUG: total === 0 ≠ completed
|
||||
}
|
||||
```
|
||||
|
||||
Result: `openspec new change foo && openspec view` shows `foo` as "Completed" when it has no content.
|
||||
|
||||
### Bug 2: Artifact workflow commands can't find scaffolded changes
|
||||
|
||||
```typescript
|
||||
// item-discovery.ts - getActiveChangeIds()
|
||||
const proposalPath = path.join(changesPath, entry.name, 'proposal.md');
|
||||
await fs.access(proposalPath); // Only returns changes WITH proposal.md
|
||||
```
|
||||
|
||||
Result: `openspec status --change foo` says "not found" even though the directory exists.
|
||||
|
||||
## Root Cause
|
||||
|
||||
The system conflates two different concepts:
|
||||
|
||||
| Concept | Question | Source of Truth |
|
||||
|---------|----------|-----------------|
|
||||
| **Planning Progress** | Are all spec documents created? | File existence (ArtifactGraph) |
|
||||
| **Implementation Progress** | Is the coding work done? | Task checkboxes (tasks.md) |
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### Fix 1: Add "Draft" state to view command
|
||||
|
||||
Keep Active/Completed with their existing meanings, but fix the bug:
|
||||
|
||||
| State | Criteria | Meaning |
|
||||
|-------|----------|---------|
|
||||
| **Draft** | No tasks.md OR `tasks.total === 0` | Still planning |
|
||||
| **Active** | `tasks.total > 0` AND `completed < total` | Implementing |
|
||||
| **Completed** | `tasks.total > 0` AND `completed === total` | Done |
|
||||
|
||||
### Fix 2: Artifact workflow uses directory existence
|
||||
|
||||
Update `validateChangeExists()` to check if the directory exists, not if `proposal.md` exists. This allows the artifact workflow to guide users through creating their first artifact.
|
||||
|
||||
### Keep existing discovery functions
|
||||
|
||||
`getActiveChangeIds()` continues to require `proposal.md` for backward compatibility with validation and other commands.
|
||||
|
||||
## What Changes
|
||||
|
||||
| Command | Before | After |
|
||||
|---------|--------|-------|
|
||||
| `openspec view` | Empty = "Completed" | Empty = "Draft" |
|
||||
| `openspec status --change X` | Requires proposal.md | Works on any directory |
|
||||
| `openspec validate X` | Requires proposal.md | Unchanged (still requires it) |
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Minimal Breaking Change
|
||||
|
||||
1. **`openspec view` output**: Empty changes move from "Completed" section to new "Draft" section
|
||||
|
||||
### Non-Breaking
|
||||
|
||||
- Active/Completed semantics unchanged (still task-based)
|
||||
- `getActiveChangeIds()` unchanged
|
||||
- `openspec validate` unchanged
|
||||
- Archived changes unaffected
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Merging task-based and artifact-based progress (they serve different purposes)
|
||||
- Changing what "Completed" means (it stays = all tasks done)
|
||||
- Adding artifact progress to view command (separate enhancement)
|
||||
- Shell tab completions for artifact workflow commands (not yet registered)
|
||||
|
||||
## Related Commands Analysis
|
||||
|
||||
| Command | Uses `getActiveChangeIds()` | Should include scaffolded? | Change needed? |
|
||||
|---------|-----------------------------|-----------------------------|----------------|
|
||||
| `openspec view` | No (reads dirs directly) | Yes → Draft section | **Yes** |
|
||||
| `openspec list` | No (reads dirs directly) | Yes (shows "No tasks") | No |
|
||||
| `openspec status/next/instructions` | Yes | Yes | **Yes** |
|
||||
| `openspec validate` | Yes | No (can't validate empty) | No |
|
||||
| `openspec show` | Yes | No (nothing to show) | No |
|
||||
| Tab completions | Yes | Future enhancement | No |
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. `openspec new change foo && openspec view` shows `foo` in "Draft" section
|
||||
2. `openspec new change foo && openspec status --change foo` works
|
||||
3. Changes with all tasks done still show as "Completed"
|
||||
4. All existing tests pass
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
# cli-artifact-workflow Specification Delta
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Status Command
|
||||
|
||||
The system SHALL display artifact completion status for a change, including scaffolded (empty) changes.
|
||||
|
||||
> **Fixes bug**: Previously required `proposal.md` to exist via `getActiveChangeIds()`.
|
||||
|
||||
#### Scenario: Show status with all states
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id>`
|
||||
- **THEN** the system displays each artifact with status indicator:
|
||||
- `[x]` for completed artifacts
|
||||
- `[ ]` for ready artifacts
|
||||
- `[-]` for blocked artifacts (with missing dependencies listed)
|
||||
|
||||
#### Scenario: Status shows completion summary
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id>`
|
||||
- **THEN** output includes completion percentage and count (e.g., "2/4 artifacts complete")
|
||||
|
||||
#### Scenario: Status JSON output
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id> --json`
|
||||
- **THEN** the system outputs JSON with changeName, schemaName, isComplete, and artifacts array
|
||||
|
||||
#### Scenario: Status on scaffolded change
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id>` on a change with no artifacts
|
||||
- **THEN** system displays all artifacts with their status
|
||||
- **AND** root artifacts (no dependencies) show as ready `[ ]`
|
||||
- **AND** dependent artifacts show as blocked `[-]`
|
||||
|
||||
#### Scenario: Missing change parameter
|
||||
|
||||
- **WHEN** user runs `openspec status` without `--change`
|
||||
- **THEN** the system displays an error with list of available changes
|
||||
- **AND** includes scaffolded changes (directories without proposal.md)
|
||||
|
||||
#### Scenario: Unknown change
|
||||
|
||||
- **WHEN** user runs `openspec status --change unknown-id`
|
||||
- **AND** directory `openspec/changes/unknown-id/` does not exist
|
||||
- **THEN** the system displays an error listing all available change directories
|
||||
|
||||
### Requirement: Next Command
|
||||
|
||||
The system SHALL show which artifacts are ready to be created, including for scaffolded changes.
|
||||
|
||||
#### Scenario: Show ready artifacts
|
||||
|
||||
- **WHEN** user runs `openspec next --change <id>`
|
||||
- **THEN** the system lists artifacts whose dependencies are all satisfied
|
||||
|
||||
#### Scenario: No artifacts ready
|
||||
|
||||
- **WHEN** all artifacts are either completed or blocked
|
||||
- **THEN** the system indicates no artifacts are ready (with explanation)
|
||||
|
||||
#### Scenario: All artifacts complete
|
||||
|
||||
- **WHEN** all artifacts in the change are completed
|
||||
- **THEN** the system indicates the change is complete
|
||||
|
||||
#### Scenario: Next JSON output
|
||||
|
||||
- **WHEN** user runs `openspec next --change <id> --json`
|
||||
- **THEN** the system outputs JSON array of ready artifact IDs
|
||||
|
||||
#### Scenario: Next on scaffolded change
|
||||
|
||||
- **WHEN** user runs `openspec next --change <id>` on a change with no artifacts
|
||||
- **THEN** system shows root artifacts (e.g., "proposal") as ready to create
|
||||
|
||||
### Requirement: Instructions Command
|
||||
|
||||
The system SHALL output enriched instructions for creating an artifact, including for scaffolded changes.
|
||||
|
||||
#### Scenario: Show enriched instructions
|
||||
|
||||
- **WHEN** user runs `openspec instructions <artifact> --change <id>`
|
||||
- **THEN** the system outputs:
|
||||
- Artifact metadata (ID, output path, description)
|
||||
- Template content
|
||||
- Dependency status (done/missing)
|
||||
- Unlocked artifacts (what becomes available after completion)
|
||||
|
||||
#### Scenario: Instructions JSON output
|
||||
|
||||
- **WHEN** user runs `openspec instructions <artifact> --change <id> --json`
|
||||
- **THEN** the system outputs JSON matching ArtifactInstructions interface
|
||||
|
||||
#### Scenario: Unknown artifact
|
||||
|
||||
- **WHEN** user runs `openspec instructions unknown-artifact --change <id>`
|
||||
- **THEN** the system displays an error listing valid artifact IDs for the schema
|
||||
|
||||
#### Scenario: Artifact with unmet dependencies
|
||||
|
||||
- **WHEN** user requests instructions for a blocked artifact
|
||||
- **THEN** the system displays instructions with a warning about missing dependencies
|
||||
|
||||
#### Scenario: Instructions on scaffolded change
|
||||
|
||||
- **WHEN** user runs `openspec instructions proposal --change <id>` on a scaffolded change
|
||||
- **THEN** system outputs template and metadata for creating the proposal
|
||||
- **AND** does not require any artifacts to already exist
|
||||
@@ -1,60 +0,0 @@
|
||||
# cli-view Specification Delta
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Draft Changes Display
|
||||
|
||||
The dashboard SHALL display changes without tasks in a separate "Draft" section.
|
||||
|
||||
#### Scenario: Draft changes listing
|
||||
|
||||
- **WHEN** there are changes with no tasks.md or zero tasks defined
|
||||
- **THEN** system shows them in a "Draft Changes" section
|
||||
- **AND** uses a distinct indicator (e.g., `○`) to show draft status
|
||||
|
||||
#### Scenario: Draft section ordering
|
||||
|
||||
- **WHEN** multiple draft changes exist
|
||||
- **THEN** system sorts them alphabetically by name
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Completed Changes Display
|
||||
|
||||
The dashboard SHALL list completed changes in a separate section, only showing changes with ALL tasks completed.
|
||||
|
||||
> **Fixes bug**: Previously, changes with `total === 0` were incorrectly shown as completed.
|
||||
|
||||
#### Scenario: Completed changes listing
|
||||
|
||||
- **WHEN** there are changes with `tasks.total > 0` AND `tasks.completed === tasks.total`
|
||||
- **THEN** system shows them with checkmark indicators in a dedicated section
|
||||
|
||||
#### Scenario: Mixed completion states
|
||||
|
||||
- **WHEN** some changes are complete and others active
|
||||
- **THEN** system separates them into appropriate sections
|
||||
|
||||
#### Scenario: Empty changes not completed
|
||||
|
||||
- **WHEN** a change has no tasks.md or zero tasks defined
|
||||
- **THEN** system does NOT show it in "Completed Changes" section
|
||||
- **AND** shows it in "Draft Changes" section instead
|
||||
|
||||
### Requirement: Summary Section
|
||||
|
||||
The dashboard SHALL display a summary section with key project metrics, including draft change count.
|
||||
|
||||
#### Scenario: Complete summary display
|
||||
|
||||
- **WHEN** dashboard is rendered with specs and changes
|
||||
- **THEN** system shows total number of specifications and requirements
|
||||
- **AND** shows number of draft changes
|
||||
- **AND** shows number of active changes in progress
|
||||
- **AND** shows number of completed changes
|
||||
- **AND** shows overall task progress percentage
|
||||
|
||||
#### Scenario: Empty project summary
|
||||
|
||||
- **WHEN** no specs or changes exist
|
||||
- **THEN** summary shows zero counts for all metrics
|
||||
@@ -1,25 +0,0 @@
|
||||
# Tasks: Unify Change State Model
|
||||
|
||||
## Phase 1: Fix Artifact Workflow Discovery
|
||||
|
||||
- [x] Update `validateChangeExists()` in `artifact-workflow.ts` to check directory existence instead of using `getActiveChangeIds()`
|
||||
- [x] Update error message to list all change directories (not just those with proposal.md)
|
||||
- [x] Add test for `openspec status --change <scaffolded-change>`
|
||||
- [x] Add test for `openspec next --change <scaffolded-change>`
|
||||
- [x] Add test for `openspec instructions proposal --change <scaffolded-change>`
|
||||
|
||||
## Phase 2: Fix View Command
|
||||
|
||||
- [x] Update `getChangesData()` in `view.ts` to return three categories: draft, active, completed
|
||||
- [x] Fix completion logic: `total === 0` → draft, not completed
|
||||
- [x] Add "Draft Changes" section to dashboard rendering
|
||||
- [x] Update summary to include draft count
|
||||
- [x] Add test for draft changes appearing correctly in view
|
||||
|
||||
## Phase 3: Cleanup and Validation
|
||||
|
||||
- [x] Clean up test changes (`test-workflow`, `test-workflow-2`)
|
||||
- [x] Run full test suite
|
||||
- [x] Manual test: `openspec new change foo && openspec status --change foo`
|
||||
- [x] Manual test: `openspec new change foo && openspec view` shows foo in Draft
|
||||
- [x] Validate with `openspec validate unify-change-state-model --strict`
|
||||
@@ -1,105 +0,0 @@
|
||||
# Delta for CLI Init
|
||||
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Configuration
|
||||
The init command SHALL generate slash command files for supported editors using shared templates.
|
||||
|
||||
#### Scenario: Generating slash commands for Antigravity
|
||||
- **WHEN** the user selects Antigravity during initialization
|
||||
- **THEN** create `.agent/workflows/openspec-proposal.md`, `.agent/workflows/openspec-apply.md`, and `.agent/workflows/openspec-archive.md`
|
||||
- **AND** ensure each file begins with YAML frontmatter that contains only a `description: <stage summary>` field followed by the shared OpenSpec workflow instructions wrapped in managed markers
|
||||
- **AND** populate the workflow body with the same proposal/apply/archive guidance used for other tools so Antigravity behaves like Windsurf while pointing to the `.agent/workflows/` directory
|
||||
|
||||
#### Scenario: Generating slash commands for Claude Code
|
||||
- **WHEN** the user selects Claude Code during initialization
|
||||
- **THEN** create `.claude/commands/openspec/proposal.md`, `.claude/commands/openspec/apply.md`, and `.claude/commands/openspec/archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for CodeBuddy Code
|
||||
- **WHEN** the user selects CodeBuddy Code during initialization
|
||||
- **THEN** create `.codebuddy/commands/openspec/proposal.md`, `.codebuddy/commands/openspec/apply.md`, and `.codebuddy/commands/openspec/archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Cline
|
||||
- **WHEN** the user selects Cline during initialization
|
||||
- **THEN** create `.clinerules/openspec-proposal.md`, `.clinerules/openspec-apply.md`, and `.clinerules/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include Cline-specific Markdown heading frontmatter
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Crush
|
||||
- **WHEN** the user selects Crush during initialization
|
||||
- **THEN** create `.crush/commands/openspec/proposal.md`, `.crush/commands/openspec/apply.md`, and `.crush/commands/openspec/archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include Crush-specific frontmatter with OpenSpec category and tags
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Cursor
|
||||
- **WHEN** the user selects Cursor during initialization
|
||||
- **THEN** create `.cursor/commands/openspec-proposal.md`, `.cursor/commands/openspec-apply.md`, and `.cursor/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Factory Droid
|
||||
- **WHEN** the user selects Factory Droid during initialization
|
||||
- **THEN** create `.factory/commands/openspec-proposal.md`, `.factory/commands/openspec-apply.md`, and `.factory/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates that include Factory-compatible YAML frontmatter for the `description` and `argument-hint` fields
|
||||
- **AND** include the `$ARGUMENTS` placeholder in the template body so droid receives any user-supplied input
|
||||
- **AND** wrap the generated content in OpenSpec managed markers so `openspec update` can safely refresh the commands
|
||||
|
||||
#### Scenario: Generating slash commands for OpenCode
|
||||
- **WHEN** the user selects OpenCode during initialization
|
||||
- **THEN** create `.opencode/commands/openspec-proposal.md`, `.opencode/commands/openspec-apply.md`, and `.opencode/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Windsurf
|
||||
- **WHEN** the user selects Windsurf during initialization
|
||||
- **THEN** create `.windsurf/workflows/openspec-proposal.md`, `.windsurf/workflows/openspec-apply.md`, and `.windsurf/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates (wrapped in OpenSpec markers) so workflow text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Kilo Code
|
||||
- **WHEN** the user selects Kilo Code during initialization
|
||||
- **THEN** create `.kilocode/workflows/openspec-proposal.md`, `.kilocode/workflows/openspec-apply.md`, and `.kilocode/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates (wrapped in OpenSpec markers) so workflow text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Codex
|
||||
- **WHEN** the user selects Codex during initialization
|
||||
- **THEN** create global prompt files at `~/.codex/prompts/openspec-proposal.md`, `~/.codex/prompts/openspec-apply.md`, and `~/.codex/prompts/openspec-archive.md` (or under `$CODEX_HOME/prompts` if set)
|
||||
- **AND** populate each file from shared templates that map the first numbered placeholder (`$1`) to the primary user input (e.g., change identifier or question text)
|
||||
- **AND** wrap the generated content in OpenSpec markers so `openspec update` can refresh the prompts without touching surrounding custom notes
|
||||
|
||||
#### Scenario: Generating slash commands for GitHub Copilot
|
||||
- **WHEN** the user selects GitHub Copilot during initialization
|
||||
- **THEN** create `.github/prompts/openspec-proposal.prompt.md`, `.github/prompts/openspec-apply.prompt.md`, and `.github/prompts/openspec-archive.prompt.md`
|
||||
- **AND** populate each file with YAML frontmatter containing a `description` field that summarizes the workflow stage
|
||||
- **AND** include `$ARGUMENTS` placeholder to capture user input
|
||||
- **AND** wrap the shared template body with OpenSpec markers so `openspec update` can refresh the content
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Gemini CLI
|
||||
- **WHEN** the user selects Gemini CLI during initialization
|
||||
- **THEN** create `.gemini/commands/openspec/proposal.toml`, `.gemini/commands/openspec/apply.toml`, and `.gemini/commands/openspec/archive.toml`
|
||||
- **AND** populate each file as TOML that sets a stage-specific `description = "<summary>"` and a multi-line `prompt = """` block with the shared OpenSpec template
|
||||
- **AND** wrap the OpenSpec managed markers (`<!-- OPENSPEC:START -->` / `<!-- OPENSPEC:END -->`) inside the `prompt` value so `openspec update` can safely refresh the body between markers without touching the TOML framing
|
||||
- **AND** ensure the slash-command copy matches the existing proposal/apply/archive templates used by other tools
|
||||
|
||||
#### Scenario: Generating slash commands for iFlow CLI
|
||||
- **WHEN** the user selects iFlow CLI during initialization
|
||||
- **THEN** create `.iflow/commands/openspec-proposal.md`, `.iflow/commands/openspec-apply.md`, and `.iflow/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include YAML frontmatter with `name`, `id`, `category`, and `description` fields for each command
|
||||
- **AND** wrap the generated content in OpenSpec managed markers so `openspec update` can safely refresh the commands
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for RooCode
|
||||
- **WHEN** the user selects RooCode during initialization
|
||||
- **THEN** create `.roo/commands/openspec-proposal.md`, `.roo/commands/openspec-apply.md`, and `.roo/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include simple Markdown headings (e.g., `# OpenSpec: Proposal`) without YAML frontmatter
|
||||
- **AND** wrap the generated content in OpenSpec managed markers where applicable so `openspec update` can safely refresh the commands
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
@@ -1,92 +0,0 @@
|
||||
# Delta for CLI Update
|
||||
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Updates
|
||||
The update command SHALL refresh existing slash command files for configured tools without creating new ones, and ensure the OpenCode archive command accepts change ID arguments.
|
||||
|
||||
#### Scenario: Updating slash commands for Antigravity
|
||||
- **WHEN** `.agent/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh the OpenSpec-managed portion of each file so the workflow copy matches other tools while preserving the existing single-field `description` frontmatter
|
||||
- **AND** skip creating any missing workflow files during update, mirroring the behavior for Windsurf and other IDEs
|
||||
|
||||
#### Scenario: Updating slash commands for Claude Code
|
||||
- **WHEN** `.claude/commands/openspec/` contains `proposal.md`, `apply.md`, and `archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for CodeBuddy Code
|
||||
- **WHEN** `.codebuddy/commands/openspec/` contains `proposal.md`, `apply.md`, and `archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Cline
|
||||
- **WHEN** `.clinerules/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** include Cline-specific Markdown heading frontmatter
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Crush
|
||||
- **WHEN** `.crush/commands/` contains `openspec/proposal.md`, `openspec/apply.md`, and `openspec/archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** include Crush-specific frontmatter with OpenSpec category and tags
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Cursor
|
||||
- **WHEN** `.cursor/commands/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Factory Droid
|
||||
- **WHEN** `.factory/commands/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using the shared Factory templates that include YAML frontmatter for the `description` and `argument-hint` fields
|
||||
- **AND** ensure the template body retains the `$ARGUMENTS` placeholder so user input keeps flowing into droid
|
||||
- **AND** update only the content inside the OpenSpec managed markers, leaving any unmanaged notes untouched
|
||||
- **AND** skip creating missing files during update
|
||||
|
||||
#### Scenario: Updating slash commands for OpenCode
|
||||
- **WHEN** `.opencode/command/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** ensure the archive command includes `$ARGUMENTS` placeholder in frontmatter for accepting change ID arguments
|
||||
|
||||
#### Scenario: Updating slash commands for Windsurf
|
||||
- **WHEN** `.windsurf/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
|
||||
#### Scenario: Updating slash commands for Kilo Code
|
||||
- **WHEN** `.kilocode/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
|
||||
#### Scenario: Updating slash commands for Codex
|
||||
- **GIVEN** the global Codex prompt directory contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **WHEN** a user runs `openspec update`
|
||||
- **THEN** refresh each file using the shared slash-command templates (including placeholder guidance)
|
||||
- **AND** preserve any unmanaged content outside the OpenSpec marker block
|
||||
- **AND** skip creation when a Codex prompt file is missing
|
||||
|
||||
#### Scenario: Updating slash commands for GitHub Copilot
|
||||
- **WHEN** `.github/prompts/` contains `openspec-proposal.prompt.md`, `openspec-apply.prompt.md`, and `openspec-archive.prompt.md`
|
||||
- **THEN** refresh each file using shared templates while preserving the YAML frontmatter
|
||||
- **AND** update only the OpenSpec-managed block between markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Gemini CLI
|
||||
- **WHEN** `.gemini/commands/openspec/` contains `proposal.toml`, `apply.toml`, and `archive.toml`
|
||||
- **THEN** refresh the body of each file using the shared proposal/apply/archive templates
|
||||
- **AND** replace only the content between `<!-- OPENSPEC:START -->` and `<!-- OPENSPEC:END -->` markers inside the `prompt = """` block so the TOML framing (`description`, `prompt`) stays intact
|
||||
- **AND** skip creating any missing `.toml` files during update; only pre-existing Gemini commands are refreshed
|
||||
|
||||
#### Scenario: Updating slash commands for iFlow CLI
|
||||
- **WHEN** `.iflow/commands/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** preserve the YAML frontmatter with `name`, `id`, `category`, and `description` fields
|
||||
- **AND** update only the OpenSpec-managed block between markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Missing slash command file
|
||||
- **WHEN** a tool lacks a slash command file
|
||||
- **THEN** do not create a new file during update
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
# Delta for CLI Init
|
||||
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Configuration
|
||||
The init command SHALL generate slash command files for supported editors using shared templates.
|
||||
|
||||
#### Scenario: Generating slash commands for Antigravity
|
||||
- **WHEN** the user selects Antigravity during initialization
|
||||
- **THEN** create `.agent/workflows/openspec-proposal.md`, `.agent/workflows/openspec-apply.md`, and `.agent/workflows/openspec-archive.md`
|
||||
- **AND** ensure each file begins with YAML frontmatter that contains only a `description: <stage summary>` field followed by the shared OpenSpec workflow instructions wrapped in managed markers
|
||||
- **AND** populate the workflow body with the same proposal/apply/archive guidance used for other tools so Antigravity behaves like Windsurf while pointing to the `.agent/workflows/` directory
|
||||
|
||||
#### Scenario: Generating slash commands for Claude Code
|
||||
- **WHEN** the user selects Claude Code during initialization
|
||||
- **THEN** create `.claude/commands/openspec/proposal.md`, `.claude/commands/openspec/apply.md`, and `.claude/commands/openspec/archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for CodeBuddy Code
|
||||
- **WHEN** the user selects CodeBuddy Code during initialization
|
||||
- **THEN** create `.codebuddy/commands/openspec/proposal.md`, `.codebuddy/commands/openspec/apply.md`, and `.codebuddy/commands/openspec/archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Cline
|
||||
- **WHEN** the user selects Cline during initialization
|
||||
- **THEN** create `.clinerules/workflows/openspec-proposal.md`, `.clinerules/workflows/openspec-apply.md`, and `.clinerules/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include Cline-specific Markdown heading frontmatter
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Crush
|
||||
- **WHEN** the user selects Crush during initialization
|
||||
- **THEN** create `.crush/commands/openspec/proposal.md`, `.crush/commands/openspec/apply.md`, and `.crush/commands/openspec/archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include Crush-specific frontmatter with OpenSpec category and tags
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Cursor
|
||||
- **WHEN** the user selects Cursor during initialization
|
||||
- **THEN** create `.cursor/commands/openspec-proposal.md`, `.cursor/commands/openspec-apply.md`, and `.cursor/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Factory Droid
|
||||
- **WHEN** the user selects Factory Droid during initialization
|
||||
- **THEN** create `.factory/commands/openspec-proposal.md`, `.factory/commands/openspec-apply.md`, and `.factory/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates that include Factory-compatible YAML frontmatter for the `description` and `argument-hint` fields
|
||||
- **AND** include the `$ARGUMENTS` placeholder in the template body so droid receives any user-supplied input
|
||||
- **AND** wrap the generated content in OpenSpec managed markers so `openspec update` can safely refresh the commands
|
||||
|
||||
#### Scenario: Generating slash commands for OpenCode
|
||||
- **WHEN** the user selects OpenCode during initialization
|
||||
- **THEN** create `.opencode/commands/openspec-proposal.md`, `.opencode/commands/openspec-apply.md`, and `.opencode/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Windsurf
|
||||
- **WHEN** the user selects Windsurf during initialization
|
||||
- **THEN** create `.windsurf/workflows/openspec-proposal.md`, `.windsurf/workflows/openspec-apply.md`, and `.windsurf/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates (wrapped in OpenSpec markers) so workflow text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Kilo Code
|
||||
- **WHEN** the user selects Kilo Code during initialization
|
||||
- **THEN** create `.kilocode/workflows/openspec-proposal.md`, `.kilocode/workflows/openspec-apply.md`, and `.kilocode/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates (wrapped in OpenSpec markers) so workflow text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Codex
|
||||
- **WHEN** the user selects Codex during initialization
|
||||
- **THEN** create global prompt files at `~/.codex/prompts/openspec-proposal.md`, `~/.codex/prompts/openspec-apply.md`, and `~/.codex/prompts/openspec-archive.md` (or under `$CODEX_HOME/prompts` if set)
|
||||
- **AND** populate each file from shared templates that map the first numbered placeholder (`$1`) to the primary user input (e.g., change identifier or question text)
|
||||
- **AND** wrap the generated content in OpenSpec markers so `openspec update` can refresh the prompts without touching surrounding custom notes
|
||||
|
||||
#### Scenario: Generating slash commands for GitHub Copilot
|
||||
- **WHEN** the user selects GitHub Copilot during initialization
|
||||
- **THEN** create `.github/prompts/openspec-proposal.prompt.md`, `.github/prompts/openspec-apply.prompt.md`, and `.github/prompts/openspec-archive.prompt.md`
|
||||
- **AND** populate each file with YAML frontmatter containing a `description` field that summarizes the workflow stage
|
||||
- **AND** include `$ARGUMENTS` placeholder to capture user input
|
||||
- **AND** wrap the shared template body with OpenSpec markers so `openspec update` can refresh the content
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Gemini CLI
|
||||
- **WHEN** the user selects Gemini CLI during initialization
|
||||
- **THEN** create `.gemini/commands/openspec/proposal.toml`, `.gemini/commands/openspec/apply.toml`, and `.gemini/commands/openspec/archive.toml`
|
||||
- **AND** populate each file as TOML that sets a stage-specific `description = "<summary>"` and a multi-line `prompt = """` block with the shared OpenSpec template
|
||||
- **AND** wrap the OpenSpec managed markers (`<!-- OPENSPEC:START -->` / `<!-- OPENSPEC:END -->`) inside the `prompt` value so `openspec update` can safely refresh the body between markers without touching the TOML framing
|
||||
- **AND** ensure the slash-command copy matches the existing proposal/apply/archive templates used by other tools
|
||||
|
||||
#### Scenario: Generating slash commands for iFlow CLI
|
||||
- **WHEN** the user selects iFlow CLI during initialization
|
||||
- **THEN** create `.iflow/commands/openspec-proposal.md`, `.iflow/commands/openspec-apply.md`, and `.iflow/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include YAML frontmatter with `name`, `id`, `category`, and `description` fields for each command
|
||||
- **AND** wrap the generated content in OpenSpec managed markers so `openspec update` can safely refresh the commands
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for RooCode
|
||||
- **WHEN** the user selects RooCode during initialization
|
||||
- **THEN** create `.roo/commands/openspec-proposal.md`, `.roo/commands/openspec-apply.md`, and `.roo/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include simple Markdown headings (e.g., `# OpenSpec: Proposal`) without YAML frontmatter
|
||||
- **AND** wrap the generated content in OpenSpec managed markers where applicable so `openspec update` can safely refresh the commands
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
# Delta for CLI Update
|
||||
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Updates
|
||||
The update command SHALL refresh existing slash command files for configured tools without creating new ones, and ensure the OpenCode archive command accepts change ID arguments.
|
||||
|
||||
#### Scenario: Updating slash commands for Antigravity
|
||||
- **WHEN** `.agent/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh the OpenSpec-managed portion of each file so the workflow copy matches other tools while preserving the existing single-field `description` frontmatter
|
||||
- **AND** skip creating any missing workflow files during update, mirroring the behavior for Windsurf and other IDEs
|
||||
|
||||
#### Scenario: Updating slash commands for Claude Code
|
||||
- **WHEN** `.claude/commands/openspec/` contains `proposal.md`, `apply.md`, and `archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for CodeBuddy Code
|
||||
- **WHEN** `.codebuddy/commands/openspec/` contains `proposal.md`, `apply.md`, and `archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Cline
|
||||
- **WHEN** `.clinerules/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** include Cline-specific Markdown heading frontmatter
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Crush
|
||||
- **WHEN** `.crush/commands/` contains `openspec/proposal.md`, `openspec/apply.md`, and `openspec/archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** include Crush-specific frontmatter with OpenSpec category and tags
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Cursor
|
||||
- **WHEN** `.cursor/commands/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Factory Droid
|
||||
- **WHEN** `.factory/commands/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using the shared Factory templates that include YAML frontmatter for the `description` and `argument-hint` fields
|
||||
- **AND** ensure the template body retains the `$ARGUMENTS` placeholder so user input keeps flowing into droid
|
||||
- **AND** update only the content inside the OpenSpec managed markers, leaving any unmanaged notes untouched
|
||||
- **AND** skip creating missing files during update
|
||||
|
||||
#### Scenario: Updating slash commands for OpenCode
|
||||
- **WHEN** `.opencode/command/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** ensure the archive command includes `$ARGUMENTS` placeholder in frontmatter for accepting change ID arguments
|
||||
|
||||
#### Scenario: Updating slash commands for Windsurf
|
||||
- **WHEN** `.windsurf/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
|
||||
#### Scenario: Updating slash commands for Kilo Code
|
||||
- **WHEN** `.kilocode/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
|
||||
#### Scenario: Updating slash commands for Codex
|
||||
- **GIVEN** the global Codex prompt directory contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **WHEN** a user runs `openspec update`
|
||||
- **THEN** refresh each file using the shared slash-command templates (including placeholder guidance)
|
||||
- **AND** preserve any unmanaged content outside the OpenSpec marker block
|
||||
- **AND** skip creation when a Codex prompt file is missing
|
||||
|
||||
#### Scenario: Updating slash commands for GitHub Copilot
|
||||
- **WHEN** `.github/prompts/` contains `openspec-proposal.prompt.md`, `openspec-apply.prompt.md`, and `openspec-archive.prompt.md`
|
||||
- **THEN** refresh each file using shared templates while preserving the YAML frontmatter
|
||||
- **AND** update only the OpenSpec-managed block between markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Gemini CLI
|
||||
- **WHEN** `.gemini/commands/openspec/` contains `proposal.toml`, `apply.toml`, and `archive.toml`
|
||||
- **THEN** refresh the body of each file using the shared proposal/apply/archive templates
|
||||
- **AND** replace only the content between `<!-- OPENSPEC:START -->` and `<!-- OPENSPEC:END -->` markers inside the `prompt = """` block so the TOML framing (`description`, `prompt`) stays intact
|
||||
- **AND** skip creating any missing `.toml` files during update; only pre-existing Gemini commands are refreshed
|
||||
|
||||
#### Scenario: Updating slash commands for iFlow CLI
|
||||
- **WHEN** `.iflow/commands/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** preserve the YAML frontmatter with `name`, `id`, `category`, and `description` fields
|
||||
- **AND** update only the OpenSpec-managed block between markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Missing slash command file
|
||||
- **WHEN** a tool lacks a slash command file
|
||||
- **THEN** do not create a new file during update
|
||||
@@ -1,26 +0,0 @@
|
||||
## Why
|
||||
|
||||
With per-change schema metadata in place (see `add-per-change-schema-metadata`), agents can now create changes with different workflow schemas. However, the agent skills are still hardcoded to `spec-driven` artifacts and don't offer schema selection to users.
|
||||
|
||||
## What Changes
|
||||
|
||||
**Scope: Experimental artifact workflow agent skills**
|
||||
|
||||
**Depends on:** `add-per-change-schema-metadata` (must be implemented first)
|
||||
|
||||
- Update `openspec-new-change` skill to prompt user for schema selection
|
||||
- Update `openspec-continue-change` skill to work with any schema's artifacts
|
||||
- Update `openspec-apply-change` skill to handle schema-specific task structures
|
||||
- Add schema descriptions to help users choose appropriate workflow
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Modified Capabilities
|
||||
- `cli-artifact-workflow`: Agent skills support dynamic schema selection
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected code**: `src/core/templates/skill-templates.ts`
|
||||
- **User experience**: Users can choose TDD, spec-driven, or future workflows when starting a change
|
||||
- **Agent behavior**: Skills read artifact list from schema rather than hardcoding
|
||||
- **Backward compatible**: Default remains `spec-driven` if user doesn't choose
|
||||
@@ -1,32 +0,0 @@
|
||||
## Prerequisites
|
||||
|
||||
- [x] 0.1 Implement `add-per-change-schema-metadata` change first
|
||||
|
||||
## 1. Schema Discovery
|
||||
|
||||
- [x] 1.1 Add CLI command or helper to list schemas with descriptions (for agent use)
|
||||
- [x] 1.2 Ensure `openspec templates --schema <name>` returns artifact list for any schema
|
||||
|
||||
## 2. Update New Change Skill
|
||||
|
||||
- [x] 2.1 Add schema selection prompt using AskUserQuestion tool
|
||||
- [x] 2.2 Present available schemas with descriptions (spec-driven, tdd, etc.)
|
||||
- [x] 2.3 Pass selected schema to `openspec new change --schema <name>`
|
||||
- [x] 2.4 Update output to show which schema/workflow was selected
|
||||
|
||||
## 3. Update Continue Change Skill
|
||||
|
||||
- [x] 3.1 Remove hardcoded artifact references (proposal, specs, design, tasks)
|
||||
- [x] 3.2 Read artifact list dynamically from `openspec status --json`
|
||||
- [x] 3.3 Adjust artifact creation guidelines to be schema-agnostic
|
||||
- [x] 3.4 Handle schema-specific artifact types (e.g., TDD's `tests` artifact)
|
||||
|
||||
## 4. Update Apply Change Skill
|
||||
|
||||
- [x] 4.1 Make task detection work with different schema structures
|
||||
- [x] 4.2 Adjust context file reading for schema-specific artifacts
|
||||
|
||||
## 5. Documentation
|
||||
|
||||
- [x] 5.1 Add schema descriptions to help text or skill instructions
|
||||
- [x] 5.2 Document when to use each schema (TDD for bug fixes, spec-driven for features, etc.)
|
||||
@@ -1,147 +0,0 @@
|
||||
## Context
|
||||
|
||||
The experimental artifact workflow supports multiple schemas (`spec-driven`, `tdd`), but schema selection must be passed on every command. This creates friction for agents and users.
|
||||
|
||||
We need a lightweight metadata file to persist the schema choice per change.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Store schema choice once at change creation
|
||||
- Auto-detect schema in experimental workflow commands
|
||||
- Maintain backward compatibility (no metadata = default)
|
||||
- Validate metadata with Zod schema
|
||||
|
||||
**Non-Goals:**
|
||||
- Migrate existing changes (they use default)
|
||||
- Extend to legacy commands
|
||||
- Store additional metadata beyond schema (keep minimal for now)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision: Zod Schema Design
|
||||
|
||||
The metadata file (`.openspec.yaml`) will be validated with this Zod schema:
|
||||
|
||||
```typescript
|
||||
// src/core/artifact-graph/types.ts (or new metadata.ts)
|
||||
|
||||
import { z } from 'zod';
|
||||
import { listSchemas } from './resolver.js';
|
||||
|
||||
/**
|
||||
* Schema for per-change metadata stored in .openspec.yaml
|
||||
*/
|
||||
export const ChangeMetadataSchema = z.object({
|
||||
// Required: which workflow schema this change uses
|
||||
schema: z.string().min(1, { message: 'schema is required' }).refine(
|
||||
(val) => listSchemas().includes(val),
|
||||
(val) => ({ message: `Unknown schema '${val}'. Available: ${listSchemas().join(', ')}` })
|
||||
),
|
||||
|
||||
// Optional: creation timestamp (ISO date string)
|
||||
created: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, {
|
||||
message: 'created must be YYYY-MM-DD format'
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
export type ChangeMetadata = z.infer<typeof ChangeMetadataSchema>;
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
- `schema` is required and validated against available schemas at parse time
|
||||
- `created` is optional, ISO date format for consistency
|
||||
- Minimal fields - can extend later without breaking existing files
|
||||
- Follows existing codebase pattern (see `ArtifactSchema`, `SchemaYamlSchema`)
|
||||
|
||||
### Decision: File Location and Format
|
||||
|
||||
**Location:** `openspec/changes/<name>/.openspec.yaml`
|
||||
|
||||
**Format:**
|
||||
```yaml
|
||||
schema: tdd
|
||||
created: 2025-01-05
|
||||
```
|
||||
|
||||
**Alternatives considered:**
|
||||
- `change.yaml` - less hidden, but clutters directory
|
||||
- Frontmatter in `proposal.md` - couples to proposal existence
|
||||
- `openspec.json` - YAML matches existing schema files
|
||||
|
||||
### Decision: Read/Write Functions
|
||||
|
||||
```typescript
|
||||
// src/utils/change-metadata.ts
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as yaml from 'yaml';
|
||||
import { ChangeMetadataSchema, type ChangeMetadata } from '../core/artifact-graph/types.js';
|
||||
|
||||
const METADATA_FILENAME = '.openspec.yaml';
|
||||
|
||||
export function writeChangeMetadata(
|
||||
changeDir: string,
|
||||
metadata: ChangeMetadata
|
||||
): void {
|
||||
// Validate before writing
|
||||
const validated = ChangeMetadataSchema.parse(metadata);
|
||||
const content = yaml.stringify(validated);
|
||||
fs.writeFileSync(path.join(changeDir, METADATA_FILENAME), content);
|
||||
}
|
||||
|
||||
export function readChangeMetadata(
|
||||
changeDir: string
|
||||
): ChangeMetadata | null {
|
||||
const metaPath = path.join(changeDir, METADATA_FILENAME);
|
||||
|
||||
if (!fs.existsSync(metaPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(metaPath, 'utf-8');
|
||||
const parsed = yaml.parse(content);
|
||||
|
||||
// Validate and return (throws ZodError if invalid)
|
||||
return ChangeMetadataSchema.parse(parsed);
|
||||
}
|
||||
```
|
||||
|
||||
### Decision: Schema Resolution Order
|
||||
|
||||
When determining which schema to use:
|
||||
|
||||
1. **Explicit `--schema` flag** (highest priority - user override)
|
||||
2. **`.openspec.yaml` metadata** (persisted choice)
|
||||
3. **Default `spec-driven`** (fallback)
|
||||
|
||||
```typescript
|
||||
function resolveSchemaForChange(
|
||||
changeDir: string,
|
||||
explicitSchema?: string
|
||||
): string {
|
||||
if (explicitSchema) return explicitSchema;
|
||||
|
||||
const metadata = readChangeMetadata(changeDir);
|
||||
if (metadata?.schema) return metadata.schema;
|
||||
|
||||
return 'spec-driven';
|
||||
}
|
||||
```
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Extra file per change** → Minimal overhead, hidden file
|
||||
- **YAML parsing dependency** → Already using `yaml` package for schema files
|
||||
- **Schema validation at read time** → Fail fast with clear error if corrupted
|
||||
|
||||
## Migration Plan
|
||||
|
||||
No migration needed:
|
||||
- Existing changes without `.openspec.yaml` continue to work (use default)
|
||||
- New changes created with `openspec new change --schema X` get metadata file
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should `openspec new change` prompt for schema interactively if not specified? (Leaning no - default is fine)
|
||||
@@ -1,29 +0,0 @@
|
||||
## Why
|
||||
|
||||
Currently, the schema (workflow type) must be passed via `--schema` flag on every experimental workflow command. This is repetitive and error-prone. Agents have no way to know which schema a change uses, so they default to `spec-driven` and cannot leverage alternative workflows like `tdd`.
|
||||
|
||||
## What Changes
|
||||
|
||||
**Scope: Experimental artifact workflow only** (`openspec new change`, `openspec status`, `openspec instructions`, `openspec templates`)
|
||||
|
||||
- Store schema choice in `.openspec.yaml` metadata file when creating a change via `openspec new change`
|
||||
- Auto-detect schema from metadata in experimental workflow commands
|
||||
- Make `--schema` flag optional (override only, metadata takes precedence)
|
||||
- Add `--schema` option to `openspec new change` command
|
||||
|
||||
**Not affected**: Legacy commands (`openspec validate`, `openspec archive`, `openspec list`, `openspec show`)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `change-metadata`: Reading/writing per-change metadata files
|
||||
|
||||
### Modified Capabilities
|
||||
- `cli-artifact-workflow`: Commands auto-detect schema from change metadata
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected code**: `src/utils/change-utils.ts`, `src/core/artifact-graph/instruction-loader.ts`, `src/commands/artifact-workflow.ts`
|
||||
- **Agent skills**: Can be simplified - no longer need to pass schema explicitly
|
||||
- **Backward compatible**: Changes without `.openspec.yaml` fall back to `spec-driven` default
|
||||
- **Isolation**: All changes contained within experimental workflow code; legacy commands untouched
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Change Metadata
|
||||
|
||||
The system SHALL store and validate per-change metadata in `.openspec.yaml` files using a Zod schema.
|
||||
|
||||
#### Scenario: Metadata file created with new change
|
||||
|
||||
- **WHEN** user runs `openspec new change add-feature --schema tdd`
|
||||
- **THEN** the system creates `.openspec.yaml` in the change directory
|
||||
- **AND** the file contains `schema: tdd` and `created: <YYYY-MM-DD>`
|
||||
|
||||
#### Scenario: Metadata validated on read
|
||||
|
||||
- **WHEN** the system reads `.openspec.yaml`
|
||||
- **AND** the `schema` field references an unknown schema
|
||||
- **THEN** the system displays a validation error listing available schemas
|
||||
|
||||
#### Scenario: Metadata schema validation
|
||||
|
||||
- **WHEN** `.openspec.yaml` contains invalid YAML or missing required fields
|
||||
- **THEN** the system displays a Zod validation error with details
|
||||
|
||||
#### Scenario: Missing metadata file
|
||||
|
||||
- **WHEN** a change directory has no `.openspec.yaml` file
|
||||
- **THEN** the system falls back to the default schema (`spec-driven`)
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: New Change Command
|
||||
|
||||
The system SHALL create new change directories with validation and optional schema metadata.
|
||||
|
||||
#### Scenario: Create valid change
|
||||
|
||||
- **WHEN** user runs `openspec new change add-feature`
|
||||
- **THEN** the system creates `openspec/changes/add-feature/` directory
|
||||
- **AND** creates `.openspec.yaml` with `schema: spec-driven` (default)
|
||||
|
||||
#### Scenario: Create change with schema
|
||||
|
||||
- **WHEN** user runs `openspec new change add-feature --schema tdd`
|
||||
- **THEN** the system creates `openspec/changes/add-feature/` directory
|
||||
- **AND** creates `.openspec.yaml` with `schema: tdd`
|
||||
|
||||
#### Scenario: Invalid schema on create
|
||||
|
||||
- **WHEN** user runs `openspec new change add-feature --schema unknown`
|
||||
- **THEN** the system displays an error listing available schemas
|
||||
- **AND** does not create the change directory
|
||||
|
||||
#### Scenario: Invalid change name
|
||||
|
||||
- **WHEN** user runs `openspec new change "Add Feature"` with invalid name
|
||||
- **THEN** the system displays validation error with guidance
|
||||
|
||||
#### Scenario: Duplicate change name
|
||||
|
||||
- **WHEN** user runs `openspec new change existing-change` for an existing change
|
||||
- **THEN** the system displays an error indicating the change already exists
|
||||
|
||||
#### Scenario: Create with description
|
||||
|
||||
- **WHEN** user runs `openspec new change add-feature --description "Add new feature"`
|
||||
- **THEN** the system creates the change directory with description in README.md
|
||||
|
||||
### Requirement: Schema Selection
|
||||
|
||||
The system SHALL support custom schema selection for workflow commands, with automatic detection from change metadata.
|
||||
|
||||
#### Scenario: Schema auto-detected from metadata
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id>` without `--schema`
|
||||
- **AND** the change has `.openspec.yaml` with `schema: tdd`
|
||||
- **THEN** the system uses the `tdd` schema
|
||||
|
||||
#### Scenario: Explicit schema overrides metadata
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id> --schema spec-driven`
|
||||
- **AND** the change has `.openspec.yaml` with `schema: tdd`
|
||||
- **THEN** the system uses `spec-driven` (explicit flag wins)
|
||||
|
||||
#### Scenario: Default schema fallback
|
||||
|
||||
- **WHEN** user runs workflow commands without `--schema`
|
||||
- **AND** the change has no `.openspec.yaml` file
|
||||
- **THEN** the system uses the "spec-driven" schema
|
||||
|
||||
#### Scenario: Custom schema via flag
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id> --schema tdd`
|
||||
- **THEN** the system uses the specified schema for artifact graph
|
||||
|
||||
#### Scenario: Unknown schema
|
||||
|
||||
- **WHEN** user specifies an unknown schema
|
||||
- **THEN** the system displays an error listing available schemas
|
||||
@@ -1,29 +0,0 @@
|
||||
## 1. Zod Schema and Types
|
||||
|
||||
- [x] 1.1 Add `ChangeMetadataSchema` Zod schema to `src/core/artifact-graph/types.ts`
|
||||
- [x] 1.2 Export `ChangeMetadata` type inferred from schema
|
||||
|
||||
## 2. Core Metadata Functions
|
||||
|
||||
- [x] 2.1 Create `src/utils/change-metadata.ts` with `writeChangeMetadata()` function
|
||||
- [x] 2.2 Add `readChangeMetadata()` function with Zod validation
|
||||
- [x] 2.3 Update `createChange()` to accept optional `schema` param and write metadata
|
||||
|
||||
## 3. Auto-Detection in Instruction Loader
|
||||
|
||||
- [x] 3.1 Modify `loadChangeContext()` to read schema from `.openspec.yaml`
|
||||
- [x] 3.2 Make `schemaName` parameter optional (fall back to metadata, then default)
|
||||
|
||||
## 4. CLI Updates
|
||||
|
||||
- [x] 4.1 Add `--schema <name>` option to `openspec new change` command
|
||||
- [x] 4.2 Verify existing commands (`status`, `instructions`) work with auto-detection
|
||||
|
||||
## 5. Tests
|
||||
|
||||
- [x] 5.1 Test `ChangeMetadataSchema` validates correctly (valid/invalid cases)
|
||||
- [x] 5.2 Test `writeChangeMetadata()` creates valid YAML
|
||||
- [x] 5.3 Test `readChangeMetadata()` parses and validates schema
|
||||
- [x] 5.4 Test `loadChangeContext()` auto-detects schema from metadata
|
||||
- [x] 5.5 Test fallback to default when no metadata exists
|
||||
- [x] 5.6 Test `--schema` flag overrides metadata
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-06
|
||||
@@ -1,77 +0,0 @@
|
||||
## Context
|
||||
|
||||
Currently, delta specs are only applied to main specs when running `openspec archive`. This bundles two concerns:
|
||||
1. Applying spec changes (delta → main)
|
||||
2. Archiving the change (move to archive folder)
|
||||
|
||||
Users want flexibility to sync specs earlier, especially when iterating. The archive command already contains the reconciliation logic in `buildUpdatedSpec()`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Decouple spec syncing from archiving
|
||||
- Provide `/opsx:sync` skill for agents to sync specs on demand
|
||||
- Keep operation idempotent (safe to run multiple times)
|
||||
|
||||
**Non-Goals:**
|
||||
- Tracking whether specs have been synced (no state)
|
||||
- Changing archive behavior (it will continue to apply specs)
|
||||
- Supporting partial application (all deltas sync together)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Reuse existing reconciliation logic
|
||||
|
||||
**Decision**: Extract `buildUpdatedSpec()` logic from `ArchiveCommand` into a shared module.
|
||||
|
||||
**Rationale**: The archive command already implements delta parsing and application. Rather than duplicate, we extract and reuse.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Duplicate logic in new command (rejected: maintenance burden)
|
||||
- Have sync call archive with flags (rejected: coupling)
|
||||
|
||||
### 2. No state tracking
|
||||
|
||||
**Decision**: Don't track whether specs have been synced. Each invocation reads delta and main specs, reconciles.
|
||||
|
||||
**Rationale**:
|
||||
- Idempotent operations don't need state
|
||||
- Avoids sync issues between flag and reality
|
||||
- Simpler implementation and mental model
|
||||
|
||||
**Alternatives considered**:
|
||||
- Track `specsSynced: true` in `.openspec.yaml` (rejected: unnecessary complexity)
|
||||
- Store snapshot of synced deltas (rejected: over-engineering)
|
||||
|
||||
### 3. Agent-driven approach (no CLI command)
|
||||
|
||||
**Decision**: The `/opsx:sync` skill is fully agent-driven - the agent reads delta specs and directly edits main specs.
|
||||
|
||||
**Rationale**:
|
||||
- Allows intelligent merging (add scenarios without copying entire requirements)
|
||||
- Delta represents *intent*, not wholesale replacement
|
||||
- More flexible and natural editing workflow
|
||||
- Archive still uses programmatic merge (for finalized changes)
|
||||
|
||||
### 4. Archive behavior unchanged
|
||||
|
||||
**Decision**: Archive continues to apply specs as part of its flow. If specs are already reconciled, the operation is a no-op.
|
||||
|
||||
**Rationale**: Backward compatibility. Users who don't use `/opsx:sync` get the same experience.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk] Multiple changes modify same spec**
|
||||
→ Last to sync wins. Same as today with archive. Users should coordinate or use sequential archives.
|
||||
|
||||
**[Risk] User syncs specs then continues editing deltas**
|
||||
→ Running `/opsx:sync` again reconciles. Idempotent design handles this.
|
||||
|
||||
**[Trade-off] No undo mechanism**
|
||||
→ Users can `git checkout` main specs if needed. Explicit undo command is out of scope.
|
||||
|
||||
## Implementation Approach
|
||||
|
||||
1. Extract spec application logic from `ArchiveCommand.buildUpdatedSpec()` into `src/core/specs-apply.ts`
|
||||
2. Add skill template for `/opsx:sync` in `skill-templates.ts`
|
||||
3. Register skill in managed skills
|
||||
@@ -1,32 +0,0 @@
|
||||
## Why
|
||||
|
||||
Spec application is currently bundled with archive - users must run `openspec archive` to apply delta specs to main specs. This couples two distinct concerns (applying specs vs. archiving the change) and forces users to wait until they're "done" to see main specs updated. Users want the flexibility to sync specs earlier in the workflow while iterating.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `/opsx:sync` skill that syncs delta specs to main specs as a standalone action
|
||||
- The operation is idempotent - safe to run multiple times, agent reconciles main specs to match deltas
|
||||
- Archive continues to work as today (applies specs if not already reconciled, then moves to archive)
|
||||
- No new state tracking - the agent reads delta and main specs, reconciles on each run
|
||||
- Agent-driven approach allows intelligent merging (partial updates, adding scenarios)
|
||||
|
||||
**Workflow becomes:**
|
||||
```
|
||||
/opsx:new → /opsx:continue → /opsx:apply → archive
|
||||
│
|
||||
└── /opsx:sync (optional, anytime)
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `specs-sync-skill`: Skill template for `/opsx:sync` command that reconciles main specs with delta specs
|
||||
|
||||
### Modified Capabilities
|
||||
- None (agent-driven, no CLI command needed)
|
||||
|
||||
## Impact
|
||||
|
||||
- **Skills**: New `openspec-sync-specs` skill in `skill-templates.ts`
|
||||
- **Archive**: No changes needed - already does reconciliation, will continue to work
|
||||
- **Agent workflow**: Users gain flexibility to sync specs before archive
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Specs Sync Skill
|
||||
The system SHALL provide an `/opsx:sync` skill that syncs delta specs from a change to the main specs.
|
||||
|
||||
#### Scenario: Sync delta specs to main specs
|
||||
- **WHEN** agent executes `/opsx:sync` with a change name
|
||||
- **THEN** the agent reads delta specs from `openspec/changes/<name>/specs/`
|
||||
- **AND** reads corresponding main specs from `openspec/specs/`
|
||||
- **AND** reconciles main specs to match what the deltas describe
|
||||
|
||||
#### Scenario: Idempotent operation
|
||||
- **WHEN** agent executes `/opsx:sync` multiple times on the same change
|
||||
- **THEN** the result is the same as running it once
|
||||
- **AND** no duplicate requirements are created
|
||||
|
||||
#### Scenario: Change selection prompt
|
||||
- **WHEN** agent executes `/opsx:sync` without specifying a change
|
||||
- **THEN** the agent prompts user to select from available changes
|
||||
- **AND** shows changes that have delta specs
|
||||
|
||||
### Requirement: Delta Reconciliation Logic
|
||||
The agent SHALL reconcile main specs with delta specs using the delta operation headers.
|
||||
|
||||
#### Scenario: ADDED requirements
|
||||
- **WHEN** delta contains `## ADDED Requirements` with a requirement
|
||||
- **AND** the requirement does not exist in main spec
|
||||
- **THEN** add the requirement to main spec
|
||||
|
||||
#### Scenario: ADDED requirement already exists
|
||||
- **WHEN** delta contains `## ADDED Requirements` with a requirement
|
||||
- **AND** a requirement with the same name already exists in main spec
|
||||
- **THEN** update the existing requirement to match the delta version
|
||||
|
||||
#### Scenario: MODIFIED requirements
|
||||
- **WHEN** delta contains `## MODIFIED Requirements` with a requirement
|
||||
- **AND** the requirement exists in main spec
|
||||
- **THEN** replace the requirement in main spec with the delta version
|
||||
|
||||
#### Scenario: REMOVED requirements
|
||||
- **WHEN** delta contains `## REMOVED Requirements` with a requirement name
|
||||
- **AND** the requirement exists in main spec
|
||||
- **THEN** remove the requirement from main spec
|
||||
|
||||
#### Scenario: RENAMED requirements
|
||||
- **WHEN** delta contains `## RENAMED Requirements` with FROM:/TO: format
|
||||
- **AND** the FROM requirement exists in main spec
|
||||
- **THEN** rename the requirement to the TO name
|
||||
|
||||
#### Scenario: New capability spec
|
||||
- **WHEN** delta spec exists for a capability not in main specs
|
||||
- **THEN** create new main spec file at `openspec/specs/<capability>/spec.md`
|
||||
|
||||
### Requirement: Skill Output
|
||||
The skill SHALL provide clear feedback on what was synced.
|
||||
|
||||
#### Scenario: Show synced changes
|
||||
- **WHEN** reconciliation completes successfully
|
||||
- **THEN** display summary of changes per capability:
|
||||
- Number of requirements added
|
||||
- Number of requirements modified
|
||||
- Number of requirements removed
|
||||
- Number of requirements renamed
|
||||
|
||||
#### Scenario: No changes needed
|
||||
- **WHEN** main specs already match delta specs
|
||||
- **THEN** display "Specs already in sync - no changes needed"
|
||||
@@ -1,40 +0,0 @@
|
||||
## Tasks
|
||||
|
||||
### Core Implementation
|
||||
|
||||
- [x] Extract spec application logic from `ArchiveCommand` into `src/core/specs-apply.ts`
|
||||
- Move `buildUpdatedSpec()`, `findSpecUpdates()`, `writeUpdatedSpec()` to shared module
|
||||
- Keep `ArchiveCommand` importing from the new module
|
||||
- Ensure all validation logic is preserved
|
||||
|
||||
### Skill Template
|
||||
|
||||
- [x] Add `getSyncSpecsSkillTemplate()` function in `src/core/templates/skill-templates.ts`
|
||||
- Skill name: `openspec-sync-specs`
|
||||
- Description: Sync delta specs to main specs
|
||||
- **Agent-driven**: Instructions for agent to read deltas and edit main specs directly
|
||||
|
||||
- [x] Add `/opsx:sync` slash command template in `skill-templates.ts`
|
||||
- Mirror the skill template for slash command format
|
||||
- **Agent-driven**: No CLI command, agent does the merge
|
||||
|
||||
### Registration
|
||||
|
||||
- [x] Register skill in managed skills (via `artifact-experimental-setup`)
|
||||
- Add to skill list with appropriate metadata
|
||||
- Ensure it appears in setup output
|
||||
|
||||
### Design Decision
|
||||
|
||||
**Why agent-driven instead of CLI-driven?**
|
||||
|
||||
The programmatic merge operates at requirement-level granularity:
|
||||
- MODIFIED requires copying ALL scenarios, not just the changed ones
|
||||
- If agent forgets a scenario, it gets deleted
|
||||
- Delta specs become bloated with copied content
|
||||
|
||||
Agent-driven approach:
|
||||
- Agent can apply partial updates (add a scenario without copying others)
|
||||
- Delta represents *intent*, not wholesale replacement
|
||||
- More flexible and natural editing workflow
|
||||
- Archive still uses programmatic merge (for finalized changes)
|
||||
@@ -1,138 +0,0 @@
|
||||
## Why
|
||||
|
||||
The `generateApplyInstructions` function is hardcoded to check for `spec-driven` artifacts (`proposal.md`, `specs/`, `design.md`, `tasks.md`). If a user selects a different schema like `tdd`, the apply instructions are meaningless - they check for files that don't exist in that schema.
|
||||
|
||||
This blocks the experimental workflow from supporting multiple schemas properly.
|
||||
|
||||
## What Changes
|
||||
|
||||
**Scope: Experimental artifact workflow** (`openspec instructions apply`)
|
||||
|
||||
**Depends on:** `add-per-change-schema-metadata` (to know which schema a change uses)
|
||||
|
||||
- Make `generateApplyInstructions` read artifact definitions from the schema
|
||||
- Dynamically determine which artifacts exist based on schema
|
||||
- Define when a change becomes "implementable" (see Design Decision below)
|
||||
- Generate schema-appropriate context files and instructions
|
||||
|
||||
## Design Decision: When is a change implementable?
|
||||
|
||||
This is the key question. Different approaches:
|
||||
|
||||
### Option A: Explicit `apply` artifact in schema
|
||||
|
||||
Add a field to mark which artifact is the "implementation gate":
|
||||
|
||||
```yaml
|
||||
artifacts:
|
||||
- id: tasks
|
||||
generates: tasks.md
|
||||
apply: true # ← This artifact triggers apply mode
|
||||
```
|
||||
|
||||
**Pros:** Explicit, flexible
|
||||
**Cons:** Another field to maintain, what if multiple artifacts are `apply: true`?
|
||||
|
||||
### Option B: Leaf artifacts are implementable
|
||||
|
||||
The artifact(s) with no dependents (nothing depends on them) are the apply target.
|
||||
|
||||
- `spec-driven`: `tasks` is a leaf → apply = execute tasks
|
||||
- `tdd`: `docs` is a leaf → but that doesn't make sense for TDD...
|
||||
|
||||
**Pros:** No extra schema field, derived from graph
|
||||
**Cons:** Doesn't match TDD semantics (implementation is the action, not docs)
|
||||
|
||||
### Option C: Schema-level `apply_phase` definition
|
||||
|
||||
Add a top-level field to the schema:
|
||||
|
||||
```yaml
|
||||
name: spec-driven
|
||||
apply_phase:
|
||||
requires: [tasks] # Must exist before apply
|
||||
tracks: tasks.md # File with checkboxes to track
|
||||
instruction: "Work through tasks, mark complete as you go"
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: tdd
|
||||
apply_phase:
|
||||
requires: [tests] # Must have tests before implementing
|
||||
tracks: null # No checkbox tracking - just make tests pass
|
||||
instruction: "Run tests, implement until green, refactor"
|
||||
```
|
||||
|
||||
**Pros:** Full flexibility, schema controls its own apply semantics
|
||||
**Cons:** More complex schema format
|
||||
|
||||
### Option D: Convention-based (artifact ID matching)
|
||||
|
||||
If artifact ID is `tasks` or `implementation`, it's the apply target.
|
||||
|
||||
**Pros:** Simple, no schema changes
|
||||
**Cons:** Brittle, doesn't work for custom schemas
|
||||
|
||||
### Option E: All artifacts complete → apply available
|
||||
|
||||
Apply becomes available when ALL schema artifacts exist. Implementation is whatever the user does after planning.
|
||||
|
||||
**Pros:** Simple, no schema changes
|
||||
**Cons:** Doesn't guide what "apply" means for different workflows
|
||||
|
||||
---
|
||||
|
||||
## Decision: Add `apply` block to schema.yaml
|
||||
|
||||
Add a top-level `apply` field to schema definitions:
|
||||
|
||||
```yaml
|
||||
name: spec-driven
|
||||
version: 1
|
||||
description: Default OpenSpec workflow
|
||||
|
||||
artifacts:
|
||||
# ... existing artifacts ...
|
||||
|
||||
apply:
|
||||
requires: [tasks] # Artifacts that must exist before apply
|
||||
tracks: tasks.md # File with checkboxes for progress (optional)
|
||||
instruction: | # Guidance shown to agent
|
||||
Read context files, work through pending tasks, mark complete as you go.
|
||||
Pause if you hit blockers or need clarification.
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: tdd
|
||||
version: 1
|
||||
description: Test-driven development workflow
|
||||
|
||||
artifacts:
|
||||
# ... existing artifacts ...
|
||||
|
||||
apply:
|
||||
requires: [tests] # Must have tests before implementing
|
||||
tracks: null # No checkbox tracking
|
||||
instruction: |
|
||||
Run tests to see failures. Implement minimal code to pass each test.
|
||||
Refactor while keeping tests green.
|
||||
```
|
||||
|
||||
**Key properties:**
|
||||
- `requires`: Array of artifact IDs that must exist before apply is available
|
||||
- `tracks`: Path to file with checkboxes (relative to change dir), or `null` if no tracking
|
||||
- `instruction`: Custom guidance for the apply phase
|
||||
|
||||
**Fallback behavior:** Schemas without `apply` block default to "all artifacts must exist"
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Modified Capabilities
|
||||
- `cli-artifact-workflow`: Apply instructions become schema-aware
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected code**: `src/commands/artifact-workflow.ts` (generateApplyInstructions)
|
||||
- **Schema format**: May need new `apply_phase` field
|
||||
- **Existing schemas**: Need to add apply_phase to `spec-driven` and `tdd`
|
||||
- **Backward compatible**: Schemas without apply_phase can use default behavior
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Schema Apply Block
|
||||
|
||||
The system SHALL support an `apply` block in schema definitions that controls when and how implementation begins.
|
||||
|
||||
#### Scenario: Schema with apply block
|
||||
|
||||
- **WHEN** a schema defines an `apply` block
|
||||
- **THEN** the system uses `apply.requires` to determine which artifacts must exist before apply
|
||||
- **AND** uses `apply.tracks` to identify the file for progress tracking (or null if none)
|
||||
- **AND** uses `apply.instruction` for guidance shown to the agent
|
||||
|
||||
#### Scenario: Schema without apply block
|
||||
|
||||
- **WHEN** a schema has no `apply` block
|
||||
- **THEN** the system requires all artifacts to exist before apply is available
|
||||
- **AND** uses default instruction: "All artifacts complete. Proceed with implementation."
|
||||
|
||||
### Requirement: Apply Instructions Command
|
||||
|
||||
The system SHALL generate schema-aware apply instructions via `openspec instructions apply`.
|
||||
|
||||
#### Scenario: Generate apply instructions
|
||||
|
||||
- **WHEN** user runs `openspec instructions apply --change <id>`
|
||||
- **AND** all required artifacts (per schema's `apply.requires`) exist
|
||||
- **THEN** the system outputs:
|
||||
- Context files from all existing artifacts
|
||||
- Schema-specific instruction text
|
||||
- Progress tracking file path (if `apply.tracks` is set)
|
||||
|
||||
#### Scenario: Apply blocked by missing artifacts
|
||||
|
||||
- **WHEN** user runs `openspec instructions apply --change <id>`
|
||||
- **AND** required artifacts are missing
|
||||
- **THEN** the system indicates apply is blocked
|
||||
- **AND** lists which artifacts must be created first
|
||||
|
||||
#### Scenario: Apply instructions JSON output
|
||||
|
||||
- **WHEN** user runs `openspec instructions apply --change <id> --json`
|
||||
- **THEN** the system outputs JSON with:
|
||||
- `contextFiles`: array of paths to existing artifacts
|
||||
- `instruction`: the apply instruction text
|
||||
- `tracks`: path to progress file or null
|
||||
- `applyRequires`: list of required artifact IDs
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Status Command
|
||||
|
||||
The system SHALL display artifact completion status for a change, including apply readiness.
|
||||
|
||||
#### Scenario: Status JSON includes apply requirements
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id> --json`
|
||||
- **THEN** the system outputs JSON with:
|
||||
- `changeName`, `schemaName`, `isComplete`, `artifacts` array
|
||||
- `applyRequires`: array of artifact IDs needed for apply phase
|
||||
@@ -1,35 +0,0 @@
|
||||
## Prerequisites
|
||||
|
||||
- [x] 0.1 Implement `add-per-change-schema-metadata` first (to auto-detect schema)
|
||||
|
||||
## 1. Schema Format
|
||||
|
||||
- [x] 1.1 Add `ApplyPhaseSchema` Zod schema to `src/core/artifact-graph/types.ts`
|
||||
- [x] 1.2 Update `SchemaYamlSchema` to include optional `apply` field
|
||||
- [x] 1.3 Export `ApplyPhase` type
|
||||
|
||||
## 2. Update Existing Schemas
|
||||
|
||||
- [x] 2.1 Add `apply` block to `schemas/spec-driven/schema.yaml`
|
||||
- [x] 2.2 Add `apply` block to `schemas/tdd/schema.yaml`
|
||||
|
||||
## 3. Refactor generateApplyInstructions
|
||||
|
||||
- [x] 3.1 Load schema via `resolveSchema(schemaName)`
|
||||
- [x] 3.2 Read `apply.requires` to determine required artifacts
|
||||
- [x] 3.3 Check artifact existence dynamically (not hardcoded paths)
|
||||
- [x] 3.4 Use `apply.tracks` for progress tracking (or skip if null)
|
||||
- [x] 3.5 Use `apply.instruction` for the instruction text
|
||||
- [x] 3.6 Build `contextFiles` from all existing artifacts in schema
|
||||
|
||||
## 4. Handle Fallback
|
||||
|
||||
- [x] 4.1 If schema has no `apply` block, require all artifacts to exist
|
||||
- [x] 4.2 Default instruction: "All artifacts complete. Proceed with implementation."
|
||||
|
||||
## 5. Tests
|
||||
|
||||
- [x] 5.1 Test apply instructions with spec-driven schema
|
||||
- [x] 5.2 Test apply instructions with tdd schema
|
||||
- [x] 5.3 Test fallback when schema has no apply block
|
||||
- [x] 5.4 Test blocked state when required artifacts missing
|
||||
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-07
|
||||
@@ -1,84 +0,0 @@
|
||||
## Context
|
||||
|
||||
The experimental workflow (OPSX) provides a complete lifecycle for creating changes:
|
||||
- `/opsx:new` - Scaffold a new change with schema
|
||||
- `/opsx:continue` - Create next artifact
|
||||
- `/opsx:ff` - Fast-forward all artifacts
|
||||
- `/opsx:apply` - Implement tasks
|
||||
- `/opsx:sync` - Sync delta specs to main
|
||||
|
||||
The missing piece is archiving. The existing `openspec archive` command works but:
|
||||
1. Applies specs programmatically (not agent-driven)
|
||||
2. Doesn't use the artifact graph for completion checking
|
||||
3. Doesn't integrate with the OPSX workflow philosophy
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Add `/opsx:archive` skill to complete the OPSX workflow lifecycle
|
||||
- Use artifact graph for schema-aware completion checking
|
||||
- Integrate with `/opsx:sync` for agent-driven spec syncing
|
||||
- Preserve `.openspec.yaml` schema metadata in archive
|
||||
|
||||
**Non-Goals:**
|
||||
- Replacing the existing `openspec archive` CLI command
|
||||
- Changing how specs are applied in the CLI command
|
||||
- Modifying the artifact graph or schema system
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: Skill-only implementation (no new CLI command)
|
||||
|
||||
The `/opsx:archive` will be a slash command/skill only, not a new CLI command.
|
||||
|
||||
**Rationale**: The existing `openspec archive` CLI command already handles the core archive functionality (moving to archive folder, date prefixing). The OPSX version just needs different pre-archive checks and optional sync prompting, which are agent behaviors better suited to a skill.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Adding flags to `openspec archive` (e.g., `--experimental`) - Rejected: adds complexity to CLI, harder to maintain two code paths
|
||||
- New CLI command `openspec archive-experimental` - Rejected: unnecessary duplication, agent skills are the OPSX pattern
|
||||
|
||||
### Decision 2: Prompt for sync before archive
|
||||
|
||||
The skill will check for unsynced delta specs and prompt the user before archiving.
|
||||
|
||||
**Rationale**: The OPSX philosophy is agent-driven intelligent merging via `/opsx:sync`. Rather than programmatically applying specs like the regular archive command, we prompt the user to sync first if needed. This maintains workflow flexibility (user can decline and just archive).
|
||||
|
||||
**Flow**:
|
||||
1. Check if `specs/` directory exists in the change
|
||||
2. If yes, ask: "This change has delta specs. Would you like to sync them to main specs before archiving?"
|
||||
3. If user says yes, execute `/opsx:sync` logic
|
||||
4. Proceed with archive regardless of answer
|
||||
|
||||
### Decision 3: Use artifact graph for completion checking
|
||||
|
||||
The skill will use `openspec status --change "<name>" --json` to check artifact completion instead of just validating proposal.md and specs.
|
||||
|
||||
**Rationale**: The experimental workflow is schema-aware. Different schemas have different required artifacts. The artifact graph knows which artifacts are complete/incomplete for the current schema.
|
||||
|
||||
**Behavior**:
|
||||
- Show warning if any artifacts are not `done`
|
||||
- Don't block archive (user may have valid reasons to archive early)
|
||||
- List incomplete artifacts so user can make informed decision
|
||||
|
||||
### Decision 4: Reuse tasks.md completion check from regular archive
|
||||
|
||||
The skill will parse tasks.md and warn about incomplete tasks, same as regular archive.
|
||||
|
||||
**Rationale**: Task completion checking is valuable regardless of workflow. The logic is simple (count `- [ ]` vs `- [x]`) and doesn't need special OPSX handling.
|
||||
|
||||
### Decision 5: Move change to archive/ with date prefix
|
||||
|
||||
Same archive behavior as regular command: move to `openspec/changes/archive/YYYY-MM-DD-<name>/`.
|
||||
|
||||
**Rationale**: Consistency with existing archive convention. The `.openspec.yaml` file moves with the change, preserving schema metadata.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**Risk**: Users confused about when to use `/opsx:archive` vs `openspec archive`
|
||||
→ **Mitigation**: Documentation should clarify: use `/opsx:archive` if you've been using the OPSX workflow, use `openspec archive` otherwise. Both produce the same archived result.
|
||||
|
||||
**Risk**: Incomplete sync if user declines and has delta specs
|
||||
→ **Mitigation**: The prompt is informational; user has full control. They may want to archive without syncing (e.g., abandoned change). Log a note in output.
|
||||
|
||||
**Trade-off**: No programmatic spec application in OPSX archive
|
||||
→ **Accepted**: This is intentional. OPSX philosophy is agent-driven merging. If user wants programmatic application, use `openspec archive` instead.
|
||||
@@ -1,28 +0,0 @@
|
||||
## Why
|
||||
|
||||
The experimental workflow (OPSX) provides a schema-driven, artifact-by-artifact approach to creating changes with `/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:apply`, and `/opsx:sync`. However, there's no corresponding archive command to finalize and archive completed changes. Users must currently fall back to the regular `openspec archive` command, which doesn't integrate with the OPSX philosophy of agent-driven spec syncing and schema-aware artifact tracking.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `/opsx:archive` slash command for archiving changes in the experimental workflow
|
||||
- Use artifact graph to check completion status (schema-aware) instead of just validating proposal + specs
|
||||
- Prompt for `/opsx:sync` before archiving instead of programmatically applying specs
|
||||
- Preserve `.openspec.yaml` schema metadata when moving to archive
|
||||
- Integrate with existing OPSX commands for a cohesive workflow
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `opsx-archive-skill`: Slash command and skill for archiving completed changes in the experimental workflow. Checks artifact completion via artifact graph, verifies task completion, optionally syncs specs via `/opsx:sync`, and moves the change to `archive/YYYY-MM-DD-<name>/`.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
(none - this is a new skill that doesn't modify existing specs)
|
||||
|
||||
## Impact
|
||||
|
||||
- New file: `.claude/commands/opsx/archive.md`
|
||||
- New skill definition (generated via `openspec artifact-experimental-setup`)
|
||||
- No changes to existing archive command or other OPSX commands
|
||||
- Completes the OPSX command suite for full lifecycle management
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: OPSX Archive Skill
|
||||
|
||||
The system SHALL provide an `/opsx:archive` skill that archives completed changes in the experimental workflow.
|
||||
|
||||
#### Scenario: Archive a change with all artifacts complete
|
||||
|
||||
- **WHEN** agent executes `/opsx:archive` with a change name
|
||||
- **AND** all artifacts in the schema are complete
|
||||
- **AND** all tasks are complete
|
||||
- **THEN** the agent moves the change to `openspec/changes/archive/YYYY-MM-DD-<name>/`
|
||||
- **AND** displays success message with archived location
|
||||
|
||||
#### Scenario: Change selection prompt
|
||||
|
||||
- **WHEN** agent executes `/opsx:archive` without specifying a change
|
||||
- **THEN** the agent prompts user to select from available changes
|
||||
- **AND** shows only active changes (excludes archive/)
|
||||
|
||||
### Requirement: Artifact Completion Check
|
||||
|
||||
The skill SHALL check artifact completion status using the artifact graph before archiving.
|
||||
|
||||
#### Scenario: Incomplete artifacts warning
|
||||
|
||||
- **WHEN** agent checks artifact status
|
||||
- **AND** one or more artifacts have status other than `done`
|
||||
- **THEN** display warning listing incomplete artifacts
|
||||
- **AND** prompt user for confirmation to continue
|
||||
- **AND** proceed if user confirms
|
||||
|
||||
#### Scenario: All artifacts complete
|
||||
|
||||
- **WHEN** agent checks artifact status
|
||||
- **AND** all artifacts have status `done`
|
||||
- **THEN** proceed without warning
|
||||
|
||||
### Requirement: Task Completion Check
|
||||
|
||||
The skill SHALL check task completion status from tasks.md before archiving.
|
||||
|
||||
#### Scenario: Incomplete tasks found
|
||||
|
||||
- **WHEN** agent reads tasks.md
|
||||
- **AND** incomplete tasks are found (marked with `- [ ]`)
|
||||
- **THEN** display warning showing count of incomplete tasks
|
||||
- **AND** prompt user for confirmation to continue
|
||||
- **AND** proceed if user confirms
|
||||
|
||||
#### Scenario: All tasks complete
|
||||
|
||||
- **WHEN** agent reads tasks.md
|
||||
- **AND** all tasks are complete (marked with `- [x]`)
|
||||
- **THEN** proceed without task-related warning
|
||||
|
||||
#### Scenario: No tasks file
|
||||
|
||||
- **WHEN** tasks.md does not exist
|
||||
- **THEN** proceed without task-related warning
|
||||
|
||||
### Requirement: Spec Sync Prompt
|
||||
|
||||
The skill SHALL prompt to sync delta specs before archiving if specs exist.
|
||||
|
||||
#### Scenario: Delta specs exist
|
||||
|
||||
- **WHEN** agent checks for delta specs
|
||||
- **AND** `specs/` directory exists in the change with spec files
|
||||
- **THEN** prompt user: "This change has delta specs. Would you like to sync them to main specs before archiving?"
|
||||
- **AND** if user confirms, execute `/opsx:sync` logic
|
||||
- **AND** proceed with archive regardless of sync choice
|
||||
|
||||
#### Scenario: No delta specs
|
||||
|
||||
- **WHEN** agent checks for delta specs
|
||||
- **AND** no `specs/` directory or no spec files exist
|
||||
- **THEN** proceed without sync prompt
|
||||
|
||||
### Requirement: Archive Process
|
||||
|
||||
The skill SHALL move the change to the archive folder with date prefix.
|
||||
|
||||
#### Scenario: Successful archive
|
||||
|
||||
- **WHEN** archiving a change
|
||||
- **THEN** create `archive/` directory if it doesn't exist
|
||||
- **AND** generate target name as `YYYY-MM-DD-<change-name>` using current date
|
||||
- **AND** move entire change directory to archive location
|
||||
- **AND** preserve `.openspec.yaml` file in archived change
|
||||
|
||||
#### Scenario: Archive already exists
|
||||
|
||||
- **WHEN** target archive directory already exists
|
||||
- **THEN** fail with error message
|
||||
- **AND** suggest renaming existing archive or using different date
|
||||
|
||||
### Requirement: Skill Output
|
||||
|
||||
The skill SHALL provide clear feedback about the archive operation.
|
||||
|
||||
#### Scenario: Archive complete with sync
|
||||
|
||||
- **WHEN** archive completes after syncing specs
|
||||
- **THEN** display summary:
|
||||
- Specs synced (from `/opsx:sync` output)
|
||||
- Change archived to location
|
||||
- Schema that was used
|
||||
|
||||
#### Scenario: Archive complete without sync
|
||||
|
||||
- **WHEN** archive completes without syncing specs
|
||||
- **THEN** display summary:
|
||||
- Note that specs were not synced (if applicable)
|
||||
- Change archived to location
|
||||
- Schema that was used
|
||||
|
||||
#### Scenario: Archive complete with warnings
|
||||
|
||||
- **WHEN** archive completes with incomplete artifacts or tasks
|
||||
- **THEN** include note about what was incomplete
|
||||
- **AND** suggest reviewing if archive was intentional
|
||||
@@ -1,23 +0,0 @@
|
||||
## 1. Create Slash Command
|
||||
|
||||
- [x] 1.1 Create `.claude/commands/opsx/archive.md` with skill definition
|
||||
- [x] 1.2 Add YAML frontmatter (name, description, category, tags)
|
||||
- [x] 1.3 Implement change selection logic (prompt if not provided)
|
||||
- [x] 1.4 Implement artifact completion check using `openspec status --json`
|
||||
- [x] 1.5 Implement task completion check (parse tasks.md for `- [ ]`)
|
||||
- [x] 1.6 Implement spec sync prompt (check for specs/ directory, offer `/opsx:sync`)
|
||||
- [x] 1.7 Implement archive process (move to archive/YYYY-MM-DD-<name>/)
|
||||
- [x] 1.8 Add output formatting for success/warning cases
|
||||
|
||||
## 2. Regenerate Skills
|
||||
|
||||
- [x] 2.1 Run `openspec artifact-experimental-setup` to regenerate skills
|
||||
- [x] 2.2 Verify skill appears in `.claude/skills/` directory
|
||||
|
||||
## 3. Testing
|
||||
|
||||
- [x] 3.1 Test `/opsx:archive` with a complete change (all artifacts, all tasks done)
|
||||
- [x] 3.2 Test `/opsx:archive` with incomplete artifacts (verify warning shown)
|
||||
- [x] 3.3 Test `/opsx:archive` with incomplete tasks (verify warning shown)
|
||||
- [x] 3.4 Test `/opsx:archive` with delta specs (verify sync prompt shown)
|
||||
- [x] 3.5 Test `/opsx:archive` without change name (verify selection prompt)
|
||||
+1
-1
@@ -8,6 +8,6 @@ The Cline implementation was architecturally incorrect. According to Cline's off
|
||||
- **BREAKING**: Existing Cline users will need to re-run `openspec init` to get the corrected workflow files
|
||||
|
||||
## Impact
|
||||
- Affected specs: cli-init, cli-update (corrected Cline workflow paths)
|
||||
- Affected specs: cli-init (corrected Cline workflow paths)
|
||||
- Affected code: `src/core/configurators/slash/cline.ts`, test files, README.md
|
||||
- Modified files: `.clinerules/workflows/openspec-*.md` (moved from `.clinerules/openspec-*.md`)
|
||||
@@ -0,0 +1,11 @@
|
||||
# Delta for CLI Init
|
||||
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Configuration
|
||||
|
||||
#### Scenario: Generating slash commands for Cline
|
||||
- **WHEN** the user selects Cline during initialization
|
||||
- **THEN** create `.clinerules/workflows/openspec-proposal.md`, `.clinerules/workflows/openspec-apply.md`, and `.clinerules/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include Cline-specific Markdown heading frontmatter
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
@@ -0,0 +1,12 @@
|
||||
## Why
|
||||
Validation currently errors on changes without spec deltas, even when the change is intentionally proposal-only or tooling-only. This creates false negatives and noisy CI.
|
||||
|
||||
## What Changes
|
||||
- Make change validation scope-aware: validate only artifacts that exist.
|
||||
- Only error on "No deltas found" if spec delta files exist but parse to zero deltas.
|
||||
- Keep archive stricter: if specs exist but parse to zero deltas, fail; allow `--skip-specs` for tooling-only changes.
|
||||
|
||||
## Impact
|
||||
- Affected specs: cli-validate
|
||||
- Affected code: `src/commands/validate.ts`, `src/core/validation/validator.ts`
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
## ADDED Requirements
|
||||
### Requirement: Scope-Aware Change Validation
|
||||
The validator SHALL validate only artifacts that exist for a change, avoiding errors for proposal-only or tooling-only changes.
|
||||
|
||||
#### Scenario: Proposal-only change
|
||||
- **WHEN** a change contains `proposal.md` but has no `specs/` directory or contains no `*/spec.md` files
|
||||
- **THEN** validate the proposal (Why/What sections)
|
||||
- **AND** do not require or validate spec deltas
|
||||
|
||||
#### Scenario: Delta validation when specs exist
|
||||
- **WHEN** a change contains one or more `specs/<capability>/spec.md` files
|
||||
- **THEN** validate delta-formatted specs with existing rules (SHALL/MUST, scenarios, duplicates, conflicts)
|
||||
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Validation SHALL provide actionable remediation steps
|
||||
Validation output SHALL include specific guidance to fix each error, including expected structure, example headers, and suggested commands to verify fixes.
|
||||
|
||||
#### Scenario: No deltas found in change
|
||||
- **WHEN** validating a change that contains `specs/` with one or more `*/spec.md` files but the parser finds zero deltas
|
||||
- **THEN** show error "No deltas found" with guidance:
|
||||
- Ensure `openspec/changes/{id}/specs/` has `.md` files that include delta headers
|
||||
- Use delta headers: `## ADDED Requirements`, `## MODIFIED Requirements`, `## REMOVED Requirements`, `## RENAMED Requirements`
|
||||
- Each requirement must include at least one `#### Scenario:` block
|
||||
- Try: `openspec change show {id} --json --deltas-only` to inspect parsed deltas
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
## 1. Validator changes
|
||||
- [ ] 1.1 Change `validateChangeDeltaSpecs` to only emit "Change must have at least one delta" when `specs/` exists and contains at least one `*/spec.md` but parsed total deltas is 0
|
||||
- [ ] 1.2 Return valid (no error) when `specs/` directory is missing or has no `spec.md` files
|
||||
|
||||
## 2. CLI changes
|
||||
- [ ] 2.1 In bulk validation, keep current behavior (call delta validator). Behavior remains correct after 1.1
|
||||
- [ ] 2.2 Add a short INFO log in human-readable mode when a change has no `specs/` (optional)
|
||||
|
||||
## 3. Documentation
|
||||
- [ ] 3.1 Update README and template: "Validation checks only existing artifacts. Proposal-only changes are valid without spec deltas."
|
||||
|
||||
## 4. Tests
|
||||
- [ ] 4.1 Add test: proposal-only change passes validation without deltas
|
||||
- [ ] 4.2 Add test: specs present but zero parsed deltas → ERROR
|
||||
- [ ] 4.3 Add test: specs present with proper deltas → valid
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
# artifact-graph Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change add-artifact-graph-core. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Schema Loading
|
||||
The system SHALL load artifact graph definitions from YAML schema files within schema directories.
|
||||
|
||||
#### Scenario: Valid schema loaded
|
||||
- **WHEN** a schema directory contains a valid `schema.yaml` file
|
||||
- **THEN** the system returns an ArtifactGraph with all artifacts and dependencies
|
||||
|
||||
#### Scenario: Invalid schema rejected
|
||||
- **WHEN** a schema YAML file is missing required fields
|
||||
- **THEN** the system throws an error with a descriptive message
|
||||
|
||||
#### Scenario: Cyclic dependencies detected
|
||||
- **WHEN** a schema contains cyclic artifact dependencies
|
||||
- **THEN** the system throws an error listing the artifact IDs in the cycle
|
||||
|
||||
#### Scenario: Invalid dependency reference
|
||||
- **WHEN** an artifact's `requires` array references a non-existent artifact ID
|
||||
- **THEN** the system throws an error identifying the invalid reference
|
||||
|
||||
#### Scenario: Duplicate artifact IDs rejected
|
||||
- **WHEN** a schema contains multiple artifacts with the same ID
|
||||
- **THEN** the system throws an error identifying the duplicate
|
||||
|
||||
#### Scenario: Schema directory not found
|
||||
- **WHEN** resolving a schema name that has no corresponding directory
|
||||
- **THEN** the system throws an error listing available schemas
|
||||
|
||||
### Requirement: Build Order Calculation
|
||||
The system SHALL compute a valid topological build order for artifacts.
|
||||
|
||||
#### Scenario: Linear dependency chain
|
||||
- **WHEN** artifacts form a linear chain (A → B → C)
|
||||
- **THEN** getBuildOrder() returns [A, B, C]
|
||||
|
||||
#### Scenario: Diamond dependency
|
||||
- **WHEN** artifacts form a diamond (A → B, A → C, B → D, C → D)
|
||||
- **THEN** getBuildOrder() returns A before B and C, and D last
|
||||
|
||||
#### Scenario: Independent artifacts
|
||||
- **WHEN** artifacts have no dependencies
|
||||
- **THEN** getBuildOrder() returns them in a stable order
|
||||
|
||||
### Requirement: State Detection
|
||||
The system SHALL detect artifact completion state by scanning the filesystem.
|
||||
|
||||
#### Scenario: Simple file exists
|
||||
- **WHEN** an artifact generates "proposal.md" and the file exists
|
||||
- **THEN** the artifact is marked as completed
|
||||
|
||||
#### Scenario: Simple file missing
|
||||
- **WHEN** an artifact generates "proposal.md" and the file does not exist
|
||||
- **THEN** the artifact is not marked as completed
|
||||
|
||||
#### Scenario: Glob pattern with files
|
||||
- **WHEN** an artifact generates "specs/*.md" and the specs/ directory contains .md files
|
||||
- **THEN** the artifact is marked as completed
|
||||
|
||||
#### Scenario: Glob pattern empty
|
||||
- **WHEN** an artifact generates "specs/*.md" and the specs/ directory is empty or missing
|
||||
- **THEN** the artifact is not marked as completed
|
||||
|
||||
#### Scenario: Missing change directory
|
||||
- **WHEN** the change directory does not exist
|
||||
- **THEN** all artifacts are marked as not completed (empty state)
|
||||
|
||||
### Requirement: Ready Artifact Query
|
||||
The system SHALL identify which artifacts are ready to be created based on dependency completion.
|
||||
|
||||
#### Scenario: Root artifacts ready initially
|
||||
- **WHEN** no artifacts are completed
|
||||
- **THEN** getNextArtifacts() returns artifacts with no dependencies
|
||||
|
||||
#### Scenario: Dependent artifact becomes ready
|
||||
- **WHEN** an artifact's dependencies are all completed
|
||||
- **THEN** getNextArtifacts() includes that artifact
|
||||
|
||||
#### Scenario: Blocked artifacts excluded
|
||||
- **WHEN** an artifact has uncompleted dependencies
|
||||
- **THEN** getNextArtifacts() does not include that artifact
|
||||
|
||||
### Requirement: Completion Check
|
||||
The system SHALL determine when all artifacts in a graph are complete.
|
||||
|
||||
#### Scenario: All complete
|
||||
- **WHEN** all artifacts in the graph are in the completed set
|
||||
- **THEN** isComplete() returns true
|
||||
|
||||
#### Scenario: Partially complete
|
||||
- **WHEN** some artifacts in the graph are not completed
|
||||
- **THEN** isComplete() returns false
|
||||
|
||||
### Requirement: Blocked Query
|
||||
The system SHALL identify which artifacts are blocked and return all their unmet dependencies.
|
||||
|
||||
#### Scenario: Artifact blocked by single dependency
|
||||
- **WHEN** artifact B requires artifact A and A is not complete
|
||||
- **THEN** getBlocked() returns `{ B: ['A'] }`
|
||||
|
||||
#### Scenario: Artifact blocked by multiple dependencies
|
||||
- **WHEN** artifact C requires A and B, and only A is complete
|
||||
- **THEN** getBlocked() returns `{ C: ['B'] }`
|
||||
|
||||
#### Scenario: Artifact blocked by all dependencies
|
||||
- **WHEN** artifact C requires A and B, and neither is complete
|
||||
- **THEN** getBlocked() returns `{ C: ['A', 'B'] }`
|
||||
|
||||
### Requirement: Schema Directory Structure
|
||||
The system SHALL support self-contained schema directories with co-located templates.
|
||||
|
||||
#### Scenario: Schema with templates
|
||||
- **WHEN** a schema directory contains `schema.yaml` and `templates/` subdirectory
|
||||
- **THEN** artifacts can reference templates relative to the schema's templates directory
|
||||
|
||||
#### Scenario: User schema override
|
||||
- **WHEN** a schema directory exists at `${XDG_DATA_HOME}/openspec/schemas/<name>/`
|
||||
- **THEN** the system uses that directory instead of the built-in
|
||||
|
||||
#### Scenario: Built-in schema fallback
|
||||
- **WHEN** no user override exists for a schema
|
||||
- **THEN** the system uses the package built-in schema directory
|
||||
|
||||
#### Scenario: List available schemas
|
||||
- **WHEN** listing schemas
|
||||
- **THEN** the system returns schema names from both user and package directories
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# change-creation Specification
|
||||
|
||||
## Purpose
|
||||
Provide programmatic utilities for creating and validating OpenSpec change directories.
|
||||
## Requirements
|
||||
### Requirement: Change Creation
|
||||
The system SHALL provide a function to create new change directories programmatically.
|
||||
|
||||
#### Scenario: Create change
|
||||
- **WHEN** `createChange(projectRoot, 'add-auth')` is called
|
||||
- **THEN** the system creates `openspec/changes/add-auth/` directory
|
||||
|
||||
#### Scenario: Duplicate change rejected
|
||||
- **WHEN** `createChange(projectRoot, 'add-auth')` is called and `openspec/changes/add-auth/` already exists
|
||||
- **THEN** the system throws an error indicating the change already exists
|
||||
|
||||
#### Scenario: Creates parent directories if needed
|
||||
- **WHEN** `createChange(projectRoot, 'add-auth')` is called and `openspec/changes/` does not exist
|
||||
- **THEN** the system creates the full path including parent directories
|
||||
|
||||
#### Scenario: Invalid change name rejected
|
||||
- **WHEN** `createChange(projectRoot, 'Add Auth')` is called with an invalid name
|
||||
- **THEN** the system throws a validation error
|
||||
|
||||
### Requirement: Change Name Validation
|
||||
The system SHALL validate change names follow kebab-case conventions.
|
||||
|
||||
#### Scenario: Valid kebab-case name accepted
|
||||
- **WHEN** a change name like `add-user-auth` is validated
|
||||
- **THEN** validation returns `{ valid: true }`
|
||||
|
||||
#### Scenario: Numeric suffixes accepted
|
||||
- **WHEN** a change name like `add-feature-2` is validated
|
||||
- **THEN** validation returns `{ valid: true }`
|
||||
|
||||
#### Scenario: Single word accepted
|
||||
- **WHEN** a change name like `refactor` is validated
|
||||
- **THEN** validation returns `{ valid: true }`
|
||||
|
||||
#### Scenario: Uppercase characters rejected
|
||||
- **WHEN** a change name like `Add-Auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Spaces rejected
|
||||
- **WHEN** a change name like `add auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Underscores rejected
|
||||
- **WHEN** a change name like `add_auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Special characters rejected
|
||||
- **WHEN** a change name like `add-auth!` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Leading hyphen rejected
|
||||
- **WHEN** a change name like `-add-auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Trailing hyphen rejected
|
||||
- **WHEN** a change name like `add-auth-` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
#### Scenario: Consecutive hyphens rejected
|
||||
- **WHEN** a change name like `add--auth` is validated
|
||||
- **THEN** validation returns `{ valid: false, error: "..." }`
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
# cli-artifact-workflow Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change add-artifact-workflow-cli. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Status Command
|
||||
|
||||
The system SHALL display artifact completion status for a change, including scaffolded (empty) changes.
|
||||
|
||||
> **Fixes bug**: Previously required `proposal.md` to exist via `getActiveChangeIds()`.
|
||||
|
||||
#### Scenario: Show status with all states
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id>`
|
||||
- **THEN** the system displays each artifact with status indicator:
|
||||
- `[x]` for completed artifacts
|
||||
- `[ ]` for ready artifacts
|
||||
- `[-]` for blocked artifacts (with missing dependencies listed)
|
||||
|
||||
#### Scenario: Status shows completion summary
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id>`
|
||||
- **THEN** output includes completion percentage and count (e.g., "2/4 artifacts complete")
|
||||
|
||||
#### Scenario: Status JSON output
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id> --json`
|
||||
- **THEN** the system outputs JSON with changeName, schemaName, isComplete, and artifacts array
|
||||
|
||||
#### Scenario: Status JSON includes apply requirements
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id> --json`
|
||||
- **THEN** the system outputs JSON with:
|
||||
- `changeName`, `schemaName`, `isComplete`, `artifacts` array
|
||||
- `applyRequires`: array of artifact IDs needed for apply phase
|
||||
|
||||
#### Scenario: Status on scaffolded change
|
||||
|
||||
- **WHEN** user runs `openspec status --change <id>` on a change with no artifacts
|
||||
- **THEN** system displays all artifacts with their status
|
||||
- **AND** root artifacts (no dependencies) show as ready `[ ]`
|
||||
- **AND** dependent artifacts show as blocked `[-]`
|
||||
|
||||
#### Scenario: Missing change parameter
|
||||
|
||||
- **WHEN** user runs `openspec status` without `--change`
|
||||
- **THEN** the system displays an error with list of available changes
|
||||
- **AND** includes scaffolded changes (directories without proposal.md)
|
||||
|
||||
#### Scenario: Unknown change
|
||||
|
||||
- **WHEN** user runs `openspec status --change unknown-id`
|
||||
- **AND** directory `openspec/changes/unknown-id/` does not exist
|
||||
- **THEN** the system displays an error listing all available change directories
|
||||
|
||||
### Requirement: Instructions Command
|
||||
|
||||
The system SHALL output enriched instructions for creating an artifact, including for scaffolded changes.
|
||||
|
||||
#### Scenario: Show enriched instructions
|
||||
|
||||
- **WHEN** user runs `openspec instructions <artifact> --change <id>`
|
||||
- **THEN** the system outputs:
|
||||
- Artifact metadata (ID, output path, description)
|
||||
- Template content
|
||||
- Dependency status (done/missing)
|
||||
- Unlocked artifacts (what becomes available after completion)
|
||||
|
||||
#### Scenario: Instructions JSON output
|
||||
|
||||
- **WHEN** user runs `openspec instructions <artifact> --change <id> --json`
|
||||
- **THEN** the system outputs JSON matching ArtifactInstructions interface
|
||||
|
||||
#### Scenario: Unknown artifact
|
||||
|
||||
- **WHEN** user runs `openspec instructions unknown-artifact --change <id>`
|
||||
- **THEN** the system displays an error listing valid artifact IDs for the schema
|
||||
|
||||
#### Scenario: Artifact with unmet dependencies
|
||||
|
||||
- **WHEN** user requests instructions for a blocked artifact
|
||||
- **THEN** the system displays instructions with a warning about missing dependencies
|
||||
|
||||
#### Scenario: Instructions on scaffolded change
|
||||
|
||||
- **WHEN** user runs `openspec instructions proposal --change <id>` on a scaffolded change
|
||||
- **THEN** system outputs template and metadata for creating the proposal
|
||||
- **AND** does not require any artifacts to already exist
|
||||
|
||||
### Requirement: Templates Command
|
||||
The system SHALL show resolved template paths for all artifacts in a schema.
|
||||
|
||||
#### Scenario: List template paths with default schema
|
||||
- **WHEN** user runs `openspec templates`
|
||||
- **THEN** the system displays each artifact with its resolved template path using the default schema
|
||||
|
||||
#### Scenario: List template paths with custom schema
|
||||
- **WHEN** user runs `openspec templates --schema tdd`
|
||||
- **THEN** the system displays template paths for the specified schema
|
||||
|
||||
#### Scenario: Templates JSON output
|
||||
- **WHEN** user runs `openspec templates --json`
|
||||
- **THEN** the system outputs JSON mapping artifact IDs to template paths
|
||||
|
||||
#### Scenario: Template resolution source
|
||||
- **WHEN** displaying template paths
|
||||
- **THEN** the system indicates whether each template is from user override or package built-in
|
||||
|
||||
### Requirement: New Change Command
|
||||
The system SHALL create new change directories with validation.
|
||||
|
||||
#### Scenario: Create valid change
|
||||
- **WHEN** user runs `openspec new change add-feature`
|
||||
- **THEN** the system creates `openspec/changes/add-feature/` directory
|
||||
|
||||
#### Scenario: Invalid change name
|
||||
- **WHEN** user runs `openspec new change "Add Feature"` with invalid name
|
||||
- **THEN** the system displays validation error with guidance
|
||||
|
||||
#### Scenario: Duplicate change name
|
||||
- **WHEN** user runs `openspec new change existing-change` for an existing change
|
||||
- **THEN** the system displays an error indicating the change already exists
|
||||
|
||||
#### Scenario: Create with description
|
||||
- **WHEN** user runs `openspec new change add-feature --description "Add new feature"`
|
||||
- **THEN** the system creates the change directory with description in README.md
|
||||
|
||||
### Requirement: Schema Selection
|
||||
The system SHALL support custom schema selection for workflow commands.
|
||||
|
||||
#### Scenario: Default schema
|
||||
- **WHEN** user runs workflow commands without `--schema`
|
||||
- **THEN** the system uses the "spec-driven" schema
|
||||
|
||||
#### Scenario: Custom schema
|
||||
- **WHEN** user runs `openspec status --change <id> --schema tdd`
|
||||
- **THEN** the system uses the specified schema for artifact graph
|
||||
|
||||
#### Scenario: Unknown schema
|
||||
- **WHEN** user specifies an unknown schema
|
||||
- **THEN** the system displays an error listing available schemas
|
||||
|
||||
### Requirement: Output Formatting
|
||||
The system SHALL provide consistent output formatting.
|
||||
|
||||
#### Scenario: Color output
|
||||
- **WHEN** terminal supports colors
|
||||
- **THEN** status indicators use colors: green (done), yellow (ready), red (blocked)
|
||||
|
||||
#### Scenario: No color output
|
||||
- **WHEN** `--no-color` flag is used or NO_COLOR environment variable is set
|
||||
- **THEN** output uses text-only indicators without ANSI colors
|
||||
|
||||
#### Scenario: Progress indication
|
||||
- **WHEN** loading change state takes time
|
||||
- **THEN** the system displays a spinner during loading
|
||||
|
||||
### Requirement: Experimental Isolation
|
||||
The system SHALL implement artifact workflow commands in isolation for easy removal.
|
||||
|
||||
#### Scenario: Single file implementation
|
||||
- **WHEN** artifact workflow feature is implemented
|
||||
- **THEN** all commands are in `src/commands/artifact-workflow.ts`
|
||||
|
||||
#### Scenario: Help text marking
|
||||
- **WHEN** user runs `--help` on any artifact workflow command
|
||||
- **THEN** help text indicates the command is experimental
|
||||
|
||||
### Requirement: Schema Apply Block
|
||||
|
||||
The system SHALL support an `apply` block in schema definitions that controls when and how implementation begins.
|
||||
|
||||
#### Scenario: Schema with apply block
|
||||
|
||||
- **WHEN** a schema defines an `apply` block
|
||||
- **THEN** the system uses `apply.requires` to determine which artifacts must exist before apply
|
||||
- **AND** uses `apply.tracks` to identify the file for progress tracking (or null if none)
|
||||
- **AND** uses `apply.instruction` for guidance shown to the agent
|
||||
|
||||
#### Scenario: Schema without apply block
|
||||
|
||||
- **WHEN** a schema has no `apply` block
|
||||
- **THEN** the system requires all artifacts to exist before apply is available
|
||||
- **AND** uses default instruction: "All artifacts complete. Proceed with implementation."
|
||||
|
||||
### Requirement: Apply Instructions Command
|
||||
|
||||
The system SHALL generate schema-aware apply instructions via `openspec instructions apply`.
|
||||
|
||||
#### Scenario: Generate apply instructions
|
||||
|
||||
- **WHEN** user runs `openspec instructions apply --change <id>`
|
||||
- **AND** all required artifacts (per schema's `apply.requires`) exist
|
||||
- **THEN** the system outputs:
|
||||
- Context files from all existing artifacts
|
||||
- Schema-specific instruction text
|
||||
- Progress tracking file path (if `apply.tracks` is set)
|
||||
|
||||
#### Scenario: Apply blocked by missing artifacts
|
||||
|
||||
- **WHEN** user runs `openspec instructions apply --change <id>`
|
||||
- **AND** required artifacts are missing
|
||||
- **THEN** the system indicates apply is blocked
|
||||
- **AND** lists which artifacts must be created first
|
||||
|
||||
#### Scenario: Apply instructions JSON output
|
||||
|
||||
- **WHEN** user runs `openspec instructions apply --change <id> --json`
|
||||
- **THEN** the system outputs JSON with:
|
||||
- `contextFiles`: array of paths to existing artifacts
|
||||
- `instruction`: the apply instruction text
|
||||
- `tracks`: path to progress file or null
|
||||
- `applyRequires`: list of required artifact IDs
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Next Command
|
||||
|
||||
**Reason**: Redundant with Status Command - `openspec status` already shows which artifacts are ready (status: "ready") vs blocked vs done.
|
||||
|
||||
**Migration**: Use `openspec status --change <id> --json` and filter artifacts with `status: "ready"` to find artifacts that can be created next.
|
||||
|
||||
@@ -172,12 +172,6 @@ The command SHALL use consistent exit codes to indicate different failure modes.
|
||||
### Requirement: Slash Command Configuration
|
||||
The init command SHALL generate slash command files for supported editors using shared templates.
|
||||
|
||||
#### Scenario: Generating slash commands for Antigravity
|
||||
- **WHEN** the user selects Antigravity during initialization
|
||||
- **THEN** create `.agent/workflows/openspec-proposal.md`, `.agent/workflows/openspec-apply.md`, and `.agent/workflows/openspec-archive.md`
|
||||
- **AND** ensure each file begins with YAML frontmatter that contains only a `description: <stage summary>` field followed by the shared OpenSpec workflow instructions wrapped in managed markers
|
||||
- **AND** populate the workflow body with the same proposal/apply/archive guidance used for other tools so Antigravity behaves like Windsurf while pointing to the `.agent/workflows/` directory
|
||||
|
||||
#### Scenario: Generating slash commands for Claude Code
|
||||
- **WHEN** the user selects Claude Code during initialization
|
||||
- **THEN** create `.claude/commands/openspec/proposal.md`, `.claude/commands/openspec/apply.md`, and `.claude/commands/openspec/archive.md`
|
||||
@@ -192,7 +186,7 @@ The init command SHALL generate slash command files for supported editors using
|
||||
|
||||
#### Scenario: Generating slash commands for Cline
|
||||
- **WHEN** the user selects Cline during initialization
|
||||
- **THEN** create `.clinerules/workflows/openspec-proposal.md`, `.clinerules/workflows/openspec-apply.md`, and `.clinerules/workflows/openspec-archive.md`
|
||||
- **THEN** create `.clinerules/openspec-proposal.md`, `.clinerules/openspec-apply.md`, and `.clinerules/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** include Cline-specific Markdown heading frontmatter
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
@@ -52,11 +52,6 @@ The update command SHALL always update the core OpenSpec files and display an AS
|
||||
### Requirement: Slash Command Updates
|
||||
The update command SHALL refresh existing slash command files for configured tools without creating new ones, and ensure the OpenCode archive command accepts change ID arguments.
|
||||
|
||||
#### Scenario: Updating slash commands for Antigravity
|
||||
- **WHEN** `.agent/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh the OpenSpec-managed portion of each file so the workflow copy matches other tools while preserving the existing single-field `description` frontmatter
|
||||
- **AND** skip creating any missing workflow files during update, mirroring the behavior for Windsurf and other IDEs
|
||||
|
||||
#### Scenario: Updating slash commands for Claude Code
|
||||
- **WHEN** `.claude/commands/openspec/` contains `proposal.md`, `apply.md`, and `archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
@@ -68,7 +63,7 @@ The update command SHALL refresh existing slash command files for configured too
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Cline
|
||||
- **WHEN** `.clinerules/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **WHEN** `.clinerules/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** include Cline-specific Markdown heading frontmatter
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
@@ -20,13 +20,12 @@ The system SHALL provide a `view` command that displays a dashboard overview of
|
||||
|
||||
### Requirement: Summary Section
|
||||
|
||||
The dashboard SHALL display a summary section with key project metrics, including draft change count.
|
||||
The dashboard SHALL display a summary section with key project metrics.
|
||||
|
||||
#### Scenario: Complete summary display
|
||||
|
||||
- **WHEN** dashboard is rendered with specs and changes
|
||||
- **THEN** system shows total number of specifications and requirements
|
||||
- **AND** shows number of draft changes
|
||||
- **AND** shows number of active changes in progress
|
||||
- **AND** shows number of completed changes
|
||||
- **AND** shows overall task progress percentage
|
||||
@@ -47,13 +46,11 @@ The dashboard SHALL show active changes with visual progress indicators.
|
||||
|
||||
### Requirement: Completed Changes Display
|
||||
|
||||
The dashboard SHALL list completed changes in a separate section, only showing changes with ALL tasks completed.
|
||||
|
||||
> **Fixes bug**: Previously, changes with `total === 0` were incorrectly shown as completed.
|
||||
The dashboard SHALL list completed changes in a separate section.
|
||||
|
||||
#### Scenario: Completed changes listing
|
||||
|
||||
- **WHEN** there are changes with `tasks.total > 0` AND `tasks.completed === tasks.total`
|
||||
- **WHEN** there are completed changes (all tasks done)
|
||||
- **THEN** system shows them with checkmark indicators in a dedicated section
|
||||
|
||||
#### Scenario: Mixed completion states
|
||||
@@ -61,12 +58,6 @@ The dashboard SHALL list completed changes in a separate section, only showing c
|
||||
- **WHEN** some changes are complete and others active
|
||||
- **THEN** system separates them into appropriate sections
|
||||
|
||||
#### Scenario: Empty changes not completed
|
||||
|
||||
- **WHEN** a change has no tasks.md or zero tasks defined
|
||||
- **THEN** system does NOT show it in "Completed Changes" section
|
||||
- **AND** shows it in "Draft Changes" section instead
|
||||
|
||||
### Requirement: Specifications Display
|
||||
|
||||
The dashboard SHALL display specifications sorted by requirement count.
|
||||
@@ -112,18 +103,3 @@ The view command SHALL handle errors gracefully.
|
||||
- **WHEN** specs or changes have invalid format
|
||||
- **THEN** system skips invalid items and continues rendering
|
||||
|
||||
### Requirement: Draft Changes Display
|
||||
|
||||
The dashboard SHALL display changes without tasks in a separate "Draft" section.
|
||||
|
||||
#### Scenario: Draft changes listing
|
||||
|
||||
- **WHEN** there are changes with no tasks.md or zero tasks defined
|
||||
- **THEN** system shows them in a "Draft Changes" section
|
||||
- **AND** uses a distinct indicator (e.g., `○`) to show draft status
|
||||
|
||||
#### Scenario: Draft section ordering
|
||||
|
||||
- **WHEN** multiple draft changes exist
|
||||
- **THEN** system sorts them alphabetically by name
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# instruction-loader Specification
|
||||
|
||||
## Purpose
|
||||
The instruction-loader loads instruction templates from schema directories, validates and enriches them with metadata and parameters (such as change context and dependency status), and exposes them for use by downstream services including template retrieval, parameter substitution, and enrichment.
|
||||
|
||||
## Requirements
|
||||
### Requirement: Template Loading
|
||||
The system SHALL load templates from schema directories.
|
||||
|
||||
#### Scenario: Load template from schema directory
|
||||
- **WHEN** `loadTemplate(schemaName, templatePath)` is called
|
||||
- **THEN** the system loads the template from `schemas/<schemaName>/templates/<templatePath>`
|
||||
|
||||
#### Scenario: Template file not found
|
||||
- **WHEN** a template file does not exist in the schema's templates directory
|
||||
- **THEN** the system throws an error with the template path
|
||||
|
||||
### Requirement: Change Context Loading
|
||||
The system SHALL load change context combining graph and completion state.
|
||||
|
||||
#### Scenario: Load context for existing change
|
||||
- **WHEN** `loadChangeContext(projectRoot, changeName)` is called for an existing change
|
||||
- **THEN** the system returns a context with graph, completed set, schema name, and change info
|
||||
|
||||
#### Scenario: Load context with custom schema
|
||||
- **WHEN** `loadChangeContext(projectRoot, changeName, schemaName)` is called
|
||||
- **THEN** the system uses the specified schema instead of default
|
||||
|
||||
#### Scenario: Load context for non-existent change directory
|
||||
- **WHEN** `loadChangeContext` is called for a non-existent change directory
|
||||
- **THEN** the system returns context with empty completed set
|
||||
|
||||
### Requirement: Template Enrichment
|
||||
The system SHALL enrich templates with change-specific context.
|
||||
|
||||
#### Scenario: Include artifact metadata
|
||||
- **WHEN** instructions are generated for an artifact
|
||||
- **THEN** the output includes change name, artifact ID, schema name, and output path
|
||||
|
||||
#### Scenario: Include dependency status
|
||||
- **WHEN** an artifact has dependencies
|
||||
- **THEN** the output shows each dependency with completion status (done/missing)
|
||||
|
||||
#### Scenario: Include unlocked artifacts
|
||||
- **WHEN** instructions are generated
|
||||
- **THEN** the output includes which artifacts become available after this one
|
||||
|
||||
#### Scenario: Root artifact indicator
|
||||
- **WHEN** an artifact has no dependencies
|
||||
- **THEN** the dependency section indicates this is a root artifact
|
||||
|
||||
### Requirement: Status Formatting
|
||||
The system SHALL format change status as readable output.
|
||||
|
||||
#### Scenario: All artifacts completed
|
||||
- **WHEN** all artifacts are completed
|
||||
- **THEN** status shows all artifacts as "done"
|
||||
|
||||
#### Scenario: Mixed completion status
|
||||
- **WHEN** some artifacts are completed
|
||||
- **THEN** status shows completed as "done", ready as "ready", blocked as "blocked"
|
||||
|
||||
#### Scenario: Blocked artifact details
|
||||
- **WHEN** an artifact is blocked
|
||||
- **THEN** status shows which dependencies are missing
|
||||
|
||||
#### Scenario: Include output paths
|
||||
- **WHEN** status is formatted
|
||||
- **THEN** each artifact shows its output path pattern
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
# OPSX Archive Skill Spec
|
||||
|
||||
### Requirement: OPSX Archive Skill
|
||||
|
||||
The system SHALL provide an `/opsx:archive` skill that archives completed changes in the experimental workflow.
|
||||
|
||||
#### Scenario: Archive a change with all artifacts complete
|
||||
|
||||
- **WHEN** agent executes `/opsx:archive` with a change name
|
||||
- **AND** all artifacts in the schema are complete
|
||||
- **AND** all tasks are complete
|
||||
- **THEN** the agent moves the change to `openspec/changes/archive/YYYY-MM-DD-<name>/`
|
||||
- **AND** displays success message with archived location
|
||||
|
||||
#### Scenario: Change selection prompt
|
||||
|
||||
- **WHEN** agent executes `/opsx:archive` without specifying a change
|
||||
- **THEN** the agent prompts user to select from available changes
|
||||
- **AND** shows only active changes (excludes archive/)
|
||||
|
||||
### Requirement: Artifact Completion Check
|
||||
|
||||
The skill SHALL check artifact completion status using the artifact graph before archiving.
|
||||
|
||||
#### Scenario: Incomplete artifacts warning
|
||||
|
||||
- **WHEN** agent checks artifact status
|
||||
- **AND** one or more artifacts have status other than `done`
|
||||
- **THEN** display warning listing incomplete artifacts
|
||||
- **AND** prompt user for confirmation to continue
|
||||
- **AND** proceed if user confirms
|
||||
|
||||
#### Scenario: All artifacts complete
|
||||
|
||||
- **WHEN** agent checks artifact status
|
||||
- **AND** all artifacts have status `done`
|
||||
- **THEN** proceed without warning
|
||||
|
||||
### Requirement: Task Completion Check
|
||||
|
||||
The skill SHALL check task completion status from tasks.md before archiving.
|
||||
|
||||
#### Scenario: Incomplete tasks found
|
||||
|
||||
- **WHEN** agent reads tasks.md
|
||||
- **AND** incomplete tasks are found (marked with `- [ ]`)
|
||||
- **THEN** display warning showing count of incomplete tasks
|
||||
- **AND** prompt user for confirmation to continue
|
||||
- **AND** proceed if user confirms
|
||||
|
||||
#### Scenario: All tasks complete
|
||||
|
||||
- **WHEN** agent reads tasks.md
|
||||
- **AND** all tasks are complete (marked with `- [x]`)
|
||||
- **THEN** proceed without task-related warning
|
||||
|
||||
#### Scenario: No tasks file
|
||||
|
||||
- **WHEN** tasks.md does not exist
|
||||
- **THEN** proceed without task-related warning
|
||||
|
||||
### Requirement: Spec Sync Prompt
|
||||
|
||||
The skill SHALL prompt to sync delta specs before archiving if specs exist.
|
||||
|
||||
#### Scenario: Delta specs exist
|
||||
|
||||
- **WHEN** agent checks for delta specs
|
||||
- **AND** `specs/` directory exists in the change with spec files
|
||||
- **THEN** prompt user: "This change has delta specs. Would you like to sync them to main specs before archiving?"
|
||||
- **AND** if user confirms, execute `/opsx:sync` logic
|
||||
- **AND** proceed with archive regardless of sync choice
|
||||
|
||||
#### Scenario: No delta specs
|
||||
|
||||
- **WHEN** agent checks for delta specs
|
||||
- **AND** no `specs/` directory or no spec files exist
|
||||
- **THEN** proceed without sync prompt
|
||||
|
||||
### Requirement: Archive Process
|
||||
|
||||
The skill SHALL move the change to the archive folder with date prefix.
|
||||
|
||||
#### Scenario: Successful archive
|
||||
|
||||
- **WHEN** archiving a change
|
||||
- **THEN** create `archive/` directory if it doesn't exist
|
||||
- **AND** generate target name as `YYYY-MM-DD-<change-name>` using current date
|
||||
- **AND** move entire change directory to archive location
|
||||
- **AND** preserve `.openspec.yaml` file in archived change
|
||||
|
||||
#### Scenario: Archive already exists
|
||||
|
||||
- **WHEN** target archive directory already exists
|
||||
- **THEN** fail with error message
|
||||
- **AND** suggest renaming existing archive or using different date
|
||||
|
||||
### Requirement: Skill Output
|
||||
|
||||
The skill SHALL provide clear feedback about the archive operation.
|
||||
|
||||
#### Scenario: Archive complete with sync
|
||||
|
||||
- **WHEN** archive completes after syncing specs
|
||||
- **THEN** display summary:
|
||||
- Specs synced (from `/opsx:sync` output)
|
||||
- Change archived to location
|
||||
- Schema that was used
|
||||
|
||||
#### Scenario: Archive complete without sync
|
||||
|
||||
- **WHEN** archive completes without syncing specs
|
||||
- **THEN** display summary:
|
||||
- Note that specs were not synced (if applicable)
|
||||
- Change archived to location
|
||||
- Schema that was used
|
||||
|
||||
#### Scenario: Archive complete with warnings
|
||||
|
||||
- **WHEN** archive completes with incomplete artifacts or tasks
|
||||
- **THEN** include note about what was incomplete
|
||||
- **AND** suggest reviewing if archive was intentional
|
||||
@@ -1,72 +0,0 @@
|
||||
# specs-sync-skill Specification
|
||||
|
||||
## Purpose
|
||||
Defines the agent skill for syncing delta specs from changes to main specs.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Specs Sync Skill
|
||||
The system SHALL provide an `/opsx:sync` skill that syncs delta specs from a change to the main specs.
|
||||
|
||||
#### Scenario: Sync delta specs to main specs
|
||||
- **WHEN** agent executes `/opsx:sync` with a change name
|
||||
- **THEN** the agent reads delta specs from `openspec/changes/<name>/specs/`
|
||||
- **AND** reads corresponding main specs from `openspec/specs/`
|
||||
- **AND** reconciles main specs to match what the deltas describe
|
||||
|
||||
#### Scenario: Idempotent operation
|
||||
- **WHEN** agent executes `/opsx:sync` multiple times on the same change
|
||||
- **THEN** the result is the same as running it once
|
||||
- **AND** no duplicate requirements are created
|
||||
|
||||
#### Scenario: Change selection prompt
|
||||
- **WHEN** agent executes `/opsx:sync` without specifying a change
|
||||
- **THEN** the agent prompts user to select from available changes
|
||||
- **AND** shows changes that have delta specs
|
||||
|
||||
### Requirement: Delta Reconciliation Logic
|
||||
The agent SHALL reconcile main specs with delta specs using the delta operation headers.
|
||||
|
||||
#### Scenario: ADDED requirements
|
||||
- **WHEN** delta contains `## ADDED Requirements` with a requirement
|
||||
- **AND** the requirement does not exist in main spec
|
||||
- **THEN** add the requirement to main spec
|
||||
|
||||
#### Scenario: ADDED requirement already exists
|
||||
- **WHEN** delta contains `## ADDED Requirements` with a requirement
|
||||
- **AND** a requirement with the same name already exists in main spec
|
||||
- **THEN** update the existing requirement to match the delta version
|
||||
|
||||
#### Scenario: MODIFIED requirements
|
||||
- **WHEN** delta contains `## MODIFIED Requirements` with a requirement
|
||||
- **AND** the requirement exists in main spec
|
||||
- **THEN** replace the requirement in main spec with the delta version
|
||||
|
||||
#### Scenario: REMOVED requirements
|
||||
- **WHEN** delta contains `## REMOVED Requirements` with a requirement name
|
||||
- **AND** the requirement exists in main spec
|
||||
- **THEN** remove the requirement from main spec
|
||||
|
||||
#### Scenario: RENAMED requirements
|
||||
- **WHEN** delta contains `## RENAMED Requirements` with FROM:/TO: format
|
||||
- **AND** the FROM requirement exists in main spec
|
||||
- **THEN** rename the requirement to the TO name
|
||||
|
||||
#### Scenario: New capability spec
|
||||
- **WHEN** delta spec exists for a capability not in main specs
|
||||
- **THEN** create new main spec file at `openspec/specs/<capability>/spec.md`
|
||||
|
||||
### Requirement: Skill Output
|
||||
The skill SHALL provide clear feedback on what was applied.
|
||||
|
||||
#### Scenario: Show applied changes
|
||||
- **WHEN** reconciliation completes successfully
|
||||
- **THEN** display summary of changes per capability:
|
||||
- Number of requirements added
|
||||
- Number of requirements modified
|
||||
- Number of requirements removed
|
||||
- Number of requirements renamed
|
||||
|
||||
#### Scenario: No changes needed
|
||||
- **WHEN** main specs already match delta specs
|
||||
- **THEN** display "Specs already in sync - no changes needed"
|
||||
+1
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fission-ai/openspec",
|
||||
"version": "0.17.2",
|
||||
"version": "0.17.0",
|
||||
"description": "AI-native system for spec-driven development",
|
||||
"keywords": [
|
||||
"openspec",
|
||||
@@ -32,7 +32,6 @@
|
||||
"files": [
|
||||
"dist",
|
||||
"bin",
|
||||
"schemas",
|
||||
"scripts/postinstall.js",
|
||||
"!dist/**/*.test.js",
|
||||
"!dist/**/__tests__",
|
||||
@@ -74,9 +73,7 @@
|
||||
"@inquirer/prompts": "^7.8.0",
|
||||
"chalk": "^5.5.0",
|
||||
"commander": "^14.0.0",
|
||||
"fast-glob": "^3.3.3",
|
||||
"ora": "^8.2.0",
|
||||
"yaml": "^2.8.2",
|
||||
"zod": "^4.0.17"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+11
-25
@@ -20,15 +20,9 @@ importers:
|
||||
commander:
|
||||
specifier: ^14.0.0
|
||||
version: 14.0.0
|
||||
fast-glob:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
ora:
|
||||
specifier: ^8.2.0
|
||||
version: 8.2.0
|
||||
yaml:
|
||||
specifier: ^2.8.2
|
||||
version: 2.8.2
|
||||
zod:
|
||||
specifier: ^4.0.17
|
||||
version: 4.0.17
|
||||
@@ -53,7 +47,7 @@ importers:
|
||||
version: 8.50.1(eslint@9.39.2)(typescript@5.9.3)
|
||||
vitest:
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(@types/node@24.2.0)(@vitest/ui@3.2.4)(yaml@2.8.2)
|
||||
version: 3.2.4(@types/node@24.2.0)(@vitest/ui@3.2.4)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -1582,11 +1576,6 @@ packages:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
yaml@2.8.2:
|
||||
resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2213,13 +2202,13 @@ snapshots:
|
||||
chai: 5.2.1
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@7.0.6(@types/node@24.2.0)(yaml@2.8.2))':
|
||||
'@vitest/mocker@3.2.4(vite@7.0.6(@types/node@24.2.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
vite: 7.0.6(@types/node@24.2.0)(yaml@2.8.2)
|
||||
vite: 7.0.6(@types/node@24.2.0)
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
@@ -2250,7 +2239,7 @@ snapshots:
|
||||
sirv: 3.0.1
|
||||
tinyglobby: 0.2.14
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/node@24.2.0)(@vitest/ui@3.2.4)(yaml@2.8.2)
|
||||
vitest: 3.2.4(@types/node@24.2.0)(@vitest/ui@3.2.4)
|
||||
|
||||
'@vitest/utils@3.2.4':
|
||||
dependencies:
|
||||
@@ -2998,13 +2987,13 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
vite-node@3.2.4(@types/node@24.2.0)(yaml@2.8.2):
|
||||
vite-node@3.2.4(@types/node@24.2.0):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.1
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 7.0.6(@types/node@24.2.0)(yaml@2.8.2)
|
||||
vite: 7.0.6(@types/node@24.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@@ -3019,7 +3008,7 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite@7.0.6(@types/node@24.2.0)(yaml@2.8.2):
|
||||
vite@7.0.6(@types/node@24.2.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.8
|
||||
fdir: 6.4.6(picomatch@4.0.3)
|
||||
@@ -3030,13 +3019,12 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 24.2.0
|
||||
fsevents: 2.3.3
|
||||
yaml: 2.8.2
|
||||
|
||||
vitest@3.2.4(@types/node@24.2.0)(@vitest/ui@3.2.4)(yaml@2.8.2):
|
||||
vitest@3.2.4(@types/node@24.2.0)(@vitest/ui@3.2.4):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.2
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/mocker': 3.2.4(vite@7.0.6(@types/node@24.2.0)(yaml@2.8.2))
|
||||
'@vitest/mocker': 3.2.4(vite@7.0.6(@types/node@24.2.0))
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
'@vitest/runner': 3.2.4
|
||||
'@vitest/snapshot': 3.2.4
|
||||
@@ -3054,8 +3042,8 @@ snapshots:
|
||||
tinyglobby: 0.2.14
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 7.0.6(@types/node@24.2.0)(yaml@2.8.2)
|
||||
vite-node: 3.2.4(@types/node@24.2.0)(yaml@2.8.2)
|
||||
vite: 7.0.6(@types/node@24.2.0)
|
||||
vite-node: 3.2.4(@types/node@24.2.0)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 24.2.0
|
||||
@@ -3091,8 +3079,6 @@ snapshots:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
yaml@2.8.2: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
yoctocolors-cjs@2.1.2: {}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
name: spec-driven
|
||||
version: 1
|
||||
description: Default OpenSpec workflow - proposal → specs → design → tasks
|
||||
artifacts:
|
||||
- id: proposal
|
||||
generates: proposal.md
|
||||
description: Initial proposal document outlining the change
|
||||
template: proposal.md
|
||||
instruction: |
|
||||
Create the proposal document that establishes WHY this change is needed.
|
||||
|
||||
Sections:
|
||||
- **Why**: 1-2 sentences on the problem or opportunity. What problem does this solve? Why now?
|
||||
- **What Changes**: Bullet list of changes. Be specific about new capabilities, modifications, or removals. Mark breaking changes with **BREAKING**.
|
||||
- **Capabilities**: Identify which specs will be created or modified:
|
||||
- **New Capabilities**: List capabilities being introduced. Each becomes a new `specs/<name>/spec.md`. Use kebab-case names (e.g., `user-auth`, `data-export`).
|
||||
- **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Check `openspec/specs/` for existing spec names. Leave empty if no requirement changes.
|
||||
- **Impact**: Affected code, APIs, dependencies, or systems.
|
||||
|
||||
IMPORTANT: The Capabilities section is critical. It creates the contract between
|
||||
proposal and specs phases. Research existing specs before filling this in.
|
||||
Each capability listed here will need a corresponding spec file.
|
||||
|
||||
Keep it concise (1-2 pages). Focus on the "why" not the "how" -
|
||||
implementation details belong in design.md.
|
||||
|
||||
This is the foundation - specs, design, and tasks all build on this.
|
||||
requires: []
|
||||
|
||||
- id: specs
|
||||
generates: "specs/**/*.md"
|
||||
description: Detailed specifications for the change
|
||||
template: spec.md
|
||||
instruction: |
|
||||
Create specification files that define WHAT the system should do.
|
||||
|
||||
Create one spec file per capability/feature area in specs/<name>/spec.md.
|
||||
|
||||
Delta operations (use ## headers):
|
||||
- **ADDED Requirements**: New capabilities
|
||||
- **MODIFIED Requirements**: Changed behavior - MUST include full updated content
|
||||
- **REMOVED Requirements**: Deprecated features - MUST include **Reason** and **Migration**
|
||||
- **RENAMED Requirements**: Name changes only - use FROM:/TO: format
|
||||
|
||||
Format requirements:
|
||||
- Each requirement: `### Requirement: <name>` followed by description
|
||||
- Use SHALL/MUST for normative requirements (avoid should/may)
|
||||
- Each scenario: `#### Scenario: <name>` with WHEN/THEN format
|
||||
- **CRITICAL**: Scenarios MUST use exactly 4 hashtags (`####`). Using 3 hashtags or bullets will fail silently.
|
||||
- Every requirement MUST have at least one scenario.
|
||||
|
||||
MODIFIED requirements workflow:
|
||||
1. Locate the existing requirement in openspec/specs/<capability>/spec.md
|
||||
2. Copy the ENTIRE requirement block (from `### Requirement:` through all scenarios)
|
||||
3. Paste under `## MODIFIED Requirements` and edit to reflect new behavior
|
||||
4. Ensure header text matches exactly (whitespace-insensitive)
|
||||
|
||||
Common pitfall: Using MODIFIED with partial content loses detail at archive time.
|
||||
If adding new concerns without changing existing behavior, use ADDED instead.
|
||||
|
||||
Example:
|
||||
```
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User can export data
|
||||
The system SHALL allow users to export their data in CSV format.
|
||||
|
||||
#### Scenario: Successful export
|
||||
- **WHEN** user clicks "Export" button
|
||||
- **THEN** system downloads a CSV file with all user data
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Legacy export
|
||||
**Reason**: Replaced by new export system
|
||||
**Migration**: Use new export endpoint at /api/v2/export
|
||||
```
|
||||
|
||||
Specs should be testable - each scenario is a potential test case.
|
||||
requires:
|
||||
- proposal
|
||||
|
||||
- id: design
|
||||
generates: design.md
|
||||
description: Technical design document with implementation details
|
||||
template: design.md
|
||||
instruction: |
|
||||
Create the design document that explains HOW to implement the change.
|
||||
|
||||
When to include design.md (create only if any apply):
|
||||
- Cross-cutting change (multiple services/modules) or new architectural pattern
|
||||
- New external dependency or significant data model changes
|
||||
- Security, performance, or migration complexity
|
||||
- Ambiguity that benefits from technical decisions before coding
|
||||
|
||||
Sections:
|
||||
- **Context**: Background, current state, constraints, stakeholders
|
||||
- **Goals / Non-Goals**: What this design achieves and explicitly excludes
|
||||
- **Decisions**: Key technical choices with rationale (why X over Y?). Include alternatives considered for each decision.
|
||||
- **Risks / Trade-offs**: Known limitations, things that could go wrong. Format: [Risk] → Mitigation
|
||||
- **Migration Plan**: Steps to deploy, rollback strategy (if applicable)
|
||||
- **Open Questions**: Outstanding decisions or unknowns to resolve
|
||||
|
||||
Focus on architecture and approach, not line-by-line implementation.
|
||||
Reference the proposal for motivation and specs for requirements.
|
||||
|
||||
Good design docs explain the "why" behind technical decisions.
|
||||
requires:
|
||||
- proposal
|
||||
|
||||
- id: tasks
|
||||
generates: tasks.md
|
||||
description: Implementation tasks derived from specs and design
|
||||
template: tasks.md
|
||||
instruction: |
|
||||
Create the task list that breaks down the implementation work.
|
||||
|
||||
Guidelines:
|
||||
- Group related tasks under ## numbered headings
|
||||
- Each task is a checkbox: - [ ] X.Y Task description
|
||||
- Tasks should be small enough to complete in one session
|
||||
- Order tasks by dependency (what must be done first?)
|
||||
|
||||
Example:
|
||||
```
|
||||
## 1. Setup
|
||||
|
||||
- [ ] 1.1 Create new module structure
|
||||
- [ ] 1.2 Add dependencies to package.json
|
||||
|
||||
## 2. Core Implementation
|
||||
|
||||
- [ ] 2.1 Implement data export function
|
||||
- [ ] 2.2 Add CSV formatting utilities
|
||||
```
|
||||
|
||||
Reference specs for what needs to be built, design for how to build it.
|
||||
Each task should be verifiable - you know when it's done.
|
||||
requires:
|
||||
- specs
|
||||
- design
|
||||
|
||||
apply:
|
||||
requires: [tasks]
|
||||
tracks: tasks.md
|
||||
instruction: |
|
||||
Read context files, work through pending tasks, mark complete as you go.
|
||||
Pause if you hit blockers or need clarification.
|
||||
@@ -1,19 +0,0 @@
|
||||
## Context
|
||||
|
||||
<!-- Background and current state -->
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
<!-- What this design aims to achieve -->
|
||||
|
||||
**Non-Goals:**
|
||||
<!-- What is explicitly out of scope -->
|
||||
|
||||
## Decisions
|
||||
|
||||
<!-- Key design decisions and rationale -->
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
<!-- Known risks and trade-offs -->
|
||||
@@ -1,23 +0,0 @@
|
||||
## Why
|
||||
|
||||
<!-- Explain the motivation for this change. What problem does this solve? Why now? -->
|
||||
|
||||
## What Changes
|
||||
|
||||
<!-- Describe what will change. Be specific about new capabilities, modifications, or removals. -->
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
<!-- Capabilities being introduced. Replace <name> with kebab-case identifier (e.g., user-auth, data-export, api-rate-limiting). Each creates specs/<name>/spec.md -->
|
||||
- `<name>`: <brief description of what this capability covers>
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- Existing capabilities whose REQUIREMENTS are changing (not just implementation).
|
||||
Only list here if spec-level behavior changes. Each needs a delta spec file.
|
||||
Use existing spec names from openspec/specs/. Leave empty if no requirement changes. -->
|
||||
- `<existing-name>`: <what requirement is changing>
|
||||
|
||||
## Impact
|
||||
|
||||
<!-- Affected code, APIs, dependencies, systems -->
|
||||
@@ -1,8 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: <!-- requirement name -->
|
||||
<!-- requirement text -->
|
||||
|
||||
#### Scenario: <!-- scenario name -->
|
||||
- **WHEN** <!-- condition -->
|
||||
- **THEN** <!-- expected outcome -->
|
||||
@@ -1,9 +0,0 @@
|
||||
## 1. <!-- Task Group Name -->
|
||||
|
||||
- [ ] 1.1 <!-- Task description -->
|
||||
- [ ] 1.2 <!-- Task description -->
|
||||
|
||||
## 2. <!-- Task Group Name -->
|
||||
|
||||
- [ ] 2.1 <!-- Task description -->
|
||||
- [ ] 2.2 <!-- Task description -->
|
||||
@@ -1,213 +0,0 @@
|
||||
name: tdd
|
||||
version: 1
|
||||
description: Test-driven development workflow - tests → implementation → docs
|
||||
artifacts:
|
||||
- id: spec
|
||||
generates: spec.md
|
||||
description: Feature specification defining requirements
|
||||
template: spec.md
|
||||
instruction: |
|
||||
Create the feature specification that defines WHAT to build.
|
||||
|
||||
Sections:
|
||||
- **Feature**: Name and high-level description of the feature's purpose and user value
|
||||
- **Requirements**: List of specific requirements. Use SHALL/MUST for normative language.
|
||||
- **Acceptance Criteria**: Testable criteria in WHEN/THEN format
|
||||
|
||||
Format requirements:
|
||||
- Each requirement should be specific and testable
|
||||
- Use `#### Scenario: <name>` with WHEN/THEN format for acceptance criteria
|
||||
- Define edge cases and error scenarios explicitly
|
||||
- Every requirement MUST have at least one scenario
|
||||
|
||||
Example:
|
||||
```
|
||||
## Feature: User Authentication
|
||||
|
||||
Users can securely log into the application.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Password validation
|
||||
The system SHALL validate passwords meet minimum security requirements.
|
||||
|
||||
#### Scenario: Valid password accepted
|
||||
- **WHEN** password has 8+ chars, uppercase, lowercase, and number
|
||||
- **THEN** password is accepted
|
||||
|
||||
#### Scenario: Weak password rejected
|
||||
- **WHEN** password is less than 8 characters
|
||||
- **THEN** system displays "Password too short" error
|
||||
```
|
||||
|
||||
This spec drives test creation - each scenario becomes a test case.
|
||||
requires: []
|
||||
|
||||
- id: tests
|
||||
generates: "tests/*.test.ts"
|
||||
description: Test files written before implementation
|
||||
template: test.md
|
||||
instruction: |
|
||||
Write tests BEFORE implementation (TDD red phase).
|
||||
|
||||
File naming:
|
||||
- Create test files as `tests/<feature>.test.ts`
|
||||
- One test file per feature/capability
|
||||
- Use descriptive names matching the spec
|
||||
|
||||
Test structure:
|
||||
- Use Given/When/Then format matching spec scenarios
|
||||
- Group related tests with `describe()` blocks
|
||||
- Each scenario from spec becomes at least one `it()` test
|
||||
|
||||
Coverage requirements:
|
||||
- Cover each requirement from the spec
|
||||
- Include happy path (success cases)
|
||||
- Include edge cases (boundary conditions)
|
||||
- Include error scenarios (invalid input, failures)
|
||||
- Tests should fail initially (no implementation yet)
|
||||
|
||||
Example:
|
||||
```typescript
|
||||
describe('Password validation', () => {
|
||||
it('accepts valid password with all requirements', () => {
|
||||
// GIVEN a password meeting all requirements
|
||||
const password = 'SecurePass1';
|
||||
// WHEN validating
|
||||
const result = validatePassword(password);
|
||||
// THEN it should be accepted
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects password shorter than 8 characters', () => {
|
||||
// GIVEN a short password
|
||||
const password = 'Short1';
|
||||
// WHEN validating
|
||||
const result = validatePassword(password);
|
||||
// THEN it should be rejected with message
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toBe('Password too short');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Follow the spec requirements exactly - tests verify the spec.
|
||||
requires:
|
||||
- spec
|
||||
|
||||
- id: implementation
|
||||
generates: "src/*.ts"
|
||||
description: Implementation code to pass the tests
|
||||
template: implementation.md
|
||||
instruction: |
|
||||
Implement the feature to make tests pass (TDD green phase).
|
||||
|
||||
TDD workflow:
|
||||
1. Run tests - confirm they fail (red)
|
||||
2. Write minimal code to pass ONE test
|
||||
3. Run tests - confirm that test passes (green)
|
||||
4. Refactor if needed while keeping tests green
|
||||
5. Repeat for next failing test
|
||||
|
||||
Implementation guidelines:
|
||||
- Write minimal code to pass each test - no more, no less
|
||||
- Run tests frequently to verify progress
|
||||
- Keep functions small and focused
|
||||
- Use clear, descriptive names
|
||||
|
||||
Code organization:
|
||||
- Create source files in `src/<feature>.ts`
|
||||
- Export public API clearly
|
||||
- Keep implementation details private
|
||||
- Add JSDoc comments for public functions
|
||||
|
||||
Example structure:
|
||||
```typescript
|
||||
/**
|
||||
* Validates a password meets security requirements.
|
||||
* @param password - The password to validate
|
||||
* @returns Validation result with valid flag and optional error
|
||||
*/
|
||||
export function validatePassword(password: string): ValidationResult {
|
||||
if (password.length < 8) {
|
||||
return { valid: false, error: 'Password too short' };
|
||||
}
|
||||
// ... additional checks
|
||||
return { valid: true };
|
||||
}
|
||||
```
|
||||
|
||||
Don't over-engineer - implement only what tests require.
|
||||
requires:
|
||||
- tests
|
||||
|
||||
- id: docs
|
||||
generates: "docs/*.md"
|
||||
description: Documentation for the implemented feature
|
||||
template: docs.md
|
||||
instruction: |
|
||||
Document the implemented feature.
|
||||
|
||||
Sections:
|
||||
- **Overview**: What the feature does and why it exists (1-2 paragraphs)
|
||||
- **Getting Started**: Quick start guide to use the feature immediately
|
||||
- **Examples**: Code examples showing common use cases
|
||||
- **Reference**: Detailed API documentation, configuration options
|
||||
|
||||
Guidelines:
|
||||
- Write for the user, not the developer
|
||||
- Start with the most common use case
|
||||
- Include copy-pasteable code examples
|
||||
- Document all configuration options with defaults
|
||||
- Note any limitations, edge cases, or gotchas
|
||||
- Link to related features or specs
|
||||
|
||||
Example structure:
|
||||
```markdown
|
||||
## Overview
|
||||
|
||||
Password validation ensures user passwords meet security requirements
|
||||
before account creation or password changes.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Import and use the validation function:
|
||||
|
||||
```typescript
|
||||
import { validatePassword } from './password';
|
||||
|
||||
const result = validatePassword('MySecurePass1');
|
||||
if (!result.valid) {
|
||||
console.error(result.error);
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic validation
|
||||
...
|
||||
|
||||
### Custom error handling
|
||||
...
|
||||
|
||||
## Reference
|
||||
|
||||
### validatePassword(password)
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| password | string | The password to validate |
|
||||
|
||||
**Returns**: `{ valid: boolean, error?: string }`
|
||||
```
|
||||
|
||||
Reference the spec for requirements, implementation for details.
|
||||
requires:
|
||||
- implementation
|
||||
|
||||
apply:
|
||||
requires: [tests]
|
||||
tracks: null
|
||||
instruction: |
|
||||
Run tests to see failures. Implement minimal code to pass each test.
|
||||
Refactor while keeping tests green.
|
||||
@@ -1,15 +0,0 @@
|
||||
## Overview
|
||||
|
||||
<!-- Feature overview -->
|
||||
|
||||
## Getting Started
|
||||
|
||||
<!-- Quick start guide -->
|
||||
|
||||
## Examples
|
||||
|
||||
<!-- Code examples -->
|
||||
|
||||
## Reference
|
||||
|
||||
<!-- API reference or additional details -->
|
||||
@@ -1,11 +0,0 @@
|
||||
## Implementation Notes
|
||||
|
||||
<!-- Technical implementation details -->
|
||||
|
||||
## API
|
||||
|
||||
<!-- Public API documentation -->
|
||||
|
||||
## Usage
|
||||
|
||||
<!-- Usage examples -->
|
||||
@@ -1,11 +0,0 @@
|
||||
## Feature: <!-- feature name -->
|
||||
|
||||
<!-- Feature description -->
|
||||
|
||||
## Requirements
|
||||
|
||||
<!-- List of requirements -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- List of acceptance criteria -->
|
||||
@@ -1,11 +0,0 @@
|
||||
## Test Plan
|
||||
|
||||
<!-- Describe the testing strategy -->
|
||||
|
||||
## Test Cases
|
||||
|
||||
### <!-- Test case name -->
|
||||
|
||||
- **Given:** <!-- preconditions -->
|
||||
- **When:** <!-- action -->
|
||||
- **Then:** <!-- expected result -->
|
||||
+2
-9
@@ -14,7 +14,6 @@ import { ValidateCommand } from '../commands/validate.js';
|
||||
import { ShowCommand } from '../commands/show.js';
|
||||
import { CompletionCommand } from '../commands/completion.js';
|
||||
import { registerConfigCommand } from '../commands/config.js';
|
||||
import { registerArtifactWorkflowCommands } from '../commands/artifact-workflow.js';
|
||||
|
||||
const program = new Command();
|
||||
const require = createRequire(import.meta.url);
|
||||
@@ -96,14 +95,11 @@ program
|
||||
.description('List items (changes by default). Use --specs to list specs.')
|
||||
.option('--specs', 'List specs instead of changes')
|
||||
.option('--changes', 'List changes explicitly (default)')
|
||||
.option('--sort <order>', 'Sort order: "recent" (default) or "name"', 'recent')
|
||||
.option('--json', 'Output as JSON (for programmatic use)')
|
||||
.action(async (options?: { specs?: boolean; changes?: boolean; sort?: string; json?: boolean }) => {
|
||||
.action(async (options?: { specs?: boolean; changes?: boolean }) => {
|
||||
try {
|
||||
const listCommand = new ListCommand();
|
||||
const mode: 'changes' | 'specs' = options?.specs ? 'specs' : 'changes';
|
||||
const sort = options?.sort === 'name' ? 'name' : 'recent';
|
||||
await listCommand.execute('.', mode, { sort, json: options?.json });
|
||||
await listCommand.execute('.', mode);
|
||||
} catch (error) {
|
||||
console.log(); // Empty line for spacing
|
||||
ora().fail(`Error: ${(error as Error).message}`);
|
||||
@@ -320,7 +316,4 @@ program
|
||||
}
|
||||
});
|
||||
|
||||
// Register artifact workflow commands (experimental)
|
||||
registerArtifactWorkflowCommands(program);
|
||||
|
||||
program.parse();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
import ora from 'ora';
|
||||
import path from 'path';
|
||||
import { Validator } from '../core/validation/validator.js';
|
||||
import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
|
||||
import { isInteractive } from '../utils/interactive.js';
|
||||
import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
|
||||
import { nearestMatches } from '../utils/match.js';
|
||||
|
||||
@@ -15,7 +15,6 @@ interface ExecuteOptions {
|
||||
strict?: boolean;
|
||||
json?: boolean;
|
||||
noInteractive?: boolean;
|
||||
interactive?: boolean; // Commander sets this to false when --no-interactive is used
|
||||
concurrency?: string;
|
||||
}
|
||||
|
||||
@@ -36,7 +35,7 @@ export class ValidateCommand {
|
||||
await this.runBulkValidation({
|
||||
changes: !!options.all || !!options.changes,
|
||||
specs: !!options.all || !!options.specs,
|
||||
}, { strict: !!options.strict, json: !!options.json, concurrency: options.concurrency, noInteractive: resolveNoInteractive(options) });
|
||||
}, { strict: !!options.strict, json: !!options.json, concurrency: options.concurrency });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,8 +180,8 @@ export class ValidateCommand {
|
||||
bullets.forEach(b => console.error(` ${b}`));
|
||||
}
|
||||
|
||||
private async runBulkValidation(scope: { changes: boolean; specs: boolean }, opts: { strict: boolean; json: boolean; concurrency?: string; noInteractive?: boolean }): Promise<void> {
|
||||
const spinner = !opts.json && !opts.noInteractive ? ora('Validating...').start() : undefined;
|
||||
private async runBulkValidation(scope: { changes: boolean; specs: boolean }, opts: { strict: boolean; json: boolean; concurrency?: string }): Promise<void> {
|
||||
const spinner = !opts.json ? ora('Validating...').start() : undefined;
|
||||
const [changeIds, specIds] = await Promise.all([
|
||||
scope.changes ? getActiveChangeIds() : Promise.resolve<string[]>([]),
|
||||
scope.specs ? getSpecIds() : Promise.resolve<string[]>([]),
|
||||
|
||||
+315
-8
@@ -1,14 +1,21 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { FileSystemUtils } from '../utils/file-system.js';
|
||||
import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
|
||||
import { Validator } from './validation/validator.js';
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
findSpecUpdates,
|
||||
buildUpdatedSpec,
|
||||
writeUpdatedSpec,
|
||||
type SpecUpdate,
|
||||
} from './specs-apply.js';
|
||||
extractRequirementsSection,
|
||||
parseDeltaSpec,
|
||||
normalizeRequirementName,
|
||||
type RequirementBlock,
|
||||
} from './parsers/requirement-blocks.js';
|
||||
|
||||
interface SpecUpdate {
|
||||
source: string;
|
||||
target: string;
|
||||
exists: boolean;
|
||||
}
|
||||
|
||||
export class ArchiveCommand {
|
||||
async execute(
|
||||
@@ -161,7 +168,7 @@ export class ArchiveCommand {
|
||||
console.log('Skipping spec updates (--skip-specs flag provided).');
|
||||
} else {
|
||||
// Find specs to update
|
||||
const specUpdates = await findSpecUpdates(changeDir, mainSpecsDir);
|
||||
const specUpdates = await this.findSpecUpdates(changeDir, mainSpecsDir);
|
||||
|
||||
if (specUpdates.length > 0) {
|
||||
console.log('\nSpecs to update:');
|
||||
@@ -188,7 +195,7 @@ export class ArchiveCommand {
|
||||
const prepared: Array<{ update: SpecUpdate; rebuilt: string; counts: { added: number; modified: number; removed: number; renamed: number } }> = [];
|
||||
try {
|
||||
for (const update of specUpdates) {
|
||||
const built = await buildUpdatedSpec(update, changeName!);
|
||||
const built = await this.buildUpdatedSpec(update, changeName!);
|
||||
prepared.push({ update, rebuilt: built.rebuilt, counts: built.counts });
|
||||
}
|
||||
} catch (err: any) {
|
||||
@@ -213,7 +220,7 @@ export class ArchiveCommand {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await writeUpdatedSpec(p.update, p.rebuilt, p.counts);
|
||||
await this.writeUpdatedSpec(p.update, p.rebuilt, p.counts);
|
||||
totals.added += p.counts.added;
|
||||
totals.modified += p.counts.modified;
|
||||
totals.removed += p.counts.removed;
|
||||
@@ -295,6 +302,306 @@ export class ArchiveCommand {
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: replaced by shared task-progress utilities
|
||||
private async checkIncompleteTasks(_tasksPath: string): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async findSpecUpdates(changeDir: string, mainSpecsDir: string): Promise<SpecUpdate[]> {
|
||||
const updates: SpecUpdate[] = [];
|
||||
const changeSpecsDir = path.join(changeDir, 'specs');
|
||||
|
||||
try {
|
||||
const entries = await fs.readdir(changeSpecsDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
const specFile = path.join(changeSpecsDir, entry.name, 'spec.md');
|
||||
const targetFile = path.join(mainSpecsDir, entry.name, 'spec.md');
|
||||
|
||||
try {
|
||||
await fs.access(specFile);
|
||||
|
||||
// Check if target exists
|
||||
let exists = false;
|
||||
try {
|
||||
await fs.access(targetFile);
|
||||
exists = true;
|
||||
} catch {
|
||||
exists = false;
|
||||
}
|
||||
|
||||
updates.push({
|
||||
source: specFile,
|
||||
target: targetFile,
|
||||
exists
|
||||
});
|
||||
} catch {
|
||||
// Source spec doesn't exist, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// No specs directory in change
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
private async buildUpdatedSpec(update: SpecUpdate, changeName: string): Promise<{ rebuilt: string; counts: { added: number; modified: number; removed: number; renamed: number } }> {
|
||||
// Read change spec content (delta-format expected)
|
||||
const changeContent = await fs.readFile(update.source, 'utf-8');
|
||||
|
||||
// Parse deltas from the change spec file
|
||||
const plan = parseDeltaSpec(changeContent);
|
||||
const specName = path.basename(path.dirname(update.target));
|
||||
|
||||
// Pre-validate duplicates within sections
|
||||
const addedNames = new Set<string>();
|
||||
for (const add of plan.added) {
|
||||
const name = normalizeRequirementName(add.name);
|
||||
if (addedNames.has(name)) {
|
||||
throw new Error(
|
||||
`${specName} validation failed - duplicate requirement in ADDED for header "### Requirement: ${add.name}"`
|
||||
);
|
||||
}
|
||||
addedNames.add(name);
|
||||
}
|
||||
const modifiedNames = new Set<string>();
|
||||
for (const mod of plan.modified) {
|
||||
const name = normalizeRequirementName(mod.name);
|
||||
if (modifiedNames.has(name)) {
|
||||
throw new Error(
|
||||
`${specName} validation failed - duplicate requirement in MODIFIED for header "### Requirement: ${mod.name}"`
|
||||
);
|
||||
}
|
||||
modifiedNames.add(name);
|
||||
}
|
||||
const removedNamesSet = new Set<string>();
|
||||
for (const rem of plan.removed) {
|
||||
const name = normalizeRequirementName(rem);
|
||||
if (removedNamesSet.has(name)) {
|
||||
throw new Error(
|
||||
`${specName} validation failed - duplicate requirement in REMOVED for header "### Requirement: ${rem}"`
|
||||
);
|
||||
}
|
||||
removedNamesSet.add(name);
|
||||
}
|
||||
const renamedFromSet = new Set<string>();
|
||||
const renamedToSet = new Set<string>();
|
||||
for (const { from, to } of plan.renamed) {
|
||||
const fromNorm = normalizeRequirementName(from);
|
||||
const toNorm = normalizeRequirementName(to);
|
||||
if (renamedFromSet.has(fromNorm)) {
|
||||
throw new Error(
|
||||
`${specName} validation failed - duplicate FROM in RENAMED for header "### Requirement: ${from}"`
|
||||
);
|
||||
}
|
||||
if (renamedToSet.has(toNorm)) {
|
||||
throw new Error(
|
||||
`${specName} validation failed - duplicate TO in RENAMED for header "### Requirement: ${to}"`
|
||||
);
|
||||
}
|
||||
renamedFromSet.add(fromNorm);
|
||||
renamedToSet.add(toNorm);
|
||||
}
|
||||
|
||||
// Pre-validate cross-section conflicts
|
||||
const conflicts: Array<{ name: string; a: string; b: string }> = [];
|
||||
for (const n of modifiedNames) {
|
||||
if (removedNamesSet.has(n)) conflicts.push({ name: n, a: 'MODIFIED', b: 'REMOVED' });
|
||||
if (addedNames.has(n)) conflicts.push({ name: n, a: 'MODIFIED', b: 'ADDED' });
|
||||
}
|
||||
for (const n of addedNames) {
|
||||
if (removedNamesSet.has(n)) conflicts.push({ name: n, a: 'ADDED', b: 'REMOVED' });
|
||||
}
|
||||
// Renamed interplay: MODIFIED must reference the NEW header, not FROM
|
||||
for (const { from, to } of plan.renamed) {
|
||||
const fromNorm = normalizeRequirementName(from);
|
||||
const toNorm = normalizeRequirementName(to);
|
||||
if (modifiedNames.has(fromNorm)) {
|
||||
throw new Error(
|
||||
`${specName} validation failed - when a rename exists, MODIFIED must reference the NEW header "### Requirement: ${to}"`
|
||||
);
|
||||
}
|
||||
// Detect ADDED colliding with a RENAMED TO
|
||||
if (addedNames.has(toNorm)) {
|
||||
throw new Error(
|
||||
`${specName} validation failed - RENAMED TO header collides with ADDED for "### Requirement: ${to}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
if (conflicts.length > 0) {
|
||||
const c = conflicts[0];
|
||||
throw new Error(
|
||||
`${specName} validation failed - requirement present in multiple sections (${c.a} and ${c.b}) for header "### Requirement: ${c.name}"`
|
||||
);
|
||||
}
|
||||
const hasAnyDelta = (plan.added.length + plan.modified.length + plan.removed.length + plan.renamed.length) > 0;
|
||||
if (!hasAnyDelta) {
|
||||
throw new Error(
|
||||
`Delta parsing found no operations for ${path.basename(path.dirname(update.source))}. ` +
|
||||
`Provide ADDED/MODIFIED/REMOVED/RENAMED sections in change spec.`
|
||||
);
|
||||
}
|
||||
|
||||
// Load or create base target content
|
||||
let targetContent: string;
|
||||
try {
|
||||
targetContent = await fs.readFile(update.target, 'utf-8');
|
||||
} catch {
|
||||
// Target spec does not exist; only ADDED operations are permitted
|
||||
if (plan.modified.length > 0 || plan.removed.length > 0 || plan.renamed.length > 0) {
|
||||
throw new Error(
|
||||
`${specName}: target spec does not exist; only ADDED requirements are allowed for new specs.`
|
||||
);
|
||||
}
|
||||
targetContent = this.buildSpecSkeleton(specName, changeName);
|
||||
}
|
||||
|
||||
// Extract requirements section and build name->block map
|
||||
const parts = extractRequirementsSection(targetContent);
|
||||
const nameToBlock = new Map<string, RequirementBlock>();
|
||||
for (const block of parts.bodyBlocks) {
|
||||
nameToBlock.set(normalizeRequirementName(block.name), block);
|
||||
}
|
||||
|
||||
// Apply operations in order: RENAMED → REMOVED → MODIFIED → ADDED
|
||||
// RENAMED
|
||||
for (const r of plan.renamed) {
|
||||
const from = normalizeRequirementName(r.from);
|
||||
const to = normalizeRequirementName(r.to);
|
||||
if (!nameToBlock.has(from)) {
|
||||
throw new Error(
|
||||
`${specName} RENAMED failed for header "### Requirement: ${r.from}" - source not found`
|
||||
);
|
||||
}
|
||||
if (nameToBlock.has(to)) {
|
||||
throw new Error(
|
||||
`${specName} RENAMED failed for header "### Requirement: ${r.to}" - target already exists`
|
||||
);
|
||||
}
|
||||
const block = nameToBlock.get(from)!;
|
||||
const newHeader = `### Requirement: ${to}`;
|
||||
const rawLines = block.raw.split('\n');
|
||||
rawLines[0] = newHeader;
|
||||
const renamedBlock: RequirementBlock = {
|
||||
headerLine: newHeader,
|
||||
name: to,
|
||||
raw: rawLines.join('\n'),
|
||||
};
|
||||
nameToBlock.delete(from);
|
||||
nameToBlock.set(to, renamedBlock);
|
||||
}
|
||||
|
||||
// REMOVED
|
||||
for (const name of plan.removed) {
|
||||
const key = normalizeRequirementName(name);
|
||||
if (!nameToBlock.has(key)) {
|
||||
throw new Error(
|
||||
`${specName} REMOVED failed for header "### Requirement: ${name}" - not found`
|
||||
);
|
||||
}
|
||||
nameToBlock.delete(key);
|
||||
}
|
||||
|
||||
// MODIFIED
|
||||
for (const mod of plan.modified) {
|
||||
const key = normalizeRequirementName(mod.name);
|
||||
if (!nameToBlock.has(key)) {
|
||||
throw new Error(
|
||||
`${specName} MODIFIED failed for header "### Requirement: ${mod.name}" - not found`
|
||||
);
|
||||
}
|
||||
// Replace block with provided raw (ensure header line matches key)
|
||||
const modHeaderMatch = mod.raw.split('\n')[0].match(/^###\s*Requirement:\s*(.+)\s*$/);
|
||||
if (!modHeaderMatch || normalizeRequirementName(modHeaderMatch[1]) !== key) {
|
||||
throw new Error(
|
||||
`${specName} MODIFIED failed for header "### Requirement: ${mod.name}" - header mismatch in content`
|
||||
);
|
||||
}
|
||||
nameToBlock.set(key, mod);
|
||||
}
|
||||
|
||||
// ADDED
|
||||
for (const add of plan.added) {
|
||||
const key = normalizeRequirementName(add.name);
|
||||
if (nameToBlock.has(key)) {
|
||||
throw new Error(
|
||||
`${specName} ADDED failed for header "### Requirement: ${add.name}" - already exists`
|
||||
);
|
||||
}
|
||||
nameToBlock.set(key, add);
|
||||
}
|
||||
|
||||
// Duplicates within resulting map are implicitly prevented by key uniqueness.
|
||||
|
||||
// Recompose requirements section preserving original ordering where possible
|
||||
const keptOrder: RequirementBlock[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const block of parts.bodyBlocks) {
|
||||
const key = normalizeRequirementName(block.name);
|
||||
const replacement = nameToBlock.get(key);
|
||||
if (replacement) {
|
||||
keptOrder.push(replacement);
|
||||
seen.add(key);
|
||||
}
|
||||
}
|
||||
// Append any newly added that were not in original order
|
||||
for (const [key, block] of nameToBlock.entries()) {
|
||||
if (!seen.has(key)) {
|
||||
keptOrder.push(block);
|
||||
}
|
||||
}
|
||||
|
||||
const reqBody = [
|
||||
parts.preamble && parts.preamble.trim() ? parts.preamble.trimEnd() : ''
|
||||
]
|
||||
.filter(Boolean)
|
||||
.concat(keptOrder.map(b => b.raw))
|
||||
.join('\n\n')
|
||||
.trimEnd();
|
||||
|
||||
const rebuilt = [
|
||||
parts.before.trimEnd(),
|
||||
parts.headerLine,
|
||||
reqBody,
|
||||
parts.after
|
||||
]
|
||||
.filter((s, idx) => !(idx === 0 && s === ''))
|
||||
.join('\n')
|
||||
.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
return {
|
||||
rebuilt,
|
||||
counts: {
|
||||
added: plan.added.length,
|
||||
modified: plan.modified.length,
|
||||
removed: plan.removed.length,
|
||||
renamed: plan.renamed.length,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async writeUpdatedSpec(update: SpecUpdate, rebuilt: string, counts: { added: number; modified: number; removed: number; renamed: number }): Promise<void> {
|
||||
// Create target directory if needed
|
||||
const targetDir = path.dirname(update.target);
|
||||
await fs.mkdir(targetDir, { recursive: true });
|
||||
await fs.writeFile(update.target, rebuilt);
|
||||
|
||||
const specName = path.basename(path.dirname(update.target));
|
||||
console.log(`Applying changes to openspec/specs/${specName}/spec.md:`);
|
||||
if (counts.added) console.log(` + ${counts.added} added`);
|
||||
if (counts.modified) console.log(` ~ ${counts.modified} modified`);
|
||||
if (counts.removed) console.log(` - ${counts.removed} removed`);
|
||||
if (counts.renamed) console.log(` → ${counts.renamed} renamed`);
|
||||
}
|
||||
|
||||
private buildSpecSkeleton(specFolderName: string, changeName: string): string {
|
||||
const titleBase = specFolderName;
|
||||
return `# ${titleBase} Specification\n\n## Purpose\nTBD - created by archiving change ${changeName}. Update Purpose after archive.\n\n## Requirements\n`;
|
||||
}
|
||||
|
||||
private getArchiveDate(): string {
|
||||
// Returns date in YYYY-MM-DD format
|
||||
return new Date().toISOString().split('T')[0];
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
import type { Artifact, SchemaYaml, CompletedSet, BlockedArtifacts } from './types.js';
|
||||
import { loadSchema, parseSchema } from './schema.js';
|
||||
|
||||
/**
|
||||
* Represents an artifact dependency graph.
|
||||
* Provides methods for querying build order, ready artifacts, and completion status.
|
||||
*/
|
||||
export class ArtifactGraph {
|
||||
private artifacts: Map<string, Artifact>;
|
||||
private schema: SchemaYaml;
|
||||
|
||||
private constructor(schema: SchemaYaml) {
|
||||
this.schema = schema;
|
||||
this.artifacts = new Map(schema.artifacts.map(a => [a.id, a]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ArtifactGraph from a YAML file path.
|
||||
*/
|
||||
static fromYaml(filePath: string): ArtifactGraph {
|
||||
const schema = loadSchema(filePath);
|
||||
return new ArtifactGraph(schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ArtifactGraph from YAML content string.
|
||||
*/
|
||||
static fromYamlContent(yamlContent: string): ArtifactGraph {
|
||||
const schema = parseSchema(yamlContent);
|
||||
return new ArtifactGraph(schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ArtifactGraph from a pre-validated schema object.
|
||||
*/
|
||||
static fromSchema(schema: SchemaYaml): ArtifactGraph {
|
||||
return new ArtifactGraph(schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single artifact by ID.
|
||||
*/
|
||||
getArtifact(id: string): Artifact | undefined {
|
||||
return this.artifacts.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all artifacts in the graph.
|
||||
*/
|
||||
getAllArtifacts(): Artifact[] {
|
||||
return Array.from(this.artifacts.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the schema name.
|
||||
*/
|
||||
getName(): string {
|
||||
return this.schema.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the schema version.
|
||||
*/
|
||||
getVersion(): number {
|
||||
return this.schema.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the topological build order using Kahn's algorithm.
|
||||
* Returns artifact IDs in the order they should be built.
|
||||
*/
|
||||
getBuildOrder(): string[] {
|
||||
const inDegree = new Map<string, number>();
|
||||
const dependents = new Map<string, string[]>();
|
||||
|
||||
// Initialize all artifacts
|
||||
for (const artifact of this.artifacts.values()) {
|
||||
inDegree.set(artifact.id, artifact.requires.length);
|
||||
dependents.set(artifact.id, []);
|
||||
}
|
||||
|
||||
// Build reverse adjacency (who depends on whom)
|
||||
for (const artifact of this.artifacts.values()) {
|
||||
for (const req of artifact.requires) {
|
||||
dependents.get(req)!.push(artifact.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Start with roots (in-degree 0), sorted for determinism
|
||||
const queue = [...this.artifacts.keys()]
|
||||
.filter(id => inDegree.get(id) === 0)
|
||||
.sort();
|
||||
|
||||
const result: string[] = [];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift()!;
|
||||
result.push(current);
|
||||
|
||||
// Collect newly ready artifacts, then sort before adding
|
||||
const newlyReady: string[] = [];
|
||||
for (const dep of dependents.get(current)!) {
|
||||
const newDegree = inDegree.get(dep)! - 1;
|
||||
inDegree.set(dep, newDegree);
|
||||
if (newDegree === 0) {
|
||||
newlyReady.push(dep);
|
||||
}
|
||||
}
|
||||
queue.push(...newlyReady.sort());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets artifacts that are ready to be created (all dependencies completed).
|
||||
*/
|
||||
getNextArtifacts(completed: CompletedSet): string[] {
|
||||
const ready: string[] = [];
|
||||
|
||||
for (const artifact of this.artifacts.values()) {
|
||||
if (completed.has(artifact.id)) {
|
||||
continue; // Already completed
|
||||
}
|
||||
|
||||
const allDepsCompleted = artifact.requires.every(req => completed.has(req));
|
||||
if (allDepsCompleted) {
|
||||
ready.push(artifact.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort for deterministic ordering
|
||||
return ready.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all artifacts in the graph are completed.
|
||||
*/
|
||||
isComplete(completed: CompletedSet): boolean {
|
||||
for (const artifact of this.artifacts.values()) {
|
||||
if (!completed.has(artifact.id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets blocked artifacts and their unmet dependencies.
|
||||
*/
|
||||
getBlocked(completed: CompletedSet): BlockedArtifacts {
|
||||
const blocked: BlockedArtifacts = {};
|
||||
|
||||
for (const artifact of this.artifacts.values()) {
|
||||
if (completed.has(artifact.id)) {
|
||||
continue; // Already completed
|
||||
}
|
||||
|
||||
const unmetDeps = artifact.requires.filter(req => !completed.has(req));
|
||||
if (unmetDeps.length > 0) {
|
||||
blocked[artifact.id] = unmetDeps.sort();
|
||||
}
|
||||
}
|
||||
|
||||
return blocked;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Types
|
||||
export {
|
||||
ArtifactSchema,
|
||||
SchemaYamlSchema,
|
||||
type Artifact,
|
||||
type SchemaYaml,
|
||||
type CompletedSet,
|
||||
type BlockedArtifacts,
|
||||
} from './types.js';
|
||||
|
||||
// Schema loading and validation
|
||||
export { loadSchema, parseSchema, SchemaValidationError } from './schema.js';
|
||||
|
||||
// Graph operations
|
||||
export { ArtifactGraph } from './graph.js';
|
||||
|
||||
// State detection
|
||||
export { detectCompleted } from './state.js';
|
||||
|
||||
// Schema resolution
|
||||
export {
|
||||
resolveSchema,
|
||||
listSchemas,
|
||||
listSchemasWithInfo,
|
||||
getSchemaDir,
|
||||
getPackageSchemasDir,
|
||||
getUserSchemasDir,
|
||||
SchemaLoadError,
|
||||
type SchemaInfo,
|
||||
} from './resolver.js';
|
||||
|
||||
// Instruction loading
|
||||
export {
|
||||
loadTemplate,
|
||||
loadChangeContext,
|
||||
generateInstructions,
|
||||
formatChangeStatus,
|
||||
TemplateLoadError,
|
||||
type ChangeContext,
|
||||
type ArtifactInstructions,
|
||||
type DependencyInfo,
|
||||
type ArtifactStatus,
|
||||
type ChangeStatus,
|
||||
} from './instruction-loader.js';
|
||||
@@ -1,302 +0,0 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { getSchemaDir, resolveSchema } from './resolver.js';
|
||||
import { ArtifactGraph } from './graph.js';
|
||||
import { detectCompleted } from './state.js';
|
||||
import { resolveSchemaForChange } from '../../utils/change-metadata.js';
|
||||
import type { Artifact, CompletedSet } from './types.js';
|
||||
|
||||
/**
|
||||
* Error thrown when loading a template fails.
|
||||
*/
|
||||
export class TemplateLoadError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly templatePath: string
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'TemplateLoadError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change context containing graph, completion state, and metadata.
|
||||
*/
|
||||
export interface ChangeContext {
|
||||
/** The artifact dependency graph */
|
||||
graph: ArtifactGraph;
|
||||
/** Set of completed artifact IDs */
|
||||
completed: CompletedSet;
|
||||
/** Schema name being used */
|
||||
schemaName: string;
|
||||
/** Change name */
|
||||
changeName: string;
|
||||
/** Path to the change directory */
|
||||
changeDir: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriched instructions for creating an artifact.
|
||||
*/
|
||||
export interface ArtifactInstructions {
|
||||
/** Change name */
|
||||
changeName: string;
|
||||
/** Artifact ID */
|
||||
artifactId: string;
|
||||
/** Schema name */
|
||||
schemaName: string;
|
||||
/** Full path to change directory */
|
||||
changeDir: string;
|
||||
/** Output path pattern (e.g., "proposal.md") */
|
||||
outputPath: string;
|
||||
/** Artifact description */
|
||||
description: string;
|
||||
/** Guidance on how to create this artifact (from schema instruction field) */
|
||||
instruction: string | undefined;
|
||||
/** Template content (structure to follow) */
|
||||
template: string;
|
||||
/** Dependencies with completion status and paths */
|
||||
dependencies: DependencyInfo[];
|
||||
/** Artifacts that become available after completing this one */
|
||||
unlocks: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency information including path and description.
|
||||
*/
|
||||
export interface DependencyInfo {
|
||||
/** Artifact ID */
|
||||
id: string;
|
||||
/** Whether the dependency is completed */
|
||||
done: boolean;
|
||||
/** Relative output path of the dependency (e.g., "proposal.md") */
|
||||
path: string;
|
||||
/** Description of the dependency artifact */
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of a single artifact in the workflow.
|
||||
*/
|
||||
export interface ArtifactStatus {
|
||||
/** Artifact ID */
|
||||
id: string;
|
||||
/** Output path pattern */
|
||||
outputPath: string;
|
||||
/** Status: done, ready, or blocked */
|
||||
status: 'done' | 'ready' | 'blocked';
|
||||
/** Missing dependencies (only for blocked) */
|
||||
missingDeps?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted change status.
|
||||
*/
|
||||
export interface ChangeStatus {
|
||||
/** Change name */
|
||||
changeName: string;
|
||||
/** Schema name */
|
||||
schemaName: string;
|
||||
/** Whether all artifacts are complete */
|
||||
isComplete: boolean;
|
||||
/** Artifact IDs required before apply phase (from schema's apply.requires) */
|
||||
applyRequires: string[];
|
||||
/** Status of each artifact */
|
||||
artifacts: ArtifactStatus[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a template from a schema's templates directory.
|
||||
*
|
||||
* @param schemaName - Schema name (e.g., "spec-driven")
|
||||
* @param templatePath - Relative path within the templates directory (e.g., "proposal.md")
|
||||
* @returns The template content
|
||||
* @throws TemplateLoadError if the template cannot be loaded
|
||||
*/
|
||||
export function loadTemplate(schemaName: string, templatePath: string): string {
|
||||
const schemaDir = getSchemaDir(schemaName);
|
||||
if (!schemaDir) {
|
||||
throw new TemplateLoadError(
|
||||
`Schema '${schemaName}' not found`,
|
||||
templatePath
|
||||
);
|
||||
}
|
||||
|
||||
const fullPath = path.join(schemaDir, 'templates', templatePath);
|
||||
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
throw new TemplateLoadError(
|
||||
`Template not found: ${fullPath}`,
|
||||
fullPath
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return fs.readFileSync(fullPath, 'utf-8');
|
||||
} catch (err) {
|
||||
const ioError = err instanceof Error ? err : new Error(String(err));
|
||||
throw new TemplateLoadError(
|
||||
`Failed to read template: ${ioError.message}`,
|
||||
fullPath
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads change context combining graph and completion state.
|
||||
*
|
||||
* Schema resolution order:
|
||||
* 1. Explicit schemaName parameter (if provided)
|
||||
* 2. Schema from .openspec.yaml metadata (if exists in change directory)
|
||||
* 3. Default 'spec-driven'
|
||||
*
|
||||
* @param projectRoot - Project root directory
|
||||
* @param changeName - Change name
|
||||
* @param schemaName - Optional schema name override. If not provided, auto-detected from metadata.
|
||||
* @returns Change context with graph, completed set, and metadata
|
||||
*/
|
||||
export function loadChangeContext(
|
||||
projectRoot: string,
|
||||
changeName: string,
|
||||
schemaName?: string
|
||||
): ChangeContext {
|
||||
const changeDir = path.join(projectRoot, 'openspec', 'changes', changeName);
|
||||
|
||||
// Resolve schema: explicit > metadata > default
|
||||
const resolvedSchemaName = resolveSchemaForChange(changeDir, schemaName);
|
||||
|
||||
const schema = resolveSchema(resolvedSchemaName);
|
||||
const graph = ArtifactGraph.fromSchema(schema);
|
||||
const completed = detectCompleted(graph, changeDir);
|
||||
|
||||
return {
|
||||
graph,
|
||||
completed,
|
||||
schemaName: resolvedSchemaName,
|
||||
changeName,
|
||||
changeDir,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates enriched instructions for creating an artifact.
|
||||
*
|
||||
* @param context - Change context
|
||||
* @param artifactId - Artifact ID to generate instructions for
|
||||
* @returns Enriched artifact instructions
|
||||
* @throws Error if artifact not found
|
||||
*/
|
||||
export function generateInstructions(
|
||||
context: ChangeContext,
|
||||
artifactId: string
|
||||
): ArtifactInstructions {
|
||||
const artifact = context.graph.getArtifact(artifactId);
|
||||
if (!artifact) {
|
||||
throw new Error(`Artifact '${artifactId}' not found in schema '${context.schemaName}'`);
|
||||
}
|
||||
|
||||
const template = loadTemplate(context.schemaName, artifact.template);
|
||||
const dependencies = getDependencyInfo(artifact, context.graph, context.completed);
|
||||
const unlocks = getUnlockedArtifacts(context.graph, artifactId);
|
||||
|
||||
return {
|
||||
changeName: context.changeName,
|
||||
artifactId: artifact.id,
|
||||
schemaName: context.schemaName,
|
||||
changeDir: context.changeDir,
|
||||
outputPath: artifact.generates,
|
||||
description: artifact.description,
|
||||
instruction: artifact.instruction,
|
||||
template,
|
||||
dependencies,
|
||||
unlocks,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets dependency info including paths and descriptions.
|
||||
*/
|
||||
function getDependencyInfo(
|
||||
artifact: Artifact,
|
||||
graph: ArtifactGraph,
|
||||
completed: CompletedSet
|
||||
): DependencyInfo[] {
|
||||
return artifact.requires.map(id => {
|
||||
const depArtifact = graph.getArtifact(id);
|
||||
return {
|
||||
id,
|
||||
done: completed.has(id),
|
||||
path: depArtifact?.generates ?? id,
|
||||
description: depArtifact?.description ?? '',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets artifacts that become available after completing the given artifact.
|
||||
*/
|
||||
function getUnlockedArtifacts(graph: ArtifactGraph, artifactId: string): string[] {
|
||||
const unlocks: string[] = [];
|
||||
|
||||
for (const artifact of graph.getAllArtifacts()) {
|
||||
if (artifact.requires.includes(artifactId)) {
|
||||
unlocks.push(artifact.id);
|
||||
}
|
||||
}
|
||||
|
||||
return unlocks.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the status of all artifacts in a change.
|
||||
*
|
||||
* @param context - Change context
|
||||
* @returns Formatted change status
|
||||
*/
|
||||
export function formatChangeStatus(context: ChangeContext): ChangeStatus {
|
||||
// Load schema to get apply phase configuration
|
||||
const schema = resolveSchema(context.schemaName);
|
||||
const applyRequires = schema.apply?.requires ?? schema.artifacts.map(a => a.id);
|
||||
|
||||
const artifacts = context.graph.getAllArtifacts();
|
||||
const ready = new Set(context.graph.getNextArtifacts(context.completed));
|
||||
const blocked = context.graph.getBlocked(context.completed);
|
||||
|
||||
const artifactStatuses: ArtifactStatus[] = artifacts.map(artifact => {
|
||||
if (context.completed.has(artifact.id)) {
|
||||
return {
|
||||
id: artifact.id,
|
||||
outputPath: artifact.generates,
|
||||
status: 'done' as const,
|
||||
};
|
||||
}
|
||||
|
||||
if (ready.has(artifact.id)) {
|
||||
return {
|
||||
id: artifact.id,
|
||||
outputPath: artifact.generates,
|
||||
status: 'ready' as const,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: artifact.id,
|
||||
outputPath: artifact.generates,
|
||||
status: 'blocked' as const,
|
||||
missingDeps: blocked[artifact.id] ?? [],
|
||||
};
|
||||
});
|
||||
|
||||
// Sort by build order for consistent output
|
||||
const buildOrder = context.graph.getBuildOrder();
|
||||
const orderMap = new Map(buildOrder.map((id, idx) => [id, idx]));
|
||||
artifactStatuses.sort((a, b) => (orderMap.get(a.id) ?? 0) - (orderMap.get(b.id) ?? 0));
|
||||
|
||||
return {
|
||||
changeName: context.changeName,
|
||||
schemaName: context.schemaName,
|
||||
isComplete: context.graph.isComplete(context.completed),
|
||||
applyRequires,
|
||||
artifacts: artifactStatuses,
|
||||
};
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { getGlobalDataDir } from '../global-config.js';
|
||||
import { parseSchema, SchemaValidationError } from './schema.js';
|
||||
import type { SchemaYaml } from './types.js';
|
||||
|
||||
/**
|
||||
* Error thrown when loading a schema fails.
|
||||
*/
|
||||
export class SchemaLoadError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly schemaPath: string,
|
||||
public readonly cause?: Error
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'SchemaLoadError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the package's built-in schemas directory path.
|
||||
* Uses import.meta.url to resolve relative to the current module.
|
||||
*/
|
||||
export function getPackageSchemasDir(): string {
|
||||
const currentFile = fileURLToPath(import.meta.url);
|
||||
// Navigate from dist/core/artifact-graph/ to package root's schemas/
|
||||
return path.join(path.dirname(currentFile), '..', '..', '..', 'schemas');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's schema override directory path.
|
||||
*/
|
||||
export function getUserSchemasDir(): string {
|
||||
return path.join(getGlobalDataDir(), 'schemas');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a schema name to its directory path.
|
||||
*
|
||||
* Resolution order:
|
||||
* 1. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
|
||||
* 2. Package built-in: <package>/schemas/<name>/schema.yaml
|
||||
*
|
||||
* @param name - Schema name (e.g., "spec-driven")
|
||||
* @returns The path to the schema directory, or null if not found
|
||||
*/
|
||||
export function getSchemaDir(name: string): string | null {
|
||||
// 1. Check user override directory
|
||||
const userDir = path.join(getUserSchemasDir(), name);
|
||||
const userSchemaPath = path.join(userDir, 'schema.yaml');
|
||||
if (fs.existsSync(userSchemaPath)) {
|
||||
return userDir;
|
||||
}
|
||||
|
||||
// 2. Check package built-in directory
|
||||
const packageDir = path.join(getPackageSchemasDir(), name);
|
||||
const packageSchemaPath = path.join(packageDir, 'schema.yaml');
|
||||
if (fs.existsSync(packageSchemaPath)) {
|
||||
return packageDir;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a schema name to a SchemaYaml object.
|
||||
*
|
||||
* Resolution order:
|
||||
* 1. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
|
||||
* 2. Package built-in: <package>/schemas/<name>/schema.yaml
|
||||
*
|
||||
* @param name - Schema name (e.g., "spec-driven")
|
||||
* @returns The resolved schema object
|
||||
* @throws Error if schema is not found in any location
|
||||
*/
|
||||
export function resolveSchema(name: string): SchemaYaml {
|
||||
// Normalize name (remove .yaml extension if provided)
|
||||
const normalizedName = name.replace(/\.ya?ml$/, '');
|
||||
|
||||
const schemaDir = getSchemaDir(normalizedName);
|
||||
if (!schemaDir) {
|
||||
const availableSchemas = listSchemas();
|
||||
throw new Error(
|
||||
`Schema '${normalizedName}' not found. Available schemas: ${availableSchemas.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
const schemaPath = path.join(schemaDir, 'schema.yaml');
|
||||
|
||||
// Load and parse the schema
|
||||
let content: string;
|
||||
try {
|
||||
content = fs.readFileSync(schemaPath, 'utf-8');
|
||||
} catch (err) {
|
||||
const ioError = err instanceof Error ? err : new Error(String(err));
|
||||
throw new SchemaLoadError(
|
||||
`Failed to read schema at '${schemaPath}': ${ioError.message}`,
|
||||
schemaPath,
|
||||
ioError
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return parseSchema(content);
|
||||
} catch (err) {
|
||||
if (err instanceof SchemaValidationError) {
|
||||
throw new SchemaLoadError(
|
||||
`Invalid schema at '${schemaPath}': ${err.message}`,
|
||||
schemaPath,
|
||||
err
|
||||
);
|
||||
}
|
||||
const parseError = err instanceof Error ? err : new Error(String(err));
|
||||
throw new SchemaLoadError(
|
||||
`Failed to parse schema at '${schemaPath}': ${parseError.message}`,
|
||||
schemaPath,
|
||||
parseError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all available schema names.
|
||||
* Combines user override and package built-in schemas.
|
||||
*/
|
||||
export function listSchemas(): string[] {
|
||||
const schemas = new Set<string>();
|
||||
|
||||
// Add package built-in schemas
|
||||
const packageDir = getPackageSchemasDir();
|
||||
if (fs.existsSync(packageDir)) {
|
||||
for (const entry of fs.readdirSync(packageDir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
const schemaPath = path.join(packageDir, entry.name, 'schema.yaml');
|
||||
if (fs.existsSync(schemaPath)) {
|
||||
schemas.add(entry.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add user override schemas (may override package schemas)
|
||||
const userDir = getUserSchemasDir();
|
||||
if (fs.existsSync(userDir)) {
|
||||
for (const entry of fs.readdirSync(userDir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
const schemaPath = path.join(userDir, entry.name, 'schema.yaml');
|
||||
if (fs.existsSync(schemaPath)) {
|
||||
schemas.add(entry.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(schemas).sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema info with metadata (name, description, artifacts).
|
||||
*/
|
||||
export interface SchemaInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
artifacts: string[];
|
||||
source: 'package' | 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all available schemas with their descriptions and artifact lists.
|
||||
* Useful for agent skills to present schema selection to users.
|
||||
*/
|
||||
export function listSchemasWithInfo(): SchemaInfo[] {
|
||||
const schemas: SchemaInfo[] = [];
|
||||
const seenNames = new Set<string>();
|
||||
|
||||
// Add user override schemas first (they take precedence)
|
||||
const userDir = getUserSchemasDir();
|
||||
if (fs.existsSync(userDir)) {
|
||||
for (const entry of fs.readdirSync(userDir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
const schemaPath = path.join(userDir, entry.name, 'schema.yaml');
|
||||
if (fs.existsSync(schemaPath)) {
|
||||
try {
|
||||
const schema = parseSchema(fs.readFileSync(schemaPath, 'utf-8'));
|
||||
schemas.push({
|
||||
name: entry.name,
|
||||
description: schema.description || '',
|
||||
artifacts: schema.artifacts.map((a) => a.id),
|
||||
source: 'user',
|
||||
});
|
||||
seenNames.add(entry.name);
|
||||
} catch {
|
||||
// Skip invalid schemas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add package built-in schemas (if not overridden)
|
||||
const packageDir = getPackageSchemasDir();
|
||||
if (fs.existsSync(packageDir)) {
|
||||
for (const entry of fs.readdirSync(packageDir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory() && !seenNames.has(entry.name)) {
|
||||
const schemaPath = path.join(packageDir, entry.name, 'schema.yaml');
|
||||
if (fs.existsSync(schemaPath)) {
|
||||
try {
|
||||
const schema = parseSchema(fs.readFileSync(schemaPath, 'utf-8'));
|
||||
schemas.push({
|
||||
name: entry.name,
|
||||
description: schema.description || '',
|
||||
artifacts: schema.artifacts.map((a) => a.id),
|
||||
source: 'package',
|
||||
});
|
||||
} catch {
|
||||
// Skip invalid schemas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return schemas.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
import * as fs from 'node:fs';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
import { SchemaYamlSchema, type SchemaYaml, type Artifact } from './types.js';
|
||||
|
||||
export class SchemaValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'SchemaValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and validates an artifact schema from a YAML file.
|
||||
*/
|
||||
export function loadSchema(filePath: string): SchemaYaml {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
return parseSchema(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and validates an artifact schema from YAML content.
|
||||
*/
|
||||
export function parseSchema(yamlContent: string): SchemaYaml {
|
||||
const parsed = parseYaml(yamlContent);
|
||||
|
||||
// Validate with Zod
|
||||
const result = SchemaYamlSchema.safeParse(parsed);
|
||||
if (!result.success) {
|
||||
const errors = result.error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
|
||||
throw new SchemaValidationError(`Invalid schema: ${errors}`);
|
||||
}
|
||||
|
||||
const schema = result.data;
|
||||
|
||||
// Check for duplicate artifact IDs
|
||||
validateNoDuplicateIds(schema.artifacts);
|
||||
|
||||
// Check that all requires references are valid
|
||||
validateRequiresReferences(schema.artifacts);
|
||||
|
||||
// Check for cycles
|
||||
validateNoCycles(schema.artifacts);
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that there are no duplicate artifact IDs.
|
||||
*/
|
||||
function validateNoDuplicateIds(artifacts: Artifact[]): void {
|
||||
const seen = new Set<string>();
|
||||
for (const artifact of artifacts) {
|
||||
if (seen.has(artifact.id)) {
|
||||
throw new SchemaValidationError(`Duplicate artifact ID: ${artifact.id}`);
|
||||
}
|
||||
seen.add(artifact.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that all `requires` references point to valid artifact IDs.
|
||||
*/
|
||||
function validateRequiresReferences(artifacts: Artifact[]): void {
|
||||
const validIds = new Set(artifacts.map(a => a.id));
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
for (const req of artifact.requires) {
|
||||
if (!validIds.has(req)) {
|
||||
throw new SchemaValidationError(
|
||||
`Invalid dependency reference in artifact '${artifact.id}': '${req}' does not exist`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that there are no cyclic dependencies.
|
||||
* Uses DFS to detect cycles and reports the full cycle path.
|
||||
*/
|
||||
function validateNoCycles(artifacts: Artifact[]): void {
|
||||
const artifactMap = new Map(artifacts.map(a => [a.id, a]));
|
||||
const visited = new Set<string>();
|
||||
const inStack = new Set<string>();
|
||||
const parent = new Map<string, string>();
|
||||
|
||||
function dfs(id: string): string | null {
|
||||
visited.add(id);
|
||||
inStack.add(id);
|
||||
|
||||
const artifact = artifactMap.get(id);
|
||||
if (!artifact) return null;
|
||||
|
||||
for (const dep of artifact.requires) {
|
||||
if (!visited.has(dep)) {
|
||||
parent.set(dep, id);
|
||||
const cycle = dfs(dep);
|
||||
if (cycle) return cycle;
|
||||
} else if (inStack.has(dep)) {
|
||||
// Found a cycle - reconstruct the path
|
||||
const cyclePath = [dep];
|
||||
let current = id;
|
||||
while (current !== dep) {
|
||||
cyclePath.unshift(current);
|
||||
current = parent.get(current)!;
|
||||
}
|
||||
cyclePath.unshift(dep);
|
||||
return cyclePath.join(' → ');
|
||||
}
|
||||
}
|
||||
|
||||
inStack.delete(id);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (!visited.has(artifact.id)) {
|
||||
const cycle = dfs(artifact.id);
|
||||
if (cycle) {
|
||||
throw new SchemaValidationError(`Cyclic dependency detected: ${cycle}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import fg from 'fast-glob';
|
||||
import type { CompletedSet } from './types.js';
|
||||
import type { ArtifactGraph } from './graph.js';
|
||||
import { FileSystemUtils } from '../../utils/file-system.js';
|
||||
|
||||
/**
|
||||
* Detects which artifacts are completed by checking file existence in the change directory.
|
||||
* Returns a Set of completed artifact IDs.
|
||||
*
|
||||
* @param graph - The artifact graph to check
|
||||
* @param changeDir - The change directory to scan for files
|
||||
* @returns Set of artifact IDs whose generated files exist
|
||||
*/
|
||||
export function detectCompleted(graph: ArtifactGraph, changeDir: string): CompletedSet {
|
||||
const completed = new Set<string>();
|
||||
|
||||
// Handle missing change directory gracefully
|
||||
if (!fs.existsSync(changeDir)) {
|
||||
return completed;
|
||||
}
|
||||
|
||||
for (const artifact of graph.getAllArtifacts()) {
|
||||
if (isArtifactComplete(artifact.generates, changeDir)) {
|
||||
completed.add(artifact.id);
|
||||
}
|
||||
}
|
||||
|
||||
return completed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an artifact is complete by checking if its generated file(s) exist.
|
||||
* Supports both simple paths and glob patterns.
|
||||
*/
|
||||
function isArtifactComplete(generates: string, changeDir: string): boolean {
|
||||
const fullPattern = path.join(changeDir, generates);
|
||||
|
||||
// Check if it's a glob pattern
|
||||
if (isGlobPattern(generates)) {
|
||||
return hasGlobMatches(fullPattern);
|
||||
}
|
||||
|
||||
// Simple file path - check if file exists
|
||||
return fs.existsSync(fullPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a path contains glob pattern characters.
|
||||
*/
|
||||
function isGlobPattern(pattern: string): boolean {
|
||||
return pattern.includes('*') || pattern.includes('?') || pattern.includes('[');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a glob pattern has any matches.
|
||||
* Normalizes Windows backslashes to forward slashes for cross-platform glob compatibility.
|
||||
*/
|
||||
function hasGlobMatches(pattern: string): boolean {
|
||||
const normalizedPattern = FileSystemUtils.toPosixPath(pattern);
|
||||
const matches = fg.sync(normalizedPattern, { onlyFiles: true });
|
||||
return matches.length > 0;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// Artifact definition schema
|
||||
export const ArtifactSchema = z.object({
|
||||
id: z.string().min(1, { error: 'Artifact ID is required' }),
|
||||
generates: z.string().min(1, { error: 'generates field is required' }),
|
||||
description: z.string(),
|
||||
template: z.string().min(1, { error: 'template field is required' }),
|
||||
instruction: z.string().optional(),
|
||||
requires: z.array(z.string()).default([]),
|
||||
});
|
||||
|
||||
// Apply phase configuration for schema-aware apply instructions
|
||||
export const ApplyPhaseSchema = z.object({
|
||||
// Artifact IDs that must exist before apply is available
|
||||
requires: z.array(z.string()).min(1, { error: 'At least one required artifact' }),
|
||||
// Path to file with checkboxes for progress (relative to change dir), or null if no tracking
|
||||
tracks: z.string().nullable().optional(),
|
||||
// Custom guidance for the apply phase
|
||||
instruction: z.string().optional(),
|
||||
});
|
||||
|
||||
// Full schema YAML structure
|
||||
export const SchemaYamlSchema = z.object({
|
||||
name: z.string().min(1, { error: 'Schema name is required' }),
|
||||
version: z.number().int().positive({ error: 'Version must be a positive integer' }),
|
||||
description: z.string().optional(),
|
||||
artifacts: z.array(ArtifactSchema).min(1, { error: 'At least one artifact required' }),
|
||||
// Optional apply phase configuration (for schema-aware apply instructions)
|
||||
apply: ApplyPhaseSchema.optional(),
|
||||
});
|
||||
|
||||
// Derived TypeScript types
|
||||
export type Artifact = z.infer<typeof ArtifactSchema>;
|
||||
export type ApplyPhase = z.infer<typeof ApplyPhaseSchema>;
|
||||
export type SchemaYaml = z.infer<typeof SchemaYamlSchema>;
|
||||
|
||||
// Per-change metadata schema
|
||||
// Note: schema field is validated at parse time against available schemas
|
||||
// using a lazy import to avoid circular dependencies
|
||||
export const ChangeMetadataSchema = z.object({
|
||||
// Required: which workflow schema this change uses
|
||||
schema: z.string().min(1, { message: 'schema is required' }),
|
||||
|
||||
// Optional: creation timestamp (ISO date string)
|
||||
created: z
|
||||
.string()
|
||||
.regex(/^\d{4}-\d{2}-\d{2}$/, {
|
||||
message: 'created must be YYYY-MM-DD format',
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type ChangeMetadata = z.infer<typeof ChangeMetadataSchema>;
|
||||
|
||||
// Runtime state types (not Zod - internal only)
|
||||
|
||||
// Slice 1: Simple completion tracking via filesystem
|
||||
export type CompletedSet = Set<string>;
|
||||
|
||||
// Return type for blocked query
|
||||
export interface BlockedArtifacts {
|
||||
[artifactId: string]: string[];
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user