Compare commits

...
Author SHA1 Message Date
Tabish Bidiwale 57216a7824 refactor(test): use vitest globalSetup for build instead of per-test builds 2025-09-07 02:16:17 +10:00
Tabish Bidiwale 6e210cf084 fix(test): ensure dist exists before spawning CLI subprocesses 2025-09-07 02:13:21 +10:00
Tabish Bidiwale 5376030421 fix(ci): simplify to single Node version for faster CI 2025-09-07 01:56:00 +10:00
Tabish Bidiwale 7d735eb2d8 fix(ci): ensure build runs before tests in workflows 2025-09-07 01:45:11 +10:00
Tabish Bidiwale 4d55e9ac6d chore(ci): use NODE_AUTH_TOKEN auth, add debug, build before tests 2025-09-07 01:23:32 +10:00
Tabish Bidiwale 9d674b22a3 Fix provenance 2025-09-07 01:13:23 +10:00
Tabish Bidiwale 96458ced1f Update actions workflow 2025-09-07 01:01:55 +10:00
Tabish Bidiwale 3d8f2a5974 update workflow 2025-09-07 00:31:37 +10:00
Tabish Bidiwale 006676c973 chore(test): clarify vitest worker note and newline 2025-09-06 23:27:49 +10:00
Tabish Bidiwale 63f45c0fcf test(commands): isolate change command tests via temp fixtures 2025-09-06 23:27:42 +10:00
Tabish Bidiwale 522126a6ee fix(utils): harden item discovery for determinism 2025-09-06 23:27:33 +10:00
Tabish Bidiwale 23b8030494 fix(change): only list active changes with proposal.md 2025-09-06 23:26:51 +10:00
Tabish Bidiwale f70df96656 docs(changes): add tasks.md for improve-deterministic-tests 2025-09-06 23:20:03 +10:00
Tabish Bidiwale df12368f11 chore(changes): remove obsolete cli-list spec 2025-09-06 21:01:19 +10:00
Tabish Bidiwale acd1ca28f3 docs(changes): add deterministic tests proposal 2025-09-06 21:01:19 +10:00
Tabish Bidiwale aedf4a34af docs(release): add 0.1.0 notes 2025-09-06 21:01:19 +10:00
Tabish Bidiwale f0c52ac7e8 Merge pull request #54 from Fission-AI/changeset-release/main
chore(release): version packages
2025-09-06 14:56:28 +10:00
github-actions[bot] f933e9b144 Version Packages 2025-09-06 04:43:55 +00:00
Tabish Bidiwale 24b4866426 chore(changeset): seed release notes 2025-09-06 14:35:50 +10:00
Tabish Bidiwale b7899602b4 chore(ci): add release workflows 2025-09-06 14:32:32 +10:00
Tabish Bidiwale b66d914198 chore(changesets): add config and script 2025-09-06 02:52:02 +10:00
Tabish Bidiwale 9926103505 chore(pkg): scope to @fission-ai and set public 2025-09-06 02:34:11 +10:00
Tabish Bidiwale 873e45a996 Merge pull request #53 from Fission-AI/prepare-publish
build: prepare for package publish
2025-09-06 02:28:48 +10:00
Tabish Bidiwale 573afa0c65 prepare for package publish 2025-09-06 02:22:38 +10:00
Tabish Bidiwale 665d740adb Remove retrospective doc 2025-09-01 11:34:23 +10:00
Tabish Bidiwale c35dd38567 Test cursor rules 2025-08-27 21:20:37 +10:00
Tabish Bidiwale aa9e49612b Merge pull request #51 from Fission-AI/updating-agent-instructions
feat: streamline OpenSpec agent instructions by 48%
2025-08-27 20:59:31 +10:00
31 changed files with 1595 additions and 481 deletions
+6
View File
@@ -0,0 +1,6 @@
This directory is managed by Changesets.
- Add a changeset locally with `pnpm changeset`.
- The CI "Release (prepare)" workflow opens/updates a Version Packages PR.
- Publishing happens from a GitHub Release via the "Publish to npm" workflow.
+12
View File
@@ -0,0 +1,12 @@
{
"$schema": "https://unpkg.com/@changesets/config/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
+302
View File
@@ -0,0 +1,302 @@
---
description: OpenSpec conventions and workflow for Cursor agents working in this repo
alwaysApply: false
---
# OpenSpec Rules for Cursor Agents
Instructions for AI coding assistants using OpenSpec for spec-driven development.
## Three-Stage Workflow
### Stage 1: Creating Changes
Create proposal when you need to:
- Add features or functionality
- Make breaking changes (API, schema)
- Change architecture or patterns
- Optimize performance (changes behavior)
- Update security patterns
Skip proposal for:
- Bug fixes (restore intended behavior)
- Typos, formatting, comments
- Dependency updates (non-breaking)
- Configuration changes
- Tests for existing behavior
### Stage 2: Implementing Changes
1. **Read proposal.md** - Understand what's being built
2. **Read design.md** (if exists) - Review technical decisions
3. **Read tasks.md** - Get implementation checklist
4. **Implement tasks sequentially** - Complete in order
5. **Mark complete immediately** - Update `- [x]` after each task
### Stage 3: Archiving Changes
After deployment, create separate PR to:
- Move `changes/[name]/` → `changes/archive/YYYY-MM-DD-[name]/`
- Update `specs/` if capabilities changed
- Use `openspec archive [change] --skip-specs` for tooling-only changes
## Before Any Task
**Context Checklist:**
- [ ] Read relevant specs in `specs/[capability]/spec.md`
- [ ] Check pending changes in `changes/` for conflicts
- [ ] Read `openspec/project.md` for conventions
- [ ] Run `openspec list` to see active changes
- [ ] Run `openspec list --specs` to see existing capabilities
**Before Creating Specs:**
- Always check if capability already exists
- Prefer modifying existing specs over creating duplicates
- Use `openspec show [spec]` to review current state
## Quick Start
### CLI Commands
```bash
# Essential commands
openspec list # List active changes
openspec list --specs # List specifications
openspec show [item] # Display change or spec
openspec diff [change] # Show spec differences
openspec validate [item] # Validate changes or specs
openspec archive [change] # Archive after deployment
# Project management
openspec init [path] # Initialize OpenSpec
openspec update [path] # Update instruction files
# Interactive mode
openspec show # Prompts for selection
openspec validate # Bulk validation mode
# Debugging
openspec show [change] --json --deltas-only
openspec validate [change] --strict
```
### Command Flags
- `--json` - Machine-readable output
- `--type change|spec` - Disambiguate items
- `--strict` - Comprehensive validation
- `--no-interactive` - Disable prompts
- `--skip-specs` - Archive without spec updates
## Directory Structure
```
openspec/
├── project.md # Project conventions
├── specs/ # Current truth - what IS built
│ └── [capability]/ # Single focused capability
│ ├── spec.md # Requirements and scenarios
│ └── design.md # Technical patterns
├── changes/ # Proposals - what SHOULD change
│ ├── [change-name]/
│ │ ├── proposal.md # Why, what, impact
│ │ ├── tasks.md # Implementation checklist
│ │ ├── design.md # Technical decisions (optional)
│ │ └── specs/ # Delta changes
│ │ └── [capability]/
│ │ └── spec.md # ADDED/MODIFIED/REMOVED
│ └── archive/ # Completed changes
```
## Creating Change Proposals
### Decision Tree
```
New request?
├─ Bug fix restoring spec behavior? → Fix directly
├─ Typo/format/comment? → Fix directly
├─ New feature/capability? → Create proposal
├─ Breaking change? → Create proposal
├─ Architecture change? → Create proposal
└─ Unclear? → Create proposal (safer)
```
### Proposal Structure
1. **Create directory:** `changes/[descriptive-name]/`
2. **Write proposal.md:**
```markdown
## Why
[1-2 sentences on problem/opportunity]
## What Changes
- [Bullet list of changes]
- [Mark breaking changes with **BREAKING**]
## Impact
- Affected specs: [list capabilities]
- Affected code: [key files/systems]
```
3. **Create spec deltas:** `specs/[capability]/spec.md`
```markdown
## ADDED Requirements
### Requirement: New Feature
The system SHALL provide...
#### Scenario: Success case
- **WHEN** user performs action
- **THEN** expected result
## MODIFIED Requirements
### Requirement: Existing Feature
[Complete modified requirement]
## REMOVED Requirements
### Requirement: Old Feature
**Reason**: [Why removing]
**Migration**: [How to handle]
```
4. **Create tasks.md:**
```markdown
## 1. Implementation
- [ ] 1.1 Create database schema
- [ ] 1.2 Implement API endpoint
- [ ] 1.3 Add frontend component
- [ ] 1.4 Write tests
```
## Spec File Format
### Critical: Scenario Formatting
**CORRECT** (use #### headers):
```markdown
#### Scenario: User login success
- **WHEN** valid credentials provided
- **THEN** return JWT token
```
**WRONG** (don't use bullets or bold):
```markdown
- **Scenario: User login** ❌
**Scenario**: User login ❌
### Scenario: User login ❌
```
Every requirement MUST have at least one scenario.
### Delta Operations
- `## ADDED Requirements` - New capabilities
- `## MODIFIED Requirements` - Changed behavior
- `## REMOVED Requirements` - Deprecated features
- `## RENAMED Requirements` - Name changes
Headers matched with `trim(header)` - whitespace ignored.
## Troubleshooting
### Common Errors
**"Change must have at least one delta"**
- Check `changes/[name]/specs/` exists with .md files
- Verify files have operation prefixes (## ADDED Requirements)
**"Requirement must have at least one scenario"**
- Check scenarios use `#### Scenario:` format (4 hashtags)
- Don't use bullet points or bold for scenario headers
**Silent scenario parsing failures**
- Exact format required: `#### Scenario: Name`
- Debug with: `openspec show [change] --json --deltas-only`
### Validation Tips
```bash
# Always use strict mode for comprehensive checks
openspec validate [change] --strict
# Debug delta parsing
openspec show [change] --json | jq '.deltas'
# Check specific requirement
openspec show [spec] --json -r 1
```
## Best Practices
### Simplicity First
- Default to <100 lines of new code
- Single-file implementations until proven insufficient
- Avoid frameworks without clear justification
- Choose boring, proven patterns
### Complexity Triggers
Only add complexity with:
- Performance data showing current solution too slow
- Concrete scale requirements (>1000 users, >100MB data)
- Multiple proven use cases requiring abstraction
### Clear References
- Use `file.ts:42` format for code locations
- Reference specs as `specs/auth/spec.md`
- Link related changes and PRs
### Capability Naming
- Use verb-noun: `user-auth`, `payment-capture`
- Single purpose per capability
- 10-minute understandability rule
- Split if description needs "AND"
## Tool Selection Guide
| Task | Tool | Why |
|------|------|-----|
| Find files by pattern | Glob | Fast pattern matching |
| Search code content | Grep | Optimized regex search |
| Read specific files | Read | Direct file access |
| Explore unknown scope | Task | Multi-step investigation |
## Error Recovery
### Change Conflicts
1. Run `openspec list` to see active changes
2. Check for overlapping specs
3. Coordinate with change owners
4. Consider combining proposals
### Validation Failures
1. Run with `--strict` flag
2. Check JSON output for details
3. Verify spec file format
4. Ensure scenarios properly formatted
### Missing Context
1. Read project.md first
2. Check related specs
3. Review recent archives
4. Ask for clarification
## Quick Reference
### Stage Indicators
- `changes/` - Proposed, not yet built
- `specs/` - Built and deployed
- `archive/` - Completed changes
### File Purposes
- `proposal.md` - Why and what
- `tasks.md` - Implementation steps
- `design.md` - Technical decisions
- `spec.md` - Requirements and behavior
### CLI Essentials
```bash
openspec list # What's in progress?
openspec show [item] # View details
openspec diff [change] # What's changing?
openspec validate --strict # Is it correct?
openspec archive [change] # Mark complete
```
Remember: Specs are truth. Changes are proposals. Keep them in sync.
+141
View File
@@ -0,0 +1,141 @@
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build project
run: pnpm run build
- name: Run tests
run: pnpm test
- name: Upload test coverage
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
retention-days: 7
lint:
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build project
run: pnpm run build
- name: Type check
run: pnpm exec tsc --noEmit
- name: Check for build artifacts
run: |
if [ ! -d "dist" ]; then
echo "Error: dist directory not found after build"
exit 1
fi
if [ ! -f "dist/cli/index.js" ]; then
echo "Error: CLI entry point not found"
exit 1
fi
validate-changesets:
name: Validate Changesets
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Validate changesets
run: |
if command -v changeset &> /dev/null; then
pnpm exec changeset status --since=origin/main
else
echo "Changesets not configured, skipping validation"
fi
required-checks:
name: All checks passed
runs-on: ubuntu-latest
needs: [test, lint]
if: always()
steps:
- name: Verify all checks passed
run: |
if [[ "${{ needs.test.result }}" != "success" ]]; then
echo "Test job failed"
exit 1
fi
if [[ "${{ needs.lint.result }}" != "success" ]]; then
echo "Lint job failed"
exit 1
fi
echo "All required checks passed!"
+37
View File
@@ -0,0 +1,37 @@
name: Release (prepare)
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
jobs:
prepare:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
# Opens/updates the Version Packages PR; no publishing here
- name: Create/Update Version PR
uses: changesets/action@v1
with:
title: 'chore(release): version packages'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+72
View File
@@ -0,0 +1,72 @@
name: Publish to npm
on:
release:
types: [published]
workflow_dispatch: {}
permissions:
contents: read
id-token: write
concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
scope: '@fission-ai'
always-auth: true
- run: pnpm install --frozen-lockfile
- name: Build project
run: pnpm run build
- name: Ensure running from a tag
run: |
if [[ "$GITHUB_REF" != refs/tags/* ]]; then
echo "This workflow must run from a tag (got: $GITHUB_REF)";
exit 1;
fi
- name: Verify release tag matches package.json
run: |
TAG="${GITHUB_REF_NAME#v}"
PKG_VERSION=$(node -p "require('./package.json').version")
if [ "$TAG" != "$PKG_VERSION" ]; then
echo "Tag v$TAG does not match package.json $PKG_VERSION"; exit 1
fi
- name: Debug npm auth and context
run: |
test -n "$NODE_AUTH_TOKEN" || (echo "NODE_AUTH_TOKEN is missing" && exit 1)
echo "NODE_AUTH_TOKEN present"
npm --version
pnpm --version
node --version
npm config get registry
npm whoami
npm ping
- run: pnpm test
- name: Publish
run: pnpm publish --access public --provenance --no-git-checks --tag next
View File
+4
View File
@@ -0,0 +1,4 @@
### Minor Changes
- 24b4866: Initial release
+40
View File
@@ -0,0 +1,40 @@
<!-- OPENSPEC:START -->
# OpenSpec Project
This document provides instructions for AI coding assistants on how to use OpenSpec conventions for spec-driven development. Follow these rules precisely when working on OpenSpec-enabled projects.
This project uses OpenSpec for spec-driven development. Specifications are the source of truth.
See @openspec/AGENTS.md for detailed conventions and guidelines.
<!-- OPENSPEC:END -->
## Complexity Management
**Default to minimal solutions:**
- Propose <100 lines of new code for features
- Prefer single-file implementations until proven insufficient
- Avoid frameworks, abstractions, and optimizations without clear justification
- Choose boring, well-understood patterns over novel approaches
**Question requests for complexity:**
- Caching? → Ask for performance data and targets
- New framework? → Suggest plain code first
- Extra layers? → Start with the thinnest viable design
**Justify complexity with data:**
- Performance metrics showing current solution is too slow
- Concrete scale requirements (e.g., >1000 users, >100MB data)
- Multiple proven use cases requiring an abstraction
## Package Manager
Always use pnpm (NOT npm or yarn) for all Node.js package management:
- Install dependencies: `pnpm install`
- Add packages: `pnpm add [package]`
- Run scripts: `pnpm run [script]`
## Git Commits
Use conventional commits with these rules:
- Format: `type(scope): subject` (e.g., `fix: resolve auth error`, `feat(api): add user endpoint`)
- Keep commit messages to ONE line only - no body or footer
- Common types: feat, fix, docs, style, refactor, test, chore
- Never add co-authorship lines or attribution
+7
View File
@@ -0,0 +1,7 @@
# @fission-ai/openspec
## 0.1.0
### Minor Changes
- 24b4866: Initial release
-343
View File
@@ -1,343 +0,0 @@
# Comprehensive Retrospective: Creating an OpenSpec Change Proposal
## Executive Summary
This document consolidates learnings from creating the `bulk-validation-interactive-selection` change proposal for OpenSpec. The process revealed critical gaps in documentation, unhelpful error messages, and areas where the system could be more user-friendly. While OpenSpec's core functionality works correctly, the user experience for creating changes needs significant improvement.
## Table of Contents
1. [Errors Encountered](#errors-encountered)
2. [System Issues vs User Errors](#system-issues-vs-user-errors)
3. [Documentation Gaps Analysis](#documentation-gaps-analysis)
4. [Key Learnings](#key-learnings)
5. [Recommendations](#recommendations)
6. [Conclusion](#conclusion)
---
## Errors Encountered
### Error 1: Misunderstanding Delta Structure
**Error Message:** `✗ [ERROR] deltas: Change must have at least one delta`
**What Happened:**
Initially attempted to define deltas directly in the `proposal.md` file using markdown sections like:
```markdown
### Delta: Add validate-all command
**Type**: Feature addition
**Effort**: Small (< 100 lines)
```
**Root Cause:**
Fundamental misunderstanding of how OpenSpec processes deltas. Deltas are derived from spec files in the change's `specs/` directory, not from the proposal itself.
**Discovery Process:**
- Examined `ChangeParser` class in `/src/core/parsers/change-parser.ts`
- Found that `parseDeltaSpecs()` method looks for spec files in `specs/` subdirectory
- Learned that deltas are extracted by comparing spec files against existing specs
### Error 2: Missing Operation Prefix in Section Headers
**Error Message:** `✗ [ERROR] deltas: Change must have at least one delta`
**What Happened:**
Created new spec files with standard `## Requirements` headers instead of operation-prefixed headers.
**Root Cause:**
Failed to understand that ALL spec files in a change need operation prefixes (`ADDED`, `MODIFIED`, etc.) in their section headers, regardless of whether they're new specs or modifications.
**Discovery Process:**
- Created new specs with `## Requirements` → No deltas detected
- Changed to `## ADDED Requirements` → Deltas detected successfully!
- Realized creating new specs works perfectly fine once properly formatted
**Important Clarification:**
OpenSpec fully supports creating new specs. They appear as ADDED operations in the deltas. My initial analysis incorrectly suggested this was a limitation, but it was actually just a formatting issue.
### Error 3: Improper Scenario Formatting
**Error Message:**
```
✗ [ERROR] deltas.0.requirements.0.scenarios: Requirement must have at least one scenario
✗ [ERROR] deltas.1.requirements.0.scenarios: Requirement must have at least one scenario
```
**What Happened:**
Formatted scenarios as bullet lists under a bold "Scenarios:" label:
```markdown
**Scenarios:**
- **WHEN** executing command
- **THEN** expected behavior
```
**Root Cause:**
OpenSpec's parser expects scenarios to be defined as level 4 headers (`####`) with specific formatting:
```markdown
#### Scenario: Descriptive scenario name
- **WHEN** executing command
- **THEN** expected behavior
```
**Discovery Process:**
- Checked parsed JSON output: `npx openspec change show bulk-validation-interactive-selection --json`
- Saw `"scenarios": []` empty array despite having scenario content
- Examined working spec files and found the `#### Scenario:` header pattern
### Error 4: File Path Confusion
**Initial Confusion:**
Wasn't clear whether to create specs that would become part of the main `openspec/specs/` or just define them in the change.
**Resolution:**
Learned that changes can:
1. Create new specs (they start in `changes/{change-name}/specs/` and move to `openspec/specs/` when archived)
2. Modify existing specs (by creating a spec file with the same name as one in `openspec/specs/`)
3. The validation system detects both patterns and creates appropriate deltas
---
## System Issues vs User Errors
### System Issues / Bugs
#### 1. Unhelpful Error Messages ⚠️
**Issue:** `✗ [ERROR] deltas: Change must have at least one delta`
**Why This Is a System Problem:**
- Error message provides no guidance on HOW to create deltas
- Doesn't mention that deltas come from `specs/` subdirectory
- Doesn't explain the required section headers
- A better error would be: "No deltas found. Ensure your change has a specs/ directory with .md files containing sections like '## ADDED Requirements'"
#### 2. Silent Scenario Parsing Failures ⚠️
**Issue:** When scenarios were formatted incorrectly, they were silently ignored
**Why This Is a System Problem:**
- Parser silently returns empty scenarios array instead of warning
- No validation error explaining the format issue
- User gets "Requirement must have at least one scenario" without knowing their scenarios exist but aren't parsed
**Evidence:**
```json
{
"text": "The CLI SHALL provide a top-level `show` command with interactive selection.",
"scenarios": [] // Silent failure - scenarios existed but weren't parsed
}
```
#### 3. No Validation for Proposal Structure During Creation
**Issue:** System allows creating invalid proposals without early feedback
**Why This Is a System Problem:**
- No scaffolding or template commands
- No incremental validation as you build
- Must fully create the change before discovering structural issues
### User Errors (My Mistakes)
#### 1. Trying to Define Deltas in Proposal.md
- Incorrectly assumed deltas could be inline in the proposal
- System correctly expects deltas in separate spec files
#### 2. Using Wrong Section Headers
- Used `## Requirements` instead of `## ADDED Requirements`
- Convention is documented in existing changes, but I didn't examine carefully
#### 3. Wrong Scenario Format
- Used bullet lists instead of `#### Scenario:` headers
- Made assumptions instead of checking existing patterns
### Gray Areas
1. **Documentation gaps** - While examples exist, there's no comprehensive "How to Create a Change" guide
2. **Lack of tooling** - No scaffolding commands to create properly structured changes
---
## Documentation Gaps Analysis
### Critical Gaps in openspec/README.md
#### 1. Scenario Format - COMPLETELY MISSING ⚠️
**What README Shows:** No scenario examples at all
**What's Actually Required:**
```markdown
#### Scenario: Descriptive name
- **WHEN** condition
- **THEN** expected outcome
- **AND** additional outcomes
```
**Impact:** This was the biggest struggle. The README mentions requirements but never shows how to write scenarios. Without this, requirements fail validation.
#### 2. Complete Spec File Example - MISSING
**What README Shows:** Only fragments
**What's Actually Needed:** A complete working example showing:
- Full spec file structure
- Proper requirement format
- Scenario formatting
- All required elements
#### 3. Validation Commands - NOT MENTIONED
**Missing from README:**
- `npx openspec change validate <change-name>`
- `npx openspec change show <change-name> --json`
- The `--strict` flag for catching warnings
#### 4. Delta Detection Explanation - INCOMPLETE
**What's Missing:**
- WHERE the system looks for specs (specs/ subdirectory)
- THAT deltas are automatically extracted
- HOW to debug when deltas aren't detected
- WHAT error messages mean
### Misleading Documentation
#### "Store only the changes" - MISLEADING
**Line 145:** `# - Store only the changes (not complete future state)`
**Problem:** This suggests storing diffs or partial content. In reality, you need:
- Complete requirements in their final form
- Full scenario definitions
- The entire requirement text
### Documentation That Was Helpful
1. Delta section headers (`## ADDED Requirements`) - clearly documented
2. Directory structure - excellent visualization
3. When to create proposals - well defined
---
## Key Learnings
### 1. OpenSpec's Delta Detection Algorithm
The system follows this process:
1. Scans `openspec/changes/{change-name}/specs/` directory
2. For each spec file found, parses for delta sections (`ADDED`, `MODIFIED`, `REMOVED`, `RENAMED`)
3. Creates delta objects with operation type, affected spec, and requirements
4. Validates that at least one delta exists for the change to be valid
**Important:** Creating entirely new specs is fully supported! New specs use `## ADDED Requirements` and appear as ADDED operations in the deltas.
### 2. Spec File Structure Requirements
Valid spec files must follow this structure:
```markdown
# Spec Title
## [ADDED|MODIFIED|REMOVED|RENAMED] Requirements
### Requirement: Clear requirement statement
The requirement description using SHALL/SHOULD/MAY.
#### Scenario: Scenario name
- **WHEN** condition
- **THEN** expected outcome
- **AND** additional outcomes
```
### 3. Change Proposal Structure
A valid change must have:
- `## Why` section - explaining the motivation
- `## What Changes` section - summarizing the changes
- `specs/` directory with properly formatted spec files containing deltas
- Each delta must have at least one requirement with at least one scenario
### 4. Validation Commands Are Essential
```bash
# Basic validation
npx openspec change validate {change-name}
# Strict validation (recommended)
npx openspec change validate {change-name} --strict
# Debug delta detection
npx openspec change show {change-name} --json | jq '.deltas'
```
---
## Recommendations
### High Priority (System Bugs to Fix)
1. **Improve Error Messages**
- Add actionable guidance to error messages
- Example: "No deltas found. Check: 1) specs/ directory exists, 2) Files use ## ADDED Requirements headers, 3) Each requirement has #### Scenario: sections"
2. **Add Warnings for Malformed Content**
- Warn when scenarios exist but aren't properly formatted
- Show which line/file has the issue
3. **Add Delta Detection Debugging**
- Command like `openspec change debug-deltas {change-name}`
- Show which files were scanned, what was found, what was rejected
### Medium Priority (Documentation Improvements)
1. **Add Complete Working Example to README**
- Full change proposal with all files
- Properly formatted specs with scenarios
- Show the validation output
2. **Add Troubleshooting Section**
- Common errors and their solutions
- How to debug delta detection
- Scenario formatting requirements
3. **Add Validation Best Practices**
- When to use `--strict`
- How to use JSON output for debugging
- Common validation patterns
### Low Priority (Developer Experience)
1. **Add Scaffolding Command**
```bash
openspec change scaffold {change-name}
```
- Creates proper directory structure
- Includes template files with correct formatting
- Adds example scenarios
2. **Add Interactive Creation Wizard**
- Guide users through change creation
- Validate as they go
- Suggest fixes for common issues
3. **Add Auto-fix Capability**
- `--fix` flag to correct common formatting issues
- Convert bullet list scenarios to proper headers
- Add missing operation prefixes
---
## Conclusion
The OpenSpec system works correctly for its intended design, but the user experience for creating changes needs significant improvement. The core issues stem from:
### System Issues
- **Unhelpful error messages** that don't guide users to solutions
- **Silent parsing failures** that provide no feedback about malformed content
- **Lack of debugging tools** to understand what went wrong
### Documentation Issues
- **Critical formatting requirements missing** (especially scenario format)
- **No complete working examples** showing all required elements
- **Validation commands not documented** despite being essential
### User Issues
- **Incorrect assumptions** about how the system works
- **Not examining existing patterns** carefully enough
- **Trying to shortcut** instead of following established conventions
### The Path Forward
With better error messages, complete documentation, and basic tooling support, most of the errors encountered could be prevented. The system's delta-centric approach is powerful and ensures changes are atomic and trackable, but it needs to be more discoverable and user-friendly.
The most impactful improvements would be:
1. Adding scenario format documentation to the README
2. Improving error messages with actionable guidance
3. Creating a scaffolding command for new changes
These changes would transform OpenSpec from a system that works correctly but is hard to use, into one that actively helps developers succeed.
@@ -1,57 +0,0 @@
# Delta: CLI List Command
## MODIFIED Requirements
### Requirement: Command Execution
The command SHALL scan and analyze either active changes or specs based on the selected mode.
#### Scenario: Scanning for changes (default)
- **WHEN** `openspec list` is executed without flags
- **THEN** scan the `openspec/changes/` directory for change directories
- **AND** exclude the `archive/` subdirectory from results
- **AND** parse each change's `tasks.md` file to count task completion
#### Scenario: Scanning for specs
- **WHEN** `openspec list --specs` is executed
- **THEN** scan the `openspec/specs/` directory for capabilities
- **AND** read each capability's `spec.md`
- **AND** parse requirements to compute requirement counts
### Requirement: Output Format
The command SHALL display items in a clear, readable table format with mode-appropriate progress or counts.
#### Scenario: Displaying change list (default)
- **WHEN** displaying the list of changes
- **THEN** show a table with columns:
- Change name (directory name)
- Task progress (e.g., "3/5 tasks" or "✓ Complete")
#### Scenario: Displaying spec list
- **WHEN** displaying the list of specs
- **THEN** show a table with columns:
- Spec id (directory name)
- Requirement count (e.g., "requirements 12")
### Requirement: Empty State
The command SHALL provide clear feedback when no items are present for the selected mode.
#### Scenario: Handling empty state (changes)
- **WHEN** no active changes exist (only archive/ or empty changes/)
- **THEN** display: "No active changes found."
#### Scenario: Handling empty state (specs)
- **WHEN** no specs directory exists or contains no capabilities
- **THEN** display: "No specs found."
### Requirement: Flags
The command SHALL accept flags to select the noun being listed.
#### Scenario: Selecting specs
- **WHEN** `--specs` is provided
- **THEN** list specs instead of changes
#### Scenario: Selecting changes
- **WHEN** `--changes` is provided
- **THEN** list changes explicitly (same as default behavior)
@@ -0,0 +1,78 @@
# Change: Improve Deterministic Tests (Isolate From Repo State)
## Problem
Some unit tests (e.g., ChangeCommand.show/validate) read the live repository
state via `process.cwd()` and `openspec/changes`. This makes outcomes depend on
whatever directories happen to exist and the order returned by `fs.readdir`,
causing flaky success/failure across environments.
Symptoms observed:
- Tests sometimes select a partial or unrelated change folder.
- Failures like missing `proposal.md` when a stray change directory is picked.
- Environment/sandbox differences alter `readdir` ordering and worker behavior.
## Goals
- Make tests deterministic and hermetic.
- Remove dependence on real repo contents and directory ordering.
- Keep runtime behavior unchanged for end users.
## Non‑Goals
- Introduce heavy frameworks or test harness complexity.
- Redesign CLI behavior or change default paths for users.
## Approach
1) Test-local fixture root
- Each suite that touches filesystem discovery creates a temporary directory:
- `openspec/changes/sample-change/proposal.md`
- `openspec/changes/sample-change/specs/sample/spec.md`
- `beforeAll`: `process.chdir(tmpRoot)`; `afterAll`: restore original cwd.
- Use a constant `changeName = 'sample-change'`; remove reliance on
`readdir` order.
2) Optional thin DI for commands (minimal, if needed)
- Allow `ChangeCommand` (and similar) to accept an optional `root` path
(default `process.cwd()`), used for path resolution.
- Tests pass the temp root explicitly; production code remains unchanged.
3) Harden discovery helpers (safe enhancement)
- Update `getActiveChangeIds()`/`getActiveChanges()` to include only
directories containing `proposal.md` (and optionally at least one
`specs/*/spec.md`).
- Prevents incomplete/stray change folders from being treated as active.
## Rationale
- Small, focused changes eliminate flakiness without altering user workflows.
- Temporary fixtures are a well-understood testing pattern and keep tests fast.
- Optional constructor root param is a minimal DI surface that avoids global
stubbing and keeps code simple.
## Risks & Mitigations
- Risk: Tests forget to restore `process.cwd()`.
- Mitigation: Add `afterAll` guard restoring cwd; reset `process.exitCode` in
`afterEach` where modified.
- Risk: Behavior divergence if DI root is misused.
- Mitigation: Default to `process.cwd()`; only tests pass custom roots.
## Acceptance Criteria
- Tests that previously depended on repo state now:
- Create and use a temp fixture root.
- Do not read real `openspec/changes` during execution.
- Pass consistently regardless of directory order or stray folders.
- No change to CLI behavior for end users (paths still default to cwd).
## Rollout
- Phase 1: Convert the suites that hit `ChangeCommand.show/validate` to
isolated fixtures; verify stability locally and in CI.
- Phase 2: Apply the same pattern to any remaining suites that touch file
discovery (`list`, `show`, `validate`, `diff`).
- Phase 3 (optional): Introduce the constructor `root` param and discovery
hardening, if Phase 1 alone isn’t sufficient.
@@ -0,0 +1,25 @@
# Implementation Tasks
## 1. Test Isolation
- [x] 1.1 Create temp fixture roots per suite (openspec/changes, openspec/specs)
- [x] 1.2 Use process.chdir to temp root within tests
- [x] 1.3 Restore original cwd and clean temp dirs after each
## 2. Deterministic Discovery
- [x] 2.1 Implement getActiveChangeIds(root?) to only include dirs with proposal.md
- [x] 2.2 Implement getSpecIds(root?) to only include dirs with spec.md
- [x] 2.3 Return sorted results to avoid fs.readdir ordering variance
## 3. Command Integration
- [x] 3.1 Ensure change/show/validate rely on cwd and discovery helpers
- [x] 3.2 Keep runtime behavior unchanged for end users
## 4. Validation
- [x] 4.1 Convert affected command tests (show, spec, validate, change) to isolated fixtures
- [x] 4.2 Verify tests pass consistently across environments
- [x] 4.3 Confirm no reads from real repo state during tests
## 5. Optional (Not Needed Now)
- [x] 5.1 Add optional root param to discovery helpers (default process.cwd())
- [ ] 5.2 Consider threading root through command constructors if ever required
+13 -6
View File
@@ -1,6 +1,6 @@
{
"name": "openspec",
"version": "0.0.1",
"name": "@fission-ai/openspec",
"version": "0.1.0",
"description": "AI-native system for spec-driven development",
"keywords": [
"openspec",
@@ -9,14 +9,18 @@
"ai",
"development"
],
"homepage": "https://github.com/tabdilsaidixit/openspec",
"homepage": "https://github.com/Fission-AI/OpenSpec",
"repository": {
"type": "git",
"url": "git+https://github.com/tabdilsaidixit/openspec.git"
"url": "https://github.com/Fission-AI/OpenSpec"
},
"license": "MIT",
"author": "OpenSpec Contributors",
"type": "module",
"publishConfig": {
"access": "public",
"tag": "next"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
@@ -41,12 +45,15 @@
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"prepare": "npm run build"
"prepare": "pnpm run build",
"prepublishOnly": "pnpm run build",
"changeset": "changeset"
},
"engines": {
"node": ">=20.19.0"
},
"devDependencies": {
"@changesets/cli": "^2.27.7",
"@types/node": "^24.2.0",
"@vitest/ui": "^3.2.4",
"typescript": "^5.9.2",
@@ -60,4 +67,4 @@
"ora": "^8.2.0",
"zod": "^4.0.17"
}
}
}
+757
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -1,4 +1,5 @@
import { Command } from 'commander';
import { createRequire } from 'module';
import ora from 'ora';
import path from 'path';
import { promises as fs } from 'fs';
@@ -13,11 +14,13 @@ import { ValidateCommand } from '../commands/validate.js';
import { ShowCommand } from '../commands/show.js';
const program = new Command();
const require = createRequire(import.meta.url);
const { version } = require('../../package.json');
program
.name('openspec')
.description('AI-native system for spec-driven development')
.version('0.0.1');
.version(version);
// Global options
program.option('--no-color', 'Disable color output');
@@ -240,4 +243,4 @@ program
}
});
program.parse();
program.parse();
+13 -5
View File
@@ -241,10 +241,18 @@ export class ChangeCommand {
private async getActiveChanges(changesPath: string): Promise<string[]> {
try {
const entries = await fs.readdir(changesPath, { withFileTypes: true });
return entries
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== ARCHIVE_DIR)
.map(entry => entry.name)
.sort();
const result: string[] = [];
for (const entry of entries) {
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === ARCHIVE_DIR) continue;
const proposalPath = path.join(changesPath, entry.name, 'proposal.md');
try {
await fs.access(proposalPath);
result.push(entry.name);
} catch {
// skip directories without proposal.md
}
}
return result.sort();
} catch {
return [];
}
@@ -280,4 +288,4 @@ export class ChangeCommand {
console.error('Next steps:');
bullets.forEach(b => console.error(` ${b}`));
}
}
}
+12 -5
View File
@@ -5,10 +5,18 @@ export async function getActiveChangeIds(root: string = process.cwd()): Promise<
const changesPath = path.join(root, 'openspec', 'changes');
try {
const entries = await fs.readdir(changesPath, { withFileTypes: true });
return entries
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'archive')
.map(entry => entry.name)
.sort();
const result: string[] = [];
for (const entry of entries) {
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'archive') continue;
const proposalPath = path.join(changesPath, entry.name, 'proposal.md');
try {
await fs.access(proposalPath);
result.push(entry.name);
} catch {
// skip directories without proposal.md
}
}
return result.sort();
} catch {
return [];
}
@@ -35,4 +43,3 @@ export async function getSpecIds(root: string = process.cwd()): Promise<string[]
return result.sort();
}
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { execSync } from 'child_process';
@@ -9,9 +9,6 @@ describe('change show (interactive behavior)', () => {
const changesDir = path.join(testDir, 'openspec', 'changes');
const bin = path.join(projectRoot, 'bin', 'openspec.js');
beforeAll(() => {
execSync('pnpm -s build', { stdio: 'pipe' });
});
beforeEach(async () => {
await fs.mkdir(changesDir, { recursive: true });
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { execSync } from 'child_process';
@@ -12,9 +12,6 @@ describe('change validate (interactive behavior)', () => {
const changesDir = path.join(testDir, 'openspec', 'changes');
const bin = path.join(projectRoot, 'bin', 'openspec.js');
beforeAll(() => {
execSync('pnpm -s build', { stdio: 'pipe' });
});
beforeEach(async () => {
await fs.mkdir(changesDir, { recursive: true });
+1 -4
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, afterEach, beforeAll } from 'vitest';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { execSync } from 'child_process';
@@ -10,9 +10,6 @@ describe('top-level show command', () => {
const specsDir = path.join(testDir, 'openspec', 'specs');
const openspecBin = path.join(projectRoot, 'bin', 'openspec.js');
beforeAll(() => {
execSync('pnpm -s build', { stdio: 'pipe' });
});
beforeEach(async () => {
await fs.mkdir(changesDir, { recursive: true });
+1 -4
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { execSync } from 'child_process';
@@ -9,9 +9,6 @@ describe('spec show (interactive behavior)', () => {
const specsDir = path.join(testDir, 'openspec', 'specs');
const bin = path.join(projectRoot, 'bin', 'openspec.js');
beforeAll(() => {
execSync('pnpm -s build', { stdio: 'pipe' });
});
beforeEach(async () => {
await fs.mkdir(specsDir, { recursive: true });
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { execSync } from 'child_process';
@@ -9,9 +9,6 @@ describe('spec validate (interactive behavior)', () => {
const specsDir = path.join(testDir, 'openspec', 'specs');
const bin = path.join(projectRoot, 'bin', 'openspec.js');
beforeAll(() => {
execSync('pnpm -s build', { stdio: 'pipe' });
});
beforeEach(async () => {
await fs.mkdir(specsDir, { recursive: true });
+1 -5
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, afterEach, beforeAll } from 'vitest';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { execSync } from 'child_process';
@@ -9,10 +9,6 @@ describe('spec command', () => {
const specsDir = path.join(testDir, 'openspec', 'specs');
const openspecBin = path.join(projectRoot, 'bin', 'openspec.js');
beforeAll(() => {
// Ensure CLI is built so bin/openspec.js loads latest logic from dist/
execSync('pnpm -s build', { stdio: 'pipe' });
});
beforeEach(async () => {
await fs.mkdir(specsDir, { recursive: true });
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { execSync } from 'child_process';
@@ -9,10 +9,6 @@ describe('validate command enriched human output', () => {
const changesDir = path.join(testDir, 'openspec', 'changes');
const bin = path.join(projectRoot, 'bin', 'openspec.js');
beforeAll(() => {
// Build once so the bin can resolve dist
try { execSync('pnpm -s build', { stdio: 'pipe' }); } catch {}
});
beforeEach(async () => {
await fs.mkdir(changesDir, { recursive: true });
+1 -4
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { execSync } from 'child_process';
@@ -10,9 +10,6 @@ describe('top-level validate command', () => {
const specsDir = path.join(testDir, 'openspec', 'specs');
const bin = path.join(projectRoot, 'bin', 'openspec.js');
beforeAll(() => {
execSync('pnpm -s build', { stdio: 'pipe' });
});
beforeEach(async () => {
await fs.mkdir(changesDir, { recursive: true });
+20 -5
View File
@@ -1,14 +1,29 @@
import { describe, it, expect, beforeAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { ChangeCommand } from '../../../src/commands/change.js';
// These tests assume the repository's own openspec/changes directory exists
// and contains at least one active change (e.g., add-change-commands)
import path from 'path';
import { promises as fs } from 'fs';
import os from 'os';
describe('ChangeCommand.list', () => {
let cmd: ChangeCommand;
let tempRoot: string;
let originalCwd: string;
beforeAll(() => {
beforeAll(async () => {
cmd = new ChangeCommand();
originalCwd = process.cwd();
tempRoot = path.join(os.tmpdir(), `openspec-change-command-list-${Date.now()}`);
const changeDir = path.join(tempRoot, 'openspec', 'changes', 'demo');
await fs.mkdir(changeDir, { recursive: true });
const proposal = `# Change: Demo\n\n## Why\nTest list.\n\n## What Changes\n- **auth:** Add requirement`;
await fs.writeFile(path.join(changeDir, 'proposal.md'), proposal, 'utf-8');
await fs.writeFile(path.join(changeDir, 'tasks.md'), '- [x] Task 1\n- [ ] Task 2\n', 'utf-8');
process.chdir(tempRoot);
});
afterAll(async () => {
process.chdir(originalCwd);
await fs.rm(tempRoot, { recursive: true, force: true });
});
it('returns JSON with expected shape', async () => {
@@ -1,34 +1,33 @@
import { describe, it, expect, beforeAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { ChangeCommand } from '../../../src/commands/change.js';
import path from 'path';
import { promises as fs } from 'fs';
async function findSingleActiveChange(root: string): Promise<string | undefined> {
const changesDir = path.join(root, 'openspec', 'changes');
try {
const entries = await fs.readdir(changesDir, { withFileTypes: true });
const names = entries
.filter((e) => e.isDirectory() && e.name !== 'archive')
.map((e) => e.name);
if (names.length === 1) return names[0];
return names.includes('add-change-commands') ? 'add-change-commands' : names[0];
} catch {
return undefined;
}
}
import os from 'os';
describe('ChangeCommand.show/validate', () => {
let cmd: ChangeCommand;
let changeName: string | undefined;
let changeName: string;
let tempRoot: string;
let originalCwd: string;
beforeAll(async () => {
cmd = new ChangeCommand();
changeName = await findSingleActiveChange(process.cwd());
originalCwd = process.cwd();
tempRoot = path.join(os.tmpdir(), `openspec-change-command-${Date.now()}`);
const changesDir = path.join(tempRoot, 'openspec', 'changes', 'sample-change');
await fs.mkdir(changesDir, { recursive: true });
const proposal = `# Change: Sample Change\n\n## Why\nConsistency in tests.\n\n## What Changes\n- **auth:** Add requirement`;
await fs.writeFile(path.join(changesDir, 'proposal.md'), proposal, 'utf-8');
process.chdir(tempRoot);
changeName = 'sample-change';
});
afterAll(async () => {
process.chdir(originalCwd);
await fs.rm(tempRoot, { recursive: true, force: true });
});
it('show --json prints JSON including deltas', async () => {
if (!changeName) return; // skip if no changes present
const logs: string[] = [];
const origLog = console.log;
try {
@@ -67,8 +66,6 @@ describe('ChangeCommand.show/validate', () => {
});
it('show --json --requirements-only returns minimal object with deltas (deprecated alias)', async () => {
if (!changeName) return; // skip if no changes present
const logs: string[] = [];
const origLog = console.log;
try {
@@ -93,8 +90,6 @@ describe('ChangeCommand.show/validate', () => {
});
it('validate --strict --json returns a report with valid boolean', async () => {
if (!changeName) return; // skip if no changes present
const logs: string[] = [];
const origLog = console.log;
try {
+4 -1
View File
@@ -4,6 +4,9 @@ export default defineConfig({
test: {
globals: true,
environment: 'node',
globalSetup: './vitest.setup.ts',
// Keep default pool settings; some tests rely on process.chdir,
// which is not supported in worker threads
include: ['test/**/*.test.ts'],
coverage: {
reporter: ['text', 'json', 'html'],
@@ -19,4 +22,4 @@ export default defineConfig({
testTimeout: 10000,
hookTimeout: 10000
}
});
});
+21
View File
@@ -0,0 +1,21 @@
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import path from 'path';
// Run once before all tests
export async function setup() {
const distPath = path.join(process.cwd(), 'dist', 'cli', 'index.js');
if (!existsSync(distPath)) {
console.log('Building project before tests...');
try {
execSync('pnpm run build', {
stdio: 'inherit',
cwd: process.cwd()
});
} catch (error) {
console.error('Failed to build project:', error);
process.exit(1);
}
}
}