mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-03 22:13:19 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5cd35ae29 | ||
|
|
33d8b9d96f | ||
|
|
36078b1947 | ||
|
|
d0e1b076c2 | ||
|
|
2fbda520de | ||
|
|
5633556b6d | ||
|
|
2bb0ed36c5 | ||
|
|
06097f9cb7 | ||
|
|
8f5a526396 | ||
|
|
eb152eb2ca | ||
|
|
e987a5a327 | ||
|
|
4971cda812 |
+93
-4
@@ -1,6 +1,95 @@
|
||||
This directory is managed by Changesets.
|
||||
# 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.
|
||||
This directory is managed by [Changesets](https://github.com/changesets/changesets).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
pnpm changeset
|
||||
```
|
||||
|
||||
Follow the prompts to select version bump type and describe your changes.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Add a changeset** — Run `pnpm changeset` locally before or after your PR
|
||||
2. **Version PR** — CI opens/updates a "Version Packages" PR when changesets merge to main
|
||||
3. **Release** — Merging the Version PR triggers npm publish and GitHub Release
|
||||
|
||||
> **Note:** Contributors only need to run `pnpm changeset`. Versioning (`changeset version`) and publishing happen automatically in CI.
|
||||
|
||||
## Template
|
||||
|
||||
Use this structure for your changeset content:
|
||||
|
||||
```markdown
|
||||
---
|
||||
"@fission-ai/openspec": patch
|
||||
---
|
||||
|
||||
### New Features
|
||||
|
||||
- **Feature name** — What users can now do
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed issue where X happened when Y
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `oldMethod()` has been removed, use `newMethod()` instead
|
||||
|
||||
### Deprecations
|
||||
|
||||
- `legacyOption` is deprecated and will be removed in v2.0
|
||||
|
||||
### Other
|
||||
|
||||
- Internal refactoring of X for better performance
|
||||
```
|
||||
|
||||
Include only the sections relevant to your change.
|
||||
|
||||
## Version Bump Guide
|
||||
|
||||
| Type | When to use | Example |
|
||||
|------|-------------|---------|
|
||||
| `patch` | Bug fixes, small improvements | Fixed crash when config missing |
|
||||
| `minor` | New features, non-breaking additions | Added `--verbose` flag |
|
||||
| `major` | Breaking changes, removed features | Renamed `init` to `setup` |
|
||||
|
||||
## When to Create a Changeset
|
||||
|
||||
**Create one for:**
|
||||
- New features or commands
|
||||
- Bug fixes that affect users
|
||||
- Breaking changes or deprecations
|
||||
- Performance improvements users would notice
|
||||
|
||||
**Skip for:**
|
||||
- Documentation-only changes
|
||||
- Test additions/fixes
|
||||
- Internal refactoring with no user impact
|
||||
- CI/tooling changes
|
||||
|
||||
## Writing Good Descriptions
|
||||
|
||||
**Do:** Write for users, not developers
|
||||
```markdown
|
||||
- **Shell completions** — Tab completion now available for Bash, Fish, and PowerShell
|
||||
```
|
||||
|
||||
**Don't:** Write implementation details
|
||||
```markdown
|
||||
- Added ShellCompletionGenerator class with Bash/Fish/PowerShell subclasses
|
||||
```
|
||||
|
||||
**Do:** Explain the impact
|
||||
```markdown
|
||||
- Fixed config loading to respect `XDG_CONFIG_HOME` on Linux
|
||||
```
|
||||
|
||||
**Don't:** Just reference the fix
|
||||
```markdown
|
||||
- Fixed #123
|
||||
```
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"changelog": [
|
||||
"@changesets/changelog-github",
|
||||
{ "repo": "Fission-AI/OpenSpec" }
|
||||
],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
name: Polish Release Notes
|
||||
|
||||
# Triggers when changesets creates a release
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
polish:
|
||||
# Only run on the main repo, not forks
|
||||
if: github.repository == 'Fission-AI/OpenSpec'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get current release body
|
||||
id: get-release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release view "${{ github.event.release.tag_name }}" --json body -q '.body' > current-notes.md
|
||||
echo "Fetched release notes for ${{ github.event.release.tag_name }}"
|
||||
|
||||
- name: Transform release notes with Claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
id: claude
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
direct_prompt: |
|
||||
Transform the changelog in `current-notes.md` into release notes for OpenSpec ${{ github.event.release.tag_name }}.
|
||||
|
||||
## Voice
|
||||
|
||||
OpenSpec is a developer tool. Write like you're talking to a peer:
|
||||
- Direct and practical, not marketing copy
|
||||
- Focus on what changed and why it matters
|
||||
- Skip the hype, keep it real
|
||||
|
||||
## Output
|
||||
|
||||
Create two files:
|
||||
|
||||
### 1. `release-title.txt`
|
||||
|
||||
A short title in this format:
|
||||
```
|
||||
${{ github.event.release.tag_name }} - [1-4 words describing the release]
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `v0.18.0 - OPSX Experimental Workflow`
|
||||
- `v0.16.0 - Antigravity, iFlow Support`
|
||||
- `v0.15.0 - Gemini CLI, RooCode`
|
||||
|
||||
Rules for title:
|
||||
- Lead with the most notable addition
|
||||
- 1-4 words after the dash, no fluff
|
||||
- If multiple features, comma-separate the top 2
|
||||
- For bugfix-only releases, use something like `v0.17.2 - Pre-commit Hook Fix`
|
||||
|
||||
### 2. `polished-notes.md`
|
||||
|
||||
```markdown
|
||||
## What's New in ${{ github.event.release.tag_name }}
|
||||
|
||||
[One sentence: what's the theme of this release?]
|
||||
|
||||
### New
|
||||
|
||||
- **Feature name** - What it does and why you'd use it
|
||||
|
||||
### Improved
|
||||
|
||||
- **Area** - What got better
|
||||
|
||||
### Fixed
|
||||
|
||||
- What was broken, now works
|
||||
```
|
||||
|
||||
Omit empty sections.
|
||||
|
||||
## Rules
|
||||
|
||||
1. Write for developers using OpenSpec with AI coding assistants
|
||||
2. Remove commit hashes (like `eb152eb:`), PR numbers, and changesets wrappers (`### Minor Changes`)
|
||||
3. Lead with what users can do, not implementation details
|
||||
4. One to two sentences per item, max
|
||||
5. Use **bold** for feature/area names
|
||||
6. Skip internal changes (CI, refactors, tests) unless they affect users
|
||||
7. If the input is already well-formatted, just clean up structure and remove noise
|
||||
|
||||
## Example
|
||||
|
||||
Before:
|
||||
```
|
||||
### Minor Changes
|
||||
- 8dfd824: Add OPSX experimental workflow commands and enhanced artifact system
|
||||
**New Commands:**
|
||||
- `/opsx:ff` - Fast-forward through artifact creation
|
||||
```
|
||||
|
||||
After (polished-notes.md):
|
||||
```
|
||||
### New
|
||||
|
||||
- **Fast-forward mode** - Generate all planning artifacts at once with `/opsx:ff`. Useful when you already know what you're building.
|
||||
```
|
||||
|
||||
After (release-title.txt):
|
||||
```
|
||||
v0.18.0 - OPSX Experimental Workflow
|
||||
```
|
||||
|
||||
Write both files. No other output.
|
||||
|
||||
- name: Update release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
TAG="${{ github.event.release.tag_name }}"
|
||||
|
||||
if [ -f "polished-notes.md" ] && [ -f "release-title.txt" ]; then
|
||||
TITLE=$(cat release-title.txt)
|
||||
gh release edit "$TAG" --title "$TITLE" --notes-file polished-notes.md
|
||||
echo "Updated: $TITLE"
|
||||
elif [ -f "polished-notes.md" ]; then
|
||||
gh release edit "$TAG" --notes-file polished-notes.md
|
||||
echo "Updated notes (title unchanged)"
|
||||
else
|
||||
echo "No changes generated, keeping original"
|
||||
fi
|
||||
@@ -18,9 +18,20 @@ jobs:
|
||||
if: github.repository == 'Fission-AI/OpenSpec'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Generate GitHub App token first - used for checkout and changesets
|
||||
# This allows git operations to trigger CI workflows on the version PR
|
||||
# (GITHUB_TOKEN cannot trigger workflows by design)
|
||||
- name: Generate GitHub App Token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
@@ -44,5 +55,5 @@ jobs:
|
||||
# so package.json already contains the bumped version.
|
||||
publish: pnpm run release:ci
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
# npm authentication handled via OIDC trusted publishing (no token needed)
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @fission-ai/openspec
|
||||
|
||||
## 0.19.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- eb152eb: ### New Features
|
||||
|
||||
- **Continue IDE support** – OpenSpec now generates slash commands for [Continue](https://continue.dev/), expanding editor integration options alongside Cursor, Windsurf, Claude Code, and others
|
||||
- **Shell completions for Bash, Fish, and PowerShell** – Run `openspec completion install` to set up tab completion in your preferred shell
|
||||
- **`/opsx:explore` command** – A new thinking partner mode for exploring ideas and investigating problems before committing to changes
|
||||
- **Codebuddy slash command improvements** – Updated frontmatter format for better compatibility
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Shell completions now correctly offer parent-level flags (like `--help`) when a command has subcommands
|
||||
- Fixed Windows compatibility issues in tests
|
||||
|
||||
### Other
|
||||
|
||||
- Added optional anonymous usage statistics to help understand how OpenSpec is used. This is **opt-out** by default – set `OPENSPEC_TELEMETRY=0` or `DO_NOT_TRACK=1` to disable. Only command names and version are collected; no arguments, file paths, or content. Automatically disabled in CI environments.
|
||||
|
||||
## 0.18.0
|
||||
|
||||
### Minor Changes
|
||||
@@ -86,6 +106,8 @@
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add Continue slash command support so `openspec init` can generate `.continue/prompts/openspec-*.prompt` files with MARKDOWN frontmatter and `$ARGUMENTS` placeholder, and refresh them on `openspec update`.
|
||||
|
||||
- Add Antigravity slash command support so `openspec init` can generate `.agent/workflows/openspec-*.md` files with description-only frontmatter and `openspec update` refreshes existing workflows alongside Windsurf.
|
||||
|
||||
## 0.15.0
|
||||
|
||||
@@ -103,6 +103,7 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe
|
||||
| **Cline** | Workflows in `.clinerules/workflows/` directory (`.clinerules/workflows/openspec-*.md`) |
|
||||
| **CodeBuddy Code (CLI)** | `/openspec:proposal`, `/openspec:apply`, `/openspec:archive` (`.codebuddy/commands/`) — see [docs](https://www.codebuddy.ai/cli) |
|
||||
| **Codex** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (global: `~/.codex/prompts`, auto-installed) |
|
||||
| **Continue** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.continue/prompts/`) |
|
||||
| **CoStrict** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.cospec/openspec/commands/`) — see [docs](https://costrict.ai)|
|
||||
| **Crush** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.crush/commands/openspec/`) |
|
||||
| **Cursor** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` |
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
# OpenSpec Versioning & Archiving: Quick Guide
|
||||
|
||||
> **TL;DR:** When multiple branches modify the same spec, the last archive wins (data loss). **Solution:** Use the OPSX workflow (`/opsx:sync`) for AI-driven intelligent merging. Setup: `openspec artifact-experimental-setup`. [Jump to solution](#the-solution-opsx-workflow)
|
||||
|
||||
## The Core Problem
|
||||
|
||||
**OpenSpec can silently lose changes when multiple branches modify the same requirement:**
|
||||
|
||||
```
|
||||
Week 1:
|
||||
┌─────────────┐
|
||||
│ Branch A │ Adds: "Support SSO login"
|
||||
│ (Feature) │ To requirement: "Authentication Methods"
|
||||
└──────┬──────┘
|
||||
│ Archives Monday ✓
|
||||
▼
|
||||
Main Spec now has: [Password, OAuth, SSO]
|
||||
|
||||
|
||||
Week 1:
|
||||
┌─────────────┐
|
||||
│ Branch B │ Adds: "Support biometric login"
|
||||
│ (Feature) │ To requirement: "Authentication Methods"
|
||||
└──────┬──────┘ (Started from pre-SSO spec)
|
||||
│ Archives Friday
|
||||
▼
|
||||
Main Spec now has: [Password, OAuth, Biometric] ← SSO disappeared! 😱
|
||||
```
|
||||
|
||||
**Why?** OpenSpec replaces entire requirement blocks. No Git-like merging. No conflict detection.
|
||||
|
||||
## Workflow Risk
|
||||
|
||||
```
|
||||
Multiple feature branches
|
||||
│
|
||||
├─── Change X: updates "Auth" requirement
|
||||
├─── Change Y: updates "Auth" requirement
|
||||
├─── Change Z: updates "Logging" requirement
|
||||
│
|
||||
Weekly batch archive (Friday)
|
||||
│
|
||||
└─── Last archive wins, earlier scenarios may vanish
|
||||
```
|
||||
|
||||
|
||||
## The Solution: OPSX Workflow
|
||||
|
||||
> **Requirements:** Claude Code, experimental workflow (stable and actively used)
|
||||
|
||||
The **OPSX workflow** solves the conflict problem with **AI-driven intelligent merging**. Instead of mechanical replacement, Claude reads your delta specs and understands intent.
|
||||
|
||||
**Available today.** Setup takes 30 seconds.
|
||||
|
||||
### Quick Comparison
|
||||
|
||||
```
|
||||
Standard Archive: OPSX Sync:
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│ Replace entire │ │ AI-driven merge │
|
||||
│ requirement │ │ Preserves │
|
||||
│ block │ │ unmentioned │
|
||||
│ │ │ scenarios │
|
||||
│ Last write wins │ │ Intelligent │
|
||||
│ ⚠️ Data loss │ │ ✓ Safe │
|
||||
└──────────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
### How It Works: Your Authentication Example
|
||||
|
||||
Let's revisit the SSO/Biometric conflict with OPSX:
|
||||
|
||||
```
|
||||
Week 1:
|
||||
┌─────────────┐
|
||||
│ Branch A │ Delta: "ADDED Requirements: Authentication Methods with SSO"
|
||||
│ (Feature) │
|
||||
└──────┬──────┘
|
||||
│ Friday: /opsx:sync feature-a
|
||||
▼
|
||||
Main Spec: [Password, OAuth, SSO] ✓
|
||||
|
||||
|
||||
┌─────────────┐
|
||||
│ Branch B │ Delta: "ADDED Requirements: Authentication Methods with Biometric"
|
||||
│ (Feature) │ (Delta created from pre-SSO spec)
|
||||
└──────┬──────┘
|
||||
│ Friday: /opsx:sync feature-b
|
||||
│
|
||||
│ What Claude does:
|
||||
│ 1. Reads delta B: "Add Authentication Methods with Biometric"
|
||||
│ 2. Reads main: "Already has Authentication Methods with SSO"
|
||||
│ 3. Applies rule: "Preserve existing content not mentioned in delta"
|
||||
│ 4. Merges: Keep SSO + OAuth, add Biometric
|
||||
▼
|
||||
Main Spec: [Password, OAuth, SSO, Biometric] ✓✓ ← Both changes preserved!
|
||||
```
|
||||
|
||||
**Key principle:** Claude treats deltas as **instructions** ("add this scenario"), not **replacements** ("make it look exactly like this").
|
||||
|
||||
### Your Workflow Options
|
||||
|
||||
**Option 1: Friday Batch (Keeps Your Current Rhythm)**
|
||||
|
||||
```
|
||||
Throughout week:
|
||||
├─ Features complete on branches
|
||||
└─ Code merges to main
|
||||
|
||||
Friday:
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ /opsx:sync feature-a │
|
||||
│ ↓ Main updated with A's changes │
|
||||
│ │
|
||||
│ /opsx:sync feature-b │
|
||||
│ ↓ Claude preserves A's changes, adds B │
|
||||
│ │
|
||||
│ /opsx:sync feature-c │
|
||||
│ ↓ Claude preserves A+B, adds C │
|
||||
│ │
|
||||
│ git diff openspec/specs/ (review all) │
|
||||
│ │
|
||||
│ /opsx:archive feature-a │
|
||||
│ /opsx:archive feature-b │
|
||||
│ /opsx:archive feature-c │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Option 2: Sync-as-you-go (Even Safer)**
|
||||
|
||||
```
|
||||
Monday: Feature A merges → /opsx:sync feature-a
|
||||
Wednesday: Feature B merges → /opsx:sync feature-b (auto-merges with A)
|
||||
Friday: Just /opsx:archive (specs already synced)
|
||||
```
|
||||
|
||||
### Concrete Example: Three Teams, One Requirement
|
||||
|
||||
**Setup:**
|
||||
- Change A: Adds SSO login
|
||||
- Change B: Adds Biometric login
|
||||
- Change C: Updates OAuth token expiry 1h → 2h
|
||||
|
||||
**Main spec starts with:**
|
||||
```markdown
|
||||
### Requirement: Authentication Methods
|
||||
#### Scenario: Password login
|
||||
...
|
||||
#### Scenario: OAuth login (expires in 1h)
|
||||
...
|
||||
```
|
||||
|
||||
**Friday batch sync:**
|
||||
|
||||
```
|
||||
/opsx:sync feature-a
|
||||
├─ Delta A: ADDED "SSO login" scenario
|
||||
└─ Main spec: [Password, OAuth, SSO]
|
||||
|
||||
/opsx:sync feature-b
|
||||
├─ Delta B: ADDED "Biometric login" scenario
|
||||
├─ Claude sees: Main already has SSO (not in delta B)
|
||||
├─ Claude preserves: SSO
|
||||
└─ Main spec: [Password, OAuth, SSO, Biometric]
|
||||
|
||||
/opsx:sync feature-c
|
||||
├─ Delta C: MODIFIED "OAuth login" scenario (2h expiry)
|
||||
├─ Claude sees: Main has SSO + Biometric (not in delta C)
|
||||
├─ Claude preserves: SSO + Biometric
|
||||
├─ Claude updates: OAuth expiry
|
||||
└─ Main spec: [Password, OAuth (2h), SSO, Biometric]
|
||||
```
|
||||
|
||||
**Result:** All three changes merged successfully! 🎉
|
||||
|
||||
### Getting Started
|
||||
|
||||
**1. One-time setup:**
|
||||
```bash
|
||||
openspec artifact-experimental-setup
|
||||
# Creates /opsx:* skills in .claude/skills/
|
||||
```
|
||||
|
||||
**2. Try it this Friday:**
|
||||
```
|
||||
# In Claude Code:
|
||||
/opsx:sync
|
||||
→ Select a change
|
||||
→ Claude merges intelligently
|
||||
|
||||
git diff openspec/specs/
|
||||
→ Review what changed
|
||||
|
||||
/opsx:archive
|
||||
→ Move to archive when satisfied
|
||||
```
|
||||
|
||||
**3. For new features:**
|
||||
```
|
||||
/opsx:new feature-name # Start new change
|
||||
/opsx:ff # Create planning artifacts
|
||||
/opsx:apply # Implement
|
||||
/opsx:sync # Merge specs to main
|
||||
/opsx:archive # Archive when done
|
||||
```
|
||||
|
||||
### What Cases Does This Handle?
|
||||
|
||||
```
|
||||
✓ Multiple changes adding different scenarios to same requirement
|
||||
✓ One change adds, another modifies same requirement
|
||||
✓ Changes affecting different parts of same requirement
|
||||
✓ Mix of ADDED/MODIFIED/REMOVED operations
|
||||
✓ Most real-world parallel development
|
||||
|
||||
⚠️ Two changes modifying exact same scenario in conflicting ways
|
||||
⚠️ One removes what another modifies
|
||||
|
||||
Best practice: Always review git diff after each sync
|
||||
```
|
||||
|
||||
### Why This Works
|
||||
|
||||
The `/opsx:sync` skill instructs Claude:
|
||||
|
||||
> "Preserve scenarios/content not mentioned in the delta. The delta represents *intent*, not a wholesale replacement. Apply changes intelligently - add new scenarios without removing existing ones, update only what's explicitly mentioned."
|
||||
|
||||
This is like having a human do the merge instead of a mechanical diff tool.
|
||||
|
||||
## If You Can't Use OPSX Yet
|
||||
|
||||
If you're not using Claude Code or can't adopt OPSX yet, here are manual workarounds:
|
||||
|
||||
### Before Creating Changes
|
||||
|
||||
```bash
|
||||
# Check for conflicts
|
||||
openspec list
|
||||
ls openspec/changes/*/specs/your-capability/
|
||||
|
||||
# If overlap exists:
|
||||
→ Coordinate with that team
|
||||
→ Consider combining changes
|
||||
→ Or sequence them (one after another)
|
||||
```
|
||||
|
||||
### Archive Order Strategy
|
||||
|
||||
```
|
||||
Friday Archive Session:
|
||||
|
||||
Step 1: Group by overlap
|
||||
┌─────────────────────────────────────┐
|
||||
│ No Overlap: [Z, W, V] │ ← Archive these first
|
||||
│ Low Overlap: [A touches auth only] │ ← Then these
|
||||
│ High Overlap:[X, Y both touch auth] │ ← Coordinate these last
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
Step 2: Archive in order
|
||||
Z → W → V → A → (manually check X vs Y) → X → fix Y's delta → Y
|
||||
↑
|
||||
Key step: update Y's delta spec
|
||||
to include X's changes before
|
||||
archiving Y
|
||||
```
|
||||
|
||||
### Safety Checklist
|
||||
|
||||
```bash
|
||||
□ git diff main -- openspec/specs/your-capability/
|
||||
□ openspec validate --strict
|
||||
□ Manual review: did another change land on this spec?
|
||||
□ If yes: update your delta spec to include their scenarios
|
||||
```
|
||||
|
||||
## Your Action Plan
|
||||
|
||||
```
|
||||
This Week:
|
||||
├─ Run: openspec artifact-experimental-setup
|
||||
└─ Try /opsx:sync on one change
|
||||
|
||||
Next Sprint:
|
||||
├─ Adopt /opsx:sync for all Friday archives
|
||||
└─ Switch to sync-as-you-go if it works well
|
||||
|
||||
Ongoing:
|
||||
└─ Always run git diff after /opsx:sync to review
|
||||
```
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ OPENSPEC CONFLICT PREVENTION CHEAT SHEET │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ RECOMMENDED: Use OPSX Workflow │
|
||||
│ ✓ openspec artifact-experimental-setup (one-time) │
|
||||
│ ✓ /opsx:sync after each feature │
|
||||
│ ✓ git diff openspec/specs/ (review) │
|
||||
│ ✓ /opsx:archive when done │
|
||||
│ │
|
||||
│ FRIDAY BATCH WORKFLOW: │
|
||||
│ 1. /opsx:sync feature-a │
|
||||
│ 2. /opsx:sync feature-b (AI merges with A) │
|
||||
│ 3. /opsx:sync feature-c (AI merges with A+B) │
|
||||
│ 4. git diff openspec/specs/ (review all) │
|
||||
│ 5. /opsx:archive each │
|
||||
│ │
|
||||
│ IF NOT USING OPSX: │
|
||||
│ □ openspec list (check overlaps) │
|
||||
│ □ Coordinate with other teams │
|
||||
│ □ Archive non-overlapping first │
|
||||
│ □ Manually merge conflicts in delta specs │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Insight
|
||||
|
||||
> **OpenSpec delta specs are instructions, not files.** Standard archiving does mechanical replacement (last write wins). OPSX workflow uses AI to understand intent and merge intelligently (all writes preserved). Use OPSX for parallel development.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Detailed Technical Analysis:** [openspec-parallel-merge-plan.md](../openspec-parallel-merge-plan.md)
|
||||
- **OPSX Workflow Guide:** [experimental-workflow.md](./experimental-workflow.md)
|
||||
- **Join Discussion:** [OpenSpec Discord](https://discord.gg/BYjPaKbqMt)
|
||||
- **Report Issues:** [GitHub Issues](https://github.com/Fission-AI/openspec/issues)
|
||||
@@ -211,6 +211,12 @@ The init command SHALL generate slash command files for supported editors using
|
||||
- **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 Continue
|
||||
- **WHEN** the user selects Continue during initialization
|
||||
- **THEN** create `.continue/prompts/openspec-proposal.prompt`, `.continue/prompts/openspec-apply.prompt`, and `.continue/prompts/openspec-archive.prompt`
|
||||
- **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`
|
||||
|
||||
@@ -75,6 +75,11 @@ The update command SHALL refresh existing slash command files for configured too
|
||||
- **AND** include Cline-specific Markdown heading frontmatter
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Continue
|
||||
- **WHEN** `.continue/prompts/` contains `openspec-proposal.prompt`, `openspec-apply.prompt`, and `openspec-archive.prompt`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **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
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fission-ai/openspec",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"description": "AI-native system for spec-driven development",
|
||||
"keywords": [
|
||||
"openspec",
|
||||
@@ -54,13 +54,13 @@
|
||||
"check:pack-version": "node scripts/pack-version-check.mjs",
|
||||
"release": "pnpm run release:ci",
|
||||
"release:ci": "pnpm run check:pack-version && pnpm exec changeset publish",
|
||||
"release:local": "pnpm exec changeset version && pnpm run check:pack-version && pnpm exec changeset publish",
|
||||
"changeset": "changeset"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "^0.5.2",
|
||||
"@changesets/cli": "^2.27.7",
|
||||
"@types/node": "^24.2.0",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
|
||||
Generated
+66
@@ -36,6 +36,9 @@ importers:
|
||||
specifier: ^4.0.17
|
||||
version: 4.0.17
|
||||
devDependencies:
|
||||
'@changesets/changelog-github':
|
||||
specifier: ^0.5.2
|
||||
version: 0.5.2
|
||||
'@changesets/cli':
|
||||
specifier: ^2.27.7
|
||||
version: 2.29.6(@types/node@24.2.0)
|
||||
@@ -73,6 +76,9 @@ packages:
|
||||
'@changesets/changelog-git@0.2.1':
|
||||
resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==}
|
||||
|
||||
'@changesets/changelog-github@0.5.2':
|
||||
resolution: {integrity: sha512-HeGeDl8HaIGj9fQHo/tv5XKQ2SNEi9+9yl1Bss1jttPqeiASRXhfi0A2wv8yFKCp07kR1gpOI5ge6+CWNm1jPw==}
|
||||
|
||||
'@changesets/cli@2.29.6':
|
||||
resolution: {integrity: sha512-6qCcVsIG1KQLhpQ5zE8N0PckIx4+9QlHK3z6/lwKnw7Tir71Bjw8BeOZaxA/4Jt00pcgCnCSWZnyuZf5Il05QQ==}
|
||||
hasBin: true
|
||||
@@ -86,6 +92,9 @@ packages:
|
||||
'@changesets/get-dependents-graph@2.1.3':
|
||||
resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==}
|
||||
|
||||
'@changesets/get-github-info@0.7.0':
|
||||
resolution: {integrity: sha512-+i67Bmhfj9V4KfDeS1+Tz3iF32btKZB2AAx+cYMqDSRFP7r3/ZdGbjCo+c6qkyViN9ygDuBjzageuPGJtKGe5A==}
|
||||
|
||||
'@changesets/get-release-plan@4.0.13':
|
||||
resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==}
|
||||
|
||||
@@ -829,6 +838,9 @@ packages:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
dataloader@1.4.0:
|
||||
resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==}
|
||||
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -853,6 +865,10 @@ packages:
|
||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dotenv@8.6.0:
|
||||
resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
emoji-regex@10.4.0:
|
||||
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
|
||||
|
||||
@@ -1198,6 +1214,15 @@ packages:
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
onetime@7.0.0:
|
||||
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1465,6 +1490,9 @@ packages:
|
||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
ts-api-utils@2.1.0:
|
||||
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
|
||||
engines: {node: '>=18.12'}
|
||||
@@ -1574,6 +1602,12 @@ packages:
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1641,6 +1675,14 @@ snapshots:
|
||||
dependencies:
|
||||
'@changesets/types': 6.1.0
|
||||
|
||||
'@changesets/changelog-github@0.5.2':
|
||||
dependencies:
|
||||
'@changesets/get-github-info': 0.7.0
|
||||
'@changesets/types': 6.1.0
|
||||
dotenv: 8.6.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@changesets/cli@2.29.6(@types/node@24.2.0)':
|
||||
dependencies:
|
||||
'@changesets/apply-release-plan': 7.0.12
|
||||
@@ -1695,6 +1737,13 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
semver: 7.7.2
|
||||
|
||||
'@changesets/get-github-info@0.7.0':
|
||||
dependencies:
|
||||
dataloader: 1.4.0
|
||||
node-fetch: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@changesets/get-release-plan@4.0.13':
|
||||
dependencies:
|
||||
'@changesets/assemble-release-plan': 6.0.9
|
||||
@@ -2379,6 +2428,8 @@ snapshots:
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
dataloader@1.4.0: {}
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -2393,6 +2444,8 @@ snapshots:
|
||||
dependencies:
|
||||
path-type: 4.0.0
|
||||
|
||||
dotenv@8.6.0: {}
|
||||
|
||||
emoji-regex@10.4.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
@@ -2737,6 +2790,10 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
onetime@7.0.0:
|
||||
dependencies:
|
||||
mimic-function: 5.0.1
|
||||
@@ -2985,6 +3042,8 @@ snapshots:
|
||||
|
||||
totalist@3.0.1: {}
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
ts-api-utils@2.1.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
@@ -3092,6 +3151,13 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
+6
-3
@@ -50,7 +50,10 @@ program
|
||||
program.option('--no-color', 'Disable color output');
|
||||
|
||||
// Apply global flags and telemetry before any command runs
|
||||
program.hook('preAction', async (thisCommand) => {
|
||||
// Note: preAction receives (thisCommand, actionCommand) where:
|
||||
// - thisCommand: the command where hook was added (root program)
|
||||
// - actionCommand: the command actually being executed (subcommand)
|
||||
program.hook('preAction', async (thisCommand, actionCommand) => {
|
||||
const opts = thisCommand.opts();
|
||||
if (opts.color === false) {
|
||||
process.env.NO_COLOR = '1';
|
||||
@@ -59,8 +62,8 @@ program.hook('preAction', async (thisCommand) => {
|
||||
// Show first-run telemetry notice (if not seen)
|
||||
await maybeShowTelemetryNotice();
|
||||
|
||||
// Track command execution
|
||||
const commandPath = getCommandPath(thisCommand);
|
||||
// Track command execution (use actionCommand to get the actual subcommand)
|
||||
const commandPath = getCommandPath(actionCommand);
|
||||
await trackCommand(commandPath, version);
|
||||
});
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export const AI_TOOLS: AIToolOption[] = [
|
||||
{ name: 'Cline', value: 'cline', available: true, successLabel: 'Cline' },
|
||||
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex' },
|
||||
{ name: 'CodeBuddy Code (CLI)', value: 'codebuddy', available: true, successLabel: 'CodeBuddy Code' },
|
||||
{ name: 'Continue', value: 'continue', available: true, successLabel: 'Continue (VS Code / JetBrains / Cli)' },
|
||||
{ name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict' },
|
||||
{ name: 'Crush', value: 'crush', available: true, successLabel: 'Crush' },
|
||||
{ name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor' },
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { SlashCommandConfigurator } from './base.js';
|
||||
import { SlashCommandId } from '../../templates/index.js';
|
||||
|
||||
const FILE_PATHS: Record<SlashCommandId, string> = {
|
||||
proposal: '.continue/prompts/openspec-proposal.prompt',
|
||||
apply: '.continue/prompts/openspec-apply.prompt',
|
||||
archive: '.continue/prompts/openspec-archive.prompt'
|
||||
};
|
||||
|
||||
/*
|
||||
* Continue .prompt format requires YAML frontmatter:
|
||||
* ---
|
||||
* name: commandName
|
||||
* description: description
|
||||
* invokable: true
|
||||
* ---
|
||||
* Body...
|
||||
*
|
||||
* The 'invokable: true' field is required to make the prompt available as a slash command.
|
||||
* We use 'openspec-proposal' as the name so the command becomes /openspec-proposal.
|
||||
*/
|
||||
const FRONTMATTER: Record<SlashCommandId, string> = {
|
||||
proposal: `---
|
||||
name: openspec-proposal
|
||||
description: Scaffold a new OpenSpec change and validate strictly.
|
||||
invokable: true
|
||||
---`,
|
||||
apply: `---
|
||||
name: openspec-apply
|
||||
description: Implement an approved OpenSpec change and keep tasks in sync.
|
||||
invokable: true
|
||||
---`,
|
||||
archive: `---
|
||||
name: openspec-archive
|
||||
description: Archive a deployed OpenSpec change and update specs.
|
||||
invokable: true
|
||||
---`
|
||||
};
|
||||
|
||||
export class ContinueSlashCommandConfigurator extends SlashCommandConfigurator {
|
||||
readonly toolId = 'continue';
|
||||
readonly isAvailable = true;
|
||||
|
||||
protected getRelativePath(id: SlashCommandId): string {
|
||||
return FILE_PATHS[id];
|
||||
}
|
||||
|
||||
protected getFrontmatter(id: SlashCommandId): string {
|
||||
return FRONTMATTER[id];
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { QwenSlashCommandConfigurator } from './qwen.js';
|
||||
import { RooCodeSlashCommandConfigurator } from './roocode.js';
|
||||
import { AntigravitySlashCommandConfigurator } from './antigravity.js';
|
||||
import { IflowSlashCommandConfigurator } from './iflow.js';
|
||||
import { ContinueSlashCommandConfigurator } from './continue.js';
|
||||
|
||||
export class SlashCommandRegistry {
|
||||
private static configurators: Map<string, SlashCommandConfigurator> = new Map();
|
||||
@@ -44,6 +45,7 @@ export class SlashCommandRegistry {
|
||||
const roocode = new RooCodeSlashCommandConfigurator();
|
||||
const antigravity = new AntigravitySlashCommandConfigurator();
|
||||
const iflow = new IflowSlashCommandConfigurator();
|
||||
const continueTool = new ContinueSlashCommandConfigurator();
|
||||
|
||||
this.configurators.set(claude.toolId, claude);
|
||||
this.configurators.set(codeBuddy.toolId, codeBuddy);
|
||||
@@ -65,6 +67,7 @@ export class SlashCommandRegistry {
|
||||
this.configurators.set(roocode.toolId, roocode);
|
||||
this.configurators.set(antigravity.toolId, antigravity);
|
||||
this.configurators.set(iflow.toolId, iflow);
|
||||
this.configurators.set(continueTool.toolId, continueTool);
|
||||
}
|
||||
|
||||
static register(configurator: SlashCommandConfigurator): void {
|
||||
|
||||
@@ -1151,6 +1151,61 @@ describe('InitCommand', () => {
|
||||
expect(codeBuddyChoice.configured).toBe(true);
|
||||
});
|
||||
|
||||
it('should create Continue slash command files with templates', async () => {
|
||||
queueSelections('continue', DONE);
|
||||
|
||||
await initCommand.execute(testDir);
|
||||
|
||||
const continueProposal = path.join(
|
||||
testDir,
|
||||
'.continue/prompts/openspec-proposal.prompt'
|
||||
);
|
||||
const continueApply = path.join(
|
||||
testDir,
|
||||
'.continue/prompts/openspec-apply.prompt'
|
||||
);
|
||||
const continueArchive = path.join(
|
||||
testDir,
|
||||
'.continue/prompts/openspec-archive.prompt'
|
||||
);
|
||||
|
||||
expect(await fileExists(continueProposal)).toBe(true);
|
||||
expect(await fileExists(continueApply)).toBe(true);
|
||||
expect(await fileExists(continueArchive)).toBe(true);
|
||||
|
||||
const proposalContent = await fs.readFile(continueProposal, 'utf-8');
|
||||
expect(proposalContent).toContain('---');
|
||||
expect(proposalContent).toContain('name: openspec-proposal');
|
||||
expect(proposalContent).toContain('invokable: true');
|
||||
expect(proposalContent).toContain('<!-- OPENSPEC:START -->');
|
||||
|
||||
const applyContent = await fs.readFile(continueApply, 'utf-8');
|
||||
expect(applyContent).toContain('---');
|
||||
expect(applyContent).toContain('name: openspec-apply');
|
||||
expect(applyContent).toContain('description: Implement an approved OpenSpec change and keep tasks in sync.');
|
||||
expect(applyContent).toContain('invokable: true');
|
||||
expect(applyContent).toContain('Work through tasks sequentially');
|
||||
|
||||
const archiveContent = await fs.readFile(continueArchive, 'utf-8');
|
||||
expect(archiveContent).toContain('---');
|
||||
expect(archiveContent).toContain('name: openspec-archive');
|
||||
expect(archiveContent).toContain('description: Archive a deployed OpenSpec change and update specs.');
|
||||
expect(archiveContent).toContain('invokable: true');
|
||||
expect(archiveContent).toContain('openspec archive <id> --yes');
|
||||
});
|
||||
|
||||
it('should mark Continue as already configured during extend mode', async () => {
|
||||
queueSelections('continue', DONE, 'continue', DONE);
|
||||
await initCommand.execute(testDir);
|
||||
await initCommand.execute(testDir);
|
||||
|
||||
const secondRunArgs = mockPrompt.mock.calls[1][0];
|
||||
const continueChoice = secondRunArgs.choices.find(
|
||||
(choice: any) => choice.value === 'continue'
|
||||
);
|
||||
expect(continueChoice.configured).toBe(true);
|
||||
});
|
||||
|
||||
it('should create CODEBUDDY.md when CodeBuddy is selected', async () => {
|
||||
queueSelections('codebuddy', DONE);
|
||||
|
||||
|
||||
@@ -368,6 +368,80 @@ Old body
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should refresh existing Continue prompt files', async () => {
|
||||
const continuePath = path.join(
|
||||
testDir,
|
||||
'.continue/prompts/openspec-apply.prompt'
|
||||
);
|
||||
await fs.mkdir(path.dirname(continuePath), { recursive: true });
|
||||
const initialContent = `---
|
||||
name: openspec-apply
|
||||
description: Old description
|
||||
invokable: true
|
||||
---
|
||||
<!-- OPENSPEC:START -->
|
||||
Old body
|
||||
<!-- OPENSPEC:END -->`;
|
||||
await fs.writeFile(continuePath, initialContent);
|
||||
|
||||
const consoleSpy = vi.spyOn(console, 'log');
|
||||
|
||||
await updateCommand.execute(testDir);
|
||||
|
||||
const updated = await fs.readFile(continuePath, 'utf-8');
|
||||
expect(updated).toContain('name: openspec-apply');
|
||||
expect(updated).toContain('invokable: true');
|
||||
expect(updated).toContain('Work through tasks sequentially');
|
||||
expect(updated).not.toContain('Old body');
|
||||
|
||||
const [logMessage] = consoleSpy.mock.calls[0];
|
||||
expect(logMessage).toContain(
|
||||
'Updated OpenSpec instructions (openspec/AGENTS.md'
|
||||
);
|
||||
expect(logMessage).toContain('AGENTS.md (created)');
|
||||
expect(logMessage).toContain(
|
||||
'Updated slash commands: .continue/prompts/openspec-apply.prompt'
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not create missing Continue prompt files on update', async () => {
|
||||
const continueApply = path.join(
|
||||
testDir,
|
||||
'.continue/prompts/openspec-apply.prompt'
|
||||
);
|
||||
|
||||
// Only create apply; leave proposal and archive missing
|
||||
await fs.mkdir(path.dirname(continueApply), { recursive: true });
|
||||
await fs.writeFile(
|
||||
continueApply,
|
||||
`---
|
||||
name: openspec-apply
|
||||
description: Old description
|
||||
invokable: true
|
||||
---
|
||||
<!-- OPENSPEC:START -->
|
||||
Old body
|
||||
<!-- OPENSPEC:END -->`
|
||||
);
|
||||
|
||||
await updateCommand.execute(testDir);
|
||||
|
||||
const continueProposal = path.join(
|
||||
testDir,
|
||||
'.continue/prompts/openspec-proposal.prompt'
|
||||
);
|
||||
const continueArchive = path.join(
|
||||
testDir,
|
||||
'.continue/prompts/openspec-archive.prompt'
|
||||
);
|
||||
|
||||
// Confirm they weren't created by update
|
||||
await expect(FileSystemUtils.fileExists(continueProposal)).resolves.toBe(false);
|
||||
await expect(FileSystemUtils.fileExists(continueArchive)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('should refresh existing OpenCode slash command files', async () => {
|
||||
const openCodePath = path.join(
|
||||
testDir,
|
||||
|
||||
Reference in New Issue
Block a user