mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-04 06:18:24 +08:00
Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
473093f885 | ||
|
|
b5a884748b | ||
|
|
690c75225c | ||
|
|
dd53fb7736 | ||
|
|
2a441c472d | ||
|
|
ed4d965208 | ||
|
|
c86985d6ec | ||
|
|
bf4bc2426f | ||
|
|
c57e421cc2 | ||
|
|
ed2e832066 | ||
|
|
9db74aa5ac | ||
|
|
b5b7248610 | ||
|
|
322bfd455a | ||
|
|
08c349369a | ||
|
|
40afee643e | ||
|
|
05023dab43 | ||
|
|
d7a928b4e9 | ||
|
|
07dd634986 | ||
|
|
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,20 @@
|
||||
# Github Workflows
|
||||
|
||||
## Testing CI Locally
|
||||
|
||||
Test GitHub Actions workflows locally using [act](https://nektosact.com/):
|
||||
|
||||
```bash
|
||||
# Test all PR checks
|
||||
act pull_request
|
||||
|
||||
# Test specific job
|
||||
act pull_request -j nix-flake-validate
|
||||
|
||||
# Dry run to see what would execute
|
||||
act pull_request --dryrun
|
||||
```
|
||||
|
||||
The `.actrc` file configures act to use the appropriate Docker image.
|
||||
|
||||
|
||||
@@ -156,6 +156,64 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nix-flake-validate:
|
||||
name: Nix Flake Validation
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@v13
|
||||
|
||||
- name: Setup Nix cache
|
||||
uses: DeterminateSystems/magic-nix-cache-action@v8
|
||||
|
||||
- name: Build with Nix
|
||||
run: nix build
|
||||
|
||||
- name: Verify build output
|
||||
run: |
|
||||
if [ ! -e "result" ]; then
|
||||
echo "Error: Nix build output 'result' symlink not found"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "result/bin/openspec" ]; then
|
||||
echo "Error: openspec binary not found in build output"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Build output verified"
|
||||
|
||||
- name: Test binary execution
|
||||
run: |
|
||||
VERSION=$(nix run . -- --version)
|
||||
echo "OpenSpec version: $VERSION"
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Error: Version command returned empty output"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Binary execution successful"
|
||||
|
||||
- name: Validate update script
|
||||
run: |
|
||||
echo "Testing update-flake.sh script..."
|
||||
bash scripts/update-flake.sh
|
||||
echo "✅ Update script executed successfully"
|
||||
|
||||
- name: Check flake.nix modifications
|
||||
run: |
|
||||
if git diff --quiet flake.nix; then
|
||||
echo "⚠️ Warning: flake.nix was not modified by update script"
|
||||
else
|
||||
echo "✅ flake.nix was updated by script"
|
||||
git diff flake.nix
|
||||
fi
|
||||
|
||||
- name: Restore flake.nix
|
||||
if: always()
|
||||
run: git checkout -- flake.nix || true
|
||||
|
||||
validate-changesets:
|
||||
name: Validate Changesets
|
||||
runs-on: ubuntu-latest
|
||||
@@ -191,7 +249,7 @@ jobs:
|
||||
required-checks-pr:
|
||||
name: All checks passed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test_pr, lint]
|
||||
needs: [test_pr, lint, nix-flake-validate]
|
||||
if: always() && github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Verify all checks passed
|
||||
@@ -204,12 +262,16 @@ jobs:
|
||||
echo "Lint job failed"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${{ needs.nix-flake-validate.result }}" != "success" ]]; then
|
||||
echo "Nix flake validation job failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "All required checks passed!"
|
||||
|
||||
required-checks-main:
|
||||
name: All checks passed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test_matrix, lint]
|
||||
needs: [test_matrix, lint, nix-flake-validate]
|
||||
if: always() && github.event_name != 'pull_request'
|
||||
steps:
|
||||
- name: Verify all checks passed
|
||||
@@ -222,4 +284,8 @@ jobs:
|
||||
echo "Lint job failed"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${{ needs.nix-flake-validate.result }}" != "success" ]]; then
|
||||
echo "Nix flake validation job failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "All required checks passed!"
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
name: Polish Release Notes
|
||||
|
||||
# Manual trigger after a release is published
|
||||
# The Claude Code action doesn't support 'release' event triggers directly
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'Release tag to polish (e.g., v0.18.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
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 "${{ inputs.tag_name }}" --json body -q '.body' > current-notes.md
|
||||
echo "Fetched release notes for ${{ inputs.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 }}
|
||||
claude_args: "--allowedTools Write,Read"
|
||||
prompt: |
|
||||
Transform the changelog in `current-notes.md` into release notes for OpenSpec ${{ inputs.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:
|
||||
```
|
||||
${{ inputs.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 ${{ inputs.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="${{ inputs.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)
|
||||
|
||||
@@ -148,3 +148,4 @@ CLAUDE.md
|
||||
|
||||
# Pnpm
|
||||
.pnpm-store/
|
||||
result
|
||||
|
||||
@@ -1,5 +1,57 @@
|
||||
# @fission-ai/openspec
|
||||
|
||||
## 0.21.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#516](https://github.com/Fission-AI/OpenSpec/pull/516) [`b5a8847`](https://github.com/Fission-AI/OpenSpec/commit/b5a884748be6156a7bb140b4941cfec4f20a9fc8) Thanks [@TabishB](https://github.com/TabishB)! - ### New Features
|
||||
|
||||
- **Feedback command** — Submit feedback directly from the CLI with `openspec feedback`, which creates GitHub Issues with automatic metadata inclusion and graceful fallback for manual submission
|
||||
- **Nix flake support** — Install and develop openspec using Nix with the new `flake.nix`, including automated flake maintenance and CI validation
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Explore mode guardrails** — Explore mode now explicitly prevents implementation, keeping the focus on thinking and discovery while still allowing artifact creation
|
||||
|
||||
### Other
|
||||
|
||||
- Improved change inference in `opsx apply` — automatically detects the target change from conversation context or prompts when ambiguous
|
||||
- Streamlined archive sync assessment with clearer delta spec location guidance
|
||||
|
||||
## 0.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#502](https://github.com/Fission-AI/OpenSpec/pull/502) [`9db74aa`](https://github.com/Fission-AI/OpenSpec/commit/9db74aa5ac6547efadaed795217cfa17444f2004) Thanks [@TabishB](https://github.com/TabishB)! - ### New Features
|
||||
|
||||
- **`/opsx:verify` command** — Validate that change implementations match their specifications
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed vitest process storms by capping worker parallelism
|
||||
- Fixed agent workflows to use non-interactive mode for validation commands
|
||||
- Fixed PowerShell completions generator to remove trailing commas
|
||||
|
||||
## 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 +138,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
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Maintainers
|
||||
|
||||
People who maintain and guide OpenSpec.
|
||||
|
||||
## Core Maintainers
|
||||
|
||||
| Name | GitHub | Role |
|
||||
|------|--------|------|
|
||||
| Tabish Bidiwale | [@TabishB](https://github.com/TabishB) | Lead maintainer |
|
||||
|
||||
## Advisors
|
||||
|
||||
Advisors help shape technical direction and provide guidance to the project.
|
||||
|
||||
| Name | GitHub | Focus |
|
||||
|------|--------|-------|
|
||||
| Hari Krishnan | [@harikrishnan83](https://github.com/harikrishnan83) | Technical direction |
|
||||
@@ -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` |
|
||||
@@ -139,6 +140,8 @@ These tools automatically read workflow instructions from `openspec/AGENTS.md`.
|
||||
|
||||
#### Step 1: Install the CLI globally
|
||||
|
||||
**Option A: Using npm**
|
||||
|
||||
```bash
|
||||
npm install -g @fission-ai/openspec@latest
|
||||
```
|
||||
@@ -148,6 +151,39 @@ Verify installation:
|
||||
openspec --version
|
||||
```
|
||||
|
||||
**Option B: Using Nix (NixOS and Nix package manager)**
|
||||
|
||||
Run OpenSpec directly without installation:
|
||||
```bash
|
||||
nix run github:Fission-AI/OpenSpec -- init
|
||||
```
|
||||
|
||||
Or install to your profile:
|
||||
```bash
|
||||
nix profile install github:Fission-AI/OpenSpec
|
||||
```
|
||||
|
||||
Or add to your development environment in `flake.nix`:
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
openspec.url = "github:Fission-AI/OpenSpec";
|
||||
};
|
||||
|
||||
outputs = { nixpkgs, openspec, ... }: {
|
||||
devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {
|
||||
buildInputs = [ openspec.packages.x86_64-linux.default ];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Verify installation:
|
||||
```bash
|
||||
openspec --version
|
||||
```
|
||||
|
||||
#### Step 2: Initialize OpenSpec in your project
|
||||
|
||||
Navigate to your project directory:
|
||||
@@ -427,6 +463,13 @@ We collect only command names and version to understand usage patterns. No argum
|
||||
- Develop CLI locally: `pnpm run dev` or `pnpm run dev:cli`
|
||||
- Conventional commits (one-line): `type(scope): subject`
|
||||
|
||||
<details>
|
||||
<summary><strong>Maintainers & Advisors</strong></summary>
|
||||
|
||||
See [MAINTAINERS.md](MAINTAINERS.md) for the list of core maintainers and advisors who help guide the project.
|
||||
|
||||
</details>
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -119,7 +119,7 @@ Creates all planning artifacts at once. Use when you have a clear picture of wha
|
||||
```
|
||||
/opsx:apply
|
||||
```
|
||||
Works through tasks, checking them off as you go. **Key difference:** if you discover issues during implementation, you can update your specs, design, or tasks — then continue. No phase gates.
|
||||
Works through tasks, checking them off as you go. **Key difference:** if you discover issues during implementation, you can update your specs, design, or tasks — then continue. No phase gates. If you're juggling multiple changes, you can run `/opsx:apply <name>`; otherwise it should infer from the conversation and prompt you to choose if it can’t tell.
|
||||
|
||||
### Finish up
|
||||
```
|
||||
|
||||
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1767640445,
|
||||
"narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
description = "OpenSpec - AI-native system for spec-driven development";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
default = pkgs.stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "openspec";
|
||||
version = "0.20.0";
|
||||
|
||||
src = ./.;
|
||||
|
||||
pnpmDeps = pkgs.fetchPnpmDeps {
|
||||
inherit (finalAttrs) pname version src;
|
||||
pnpm = pkgs.pnpm_9;
|
||||
fetcherVersion = 3;
|
||||
hash = "sha256-m/7IdY1ou9ljjYAcx3W8AyEJvIZfCBWIWxproQ/INPA=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
nodejs_20
|
||||
npmHooks.npmInstallHook
|
||||
pnpmConfigHook
|
||||
pnpm_9
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
pnpm run build
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
dontNpmPrune = true;
|
||||
|
||||
meta = with pkgs.lib; {
|
||||
description = "AI-native system for spec-driven development";
|
||||
homepage = "https://github.com/Fission-AI/OpenSpec";
|
||||
license = licenses.mit;
|
||||
maintainers = [ ];
|
||||
mainProgram = "openspec";
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
apps = forAllSystems (system: {
|
||||
default = {
|
||||
type = "app";
|
||||
program = "${self.packages.${system}.default}/bin/openspec";
|
||||
};
|
||||
});
|
||||
|
||||
devShells = forAllSystems (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
nodejs_20
|
||||
pnpm_9
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
echo "OpenSpec development environment"
|
||||
echo "Node version: $(node --version)"
|
||||
echo "pnpm version: $(pnpm --version)"
|
||||
echo "Run 'pnpm install' to install dependencies"
|
||||
'';
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
+9
-7
@@ -9,7 +9,7 @@ Instructions for AI coding assistants using OpenSpec for spec-driven development
|
||||
- Pick a unique `change-id`: kebab-case, verb-led (`add-`, `update-`, `remove-`, `refactor-`)
|
||||
- Scaffold: `proposal.md`, `tasks.md`, `design.md` (only if needed), and delta specs per affected capability
|
||||
- Write deltas: use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements`; include at least one `#### Scenario:` per requirement
|
||||
- Validate: `openspec validate [change-id] --strict` and fix issues
|
||||
- Validate: `openspec validate [change-id] --strict --no-interactive` and fix issues
|
||||
- Request approval: Do not start implementation until proposal is approved
|
||||
|
||||
## Three-Stage Workflow
|
||||
@@ -44,7 +44,7 @@ Skip proposal for:
|
||||
1. Review `openspec/project.md`, `openspec list`, and `openspec list --specs` to understand current context.
|
||||
2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, optional `design.md`, and spec deltas under `openspec/changes/<id>/`.
|
||||
3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement.
|
||||
4. Run `openspec validate <id> --strict` and resolve any issues before sharing the proposal.
|
||||
4. Run `openspec validate <id> --strict --no-interactive` and resolve any issues before sharing the proposal.
|
||||
|
||||
### Stage 2: Implementing Changes
|
||||
Track these steps as TODOs and complete them one by one.
|
||||
@@ -61,7 +61,7 @@ After deployment, create separate PR to:
|
||||
- Move `changes/[name]/` → `changes/archive/YYYY-MM-DD-[name]/`
|
||||
- Update `specs/` if capabilities changed
|
||||
- Use `openspec archive <change-id> --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly)
|
||||
- Run `openspec validate --strict` to confirm the archived change passes checks
|
||||
- Run `openspec validate --strict --no-interactive` to confirm the archived change passes checks
|
||||
|
||||
## Before Any Task
|
||||
|
||||
@@ -108,7 +108,7 @@ openspec validate # Bulk validation mode
|
||||
|
||||
# Debugging
|
||||
openspec show [change] --json --deltas-only
|
||||
openspec validate [change] --strict
|
||||
openspec validate [change] --strict --no-interactive
|
||||
```
|
||||
|
||||
### Command Flags
|
||||
@@ -160,6 +160,8 @@ New request?
|
||||
|
||||
2. **Write proposal.md:**
|
||||
```markdown
|
||||
# Change: [Brief description of change]
|
||||
|
||||
## Why
|
||||
[1-2 sentences on problem/opportunity]
|
||||
|
||||
@@ -304,7 +306,7 @@ Example for RENAMED:
|
||||
|
||||
```bash
|
||||
# Always use strict mode for comprehensive checks
|
||||
openspec validate [change] --strict
|
||||
openspec validate [change] --strict --no-interactive
|
||||
|
||||
# Debug delta parsing
|
||||
openspec show [change] --json | jq '.deltas'
|
||||
@@ -341,7 +343,7 @@ Users MUST provide a second factor during login.
|
||||
EOF
|
||||
|
||||
# 4) Validate
|
||||
openspec validate $CHANGE --strict
|
||||
openspec validate $CHANGE --strict --no-interactive
|
||||
```
|
||||
|
||||
## Multi-Capability Example
|
||||
@@ -447,7 +449,7 @@ Only add complexity with:
|
||||
```bash
|
||||
openspec list # What's in progress?
|
||||
openspec show [item] # View details
|
||||
openspec validate --strict # Is it correct?
|
||||
openspec validate --strict --no-interactive # Is it correct?
|
||||
openspec archive <change-id> [--yes|-y] # Mark complete (add --yes for automation)
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
## Why
|
||||
|
||||
Users and agents need a simple way to submit feedback about OpenSpec directly from the CLI. Currently there's no mechanism to collect user feedback, feature requests, or bug reports in a way that enables follow-up conversation. Using GitHub Issues allows us to track feedback, prevent spam via GitHub auth, and enables outreach to users.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `openspec feedback <message>` CLI command
|
||||
- Leverage `gh` CLI for GitHub authentication and issue creation
|
||||
- Add `/feedback` skill for agent-assisted feedback with context enrichment
|
||||
- Ensure cross-platform compatibility (macOS, Linux, Windows)
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: New `cli-feedback` capability
|
||||
- Affected code:
|
||||
- `src/cli/index.ts` - Register feedback command
|
||||
- `src/commands/feedback.ts` - Command implementation using `gh` CLI
|
||||
- `src/core/templates/skill-templates.ts` - Feedback skill template
|
||||
- `src/core/completions/command-registry.ts` - Shell completions
|
||||
- External dependency: Requires `gh` CLI installed and authenticated
|
||||
@@ -0,0 +1,188 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Feedback command
|
||||
|
||||
The system SHALL provide an `openspec feedback` command that creates a GitHub Issue in the openspec repository using the `gh` CLI. The system SHALL use `execFileSync` with argument arrays to prevent shell injection vulnerabilities.
|
||||
|
||||
#### Scenario: Simple feedback submission
|
||||
|
||||
- **WHEN** user executes `openspec feedback "Great tool!"`
|
||||
- **THEN** the system executes `gh issue create` with title "Feedback: Great tool!"
|
||||
- **AND** the issue is created in the openspec repository
|
||||
- **AND** the issue has the `feedback` label
|
||||
- **AND** the system displays the created issue URL
|
||||
|
||||
#### Scenario: Safe command execution
|
||||
|
||||
- **WHEN** submitting feedback via `gh` CLI
|
||||
- **THEN** the system uses `execFileSync` with separate arguments array
|
||||
- **AND** user input is NOT passed through a shell
|
||||
- **AND** shell metacharacters (quotes, backticks, $(), etc.) are treated as literal text
|
||||
|
||||
#### Scenario: Feedback with body
|
||||
|
||||
- **WHEN** user executes `openspec feedback "Title here" --body "Detailed description..."`
|
||||
- **THEN** the system creates a GitHub Issue with the specified title
|
||||
- **AND** the issue body contains the detailed description
|
||||
- **AND** the issue body includes metadata (OpenSpec version, platform, timestamp)
|
||||
|
||||
### Requirement: GitHub CLI dependency
|
||||
|
||||
The system SHALL use `gh` CLI for automatic feedback submission when available, and provide a manual submission fallback when `gh` is not installed or not authenticated. The system SHALL use platform-appropriate commands to detect `gh` CLI availability.
|
||||
|
||||
#### Scenario: Missing gh CLI with fallback
|
||||
|
||||
- **WHEN** user runs `openspec feedback "message"`
|
||||
- **AND** `gh` CLI is not installed (not found in PATH)
|
||||
- **THEN** the system displays warning: "GitHub CLI not found. Manual submission required."
|
||||
- **AND** outputs structured feedback content with delimiters:
|
||||
- "--- FORMATTED FEEDBACK ---"
|
||||
- Title line
|
||||
- Labels line
|
||||
- Body content with metadata
|
||||
- "--- END FEEDBACK ---"
|
||||
- **AND** displays pre-filled GitHub issue URL for manual submission
|
||||
- **AND** exits with zero code (successful fallback)
|
||||
|
||||
#### Scenario: Cross-platform gh CLI detection on Unix
|
||||
|
||||
- **WHEN** system is running on macOS or Linux (platform is 'darwin' or 'linux')
|
||||
- **AND** checking if `gh` CLI is installed
|
||||
- **THEN** the system executes `which gh` command
|
||||
|
||||
#### Scenario: Cross-platform gh CLI detection on Windows
|
||||
|
||||
- **WHEN** system is running on Windows (platform is 'win32')
|
||||
- **AND** checking if `gh` CLI is installed
|
||||
- **THEN** the system executes `where gh` command
|
||||
|
||||
#### Scenario: Unauthenticated gh CLI with fallback
|
||||
|
||||
- **WHEN** user runs `openspec feedback "message"`
|
||||
- **AND** `gh` CLI is installed but not authenticated
|
||||
- **THEN** the system displays warning: "GitHub authentication required. Manual submission required."
|
||||
- **AND** outputs structured feedback content (same format as missing gh CLI scenario)
|
||||
- **AND** displays pre-filled GitHub issue URL for manual submission
|
||||
- **AND** displays authentication instructions: "To auto-submit in the future: gh auth login"
|
||||
- **AND** exits with zero code (successful fallback)
|
||||
|
||||
#### Scenario: Authenticated gh CLI
|
||||
|
||||
- **WHEN** user runs `openspec feedback "message"`
|
||||
- **AND** `gh auth status` returns success (authenticated)
|
||||
- **THEN** the system proceeds with feedback submission
|
||||
|
||||
### Requirement: Issue metadata
|
||||
|
||||
The system SHALL include relevant metadata in the GitHub Issue body.
|
||||
|
||||
#### Scenario: Standard metadata
|
||||
|
||||
- **WHEN** creating a GitHub Issue for feedback
|
||||
- **THEN** the issue body includes:
|
||||
- OpenSpec CLI version
|
||||
- Platform (darwin, linux, win32)
|
||||
- Submission timestamp
|
||||
- Separator line: "---\nSubmitted via OpenSpec CLI"
|
||||
|
||||
#### Scenario: Windows platform metadata
|
||||
|
||||
- **WHEN** creating a GitHub Issue for feedback on Windows
|
||||
- **THEN** the issue body includes "Platform: win32"
|
||||
- **AND** all platform detection uses Node.js `os.platform()` API
|
||||
|
||||
#### Scenario: No sensitive metadata
|
||||
|
||||
- **WHEN** creating a GitHub Issue for feedback
|
||||
- **THEN** the issue body does NOT include:
|
||||
- File paths from user's system
|
||||
- Project names or directory names
|
||||
- Environment variables
|
||||
- IP addresses
|
||||
|
||||
### Requirement: Feedback always works
|
||||
|
||||
The system SHALL allow feedback submission regardless of telemetry settings.
|
||||
|
||||
#### Scenario: Feedback with telemetry disabled
|
||||
|
||||
- **WHEN** user has disabled telemetry via `OPENSPEC_TELEMETRY=0`
|
||||
- **AND** user runs `openspec feedback "message"`
|
||||
- **THEN** the feedback is still submitted via `gh` CLI
|
||||
- **AND** telemetry events are not sent
|
||||
|
||||
#### Scenario: Feedback in CI environment
|
||||
|
||||
- **WHEN** `CI=true` is set in the environment
|
||||
- **AND** user runs `openspec feedback "message"`
|
||||
- **THEN** the feedback submission proceeds normally (if `gh` is available and authenticated)
|
||||
|
||||
### Requirement: Error handling
|
||||
|
||||
The system SHALL handle feedback submission errors gracefully.
|
||||
|
||||
#### Scenario: gh CLI execution failure
|
||||
|
||||
- **WHEN** `gh issue create` command fails
|
||||
- **THEN** the system displays the error output from `gh` CLI
|
||||
- **AND** exits with the same exit code as `gh`
|
||||
|
||||
#### Scenario: Network failure
|
||||
|
||||
- **WHEN** `gh` CLI reports network connectivity issues
|
||||
- **THEN** the system displays the error message from `gh`
|
||||
- **AND** suggests checking network connectivity
|
||||
- **AND** exits with non-zero code
|
||||
|
||||
### Requirement: Feedback skill for agents
|
||||
|
||||
The system SHALL provide a `/feedback` skill that guides agents through collecting and submitting user feedback.
|
||||
|
||||
#### Scenario: Agent-initiated feedback
|
||||
|
||||
- **WHEN** user invokes `/feedback` in an agent conversation
|
||||
- **THEN** the agent gathers context from the conversation
|
||||
- **AND** drafts a feedback issue with enriched content
|
||||
- **AND** anonymizes sensitive information
|
||||
- **AND** presents the draft to the user for approval
|
||||
- **AND** submits via `openspec feedback` command on user confirmation
|
||||
|
||||
#### Scenario: Context enrichment
|
||||
|
||||
- **WHEN** agent drafts feedback
|
||||
- **THEN** the agent includes relevant context such as:
|
||||
- What task was being performed
|
||||
- What worked well or poorly
|
||||
- Specific friction points or praise
|
||||
|
||||
#### Scenario: Anonymization
|
||||
|
||||
- **WHEN** agent drafts feedback
|
||||
- **THEN** the agent removes or replaces:
|
||||
- File paths with `<path>` or generic descriptions
|
||||
- API keys, tokens, secrets with `<redacted>`
|
||||
- Company/organization names with `<company>`
|
||||
- Personal names with `<user>`
|
||||
- Specific URLs with `<url>` unless public/relevant
|
||||
|
||||
#### Scenario: User confirmation required
|
||||
|
||||
- **WHEN** agent has drafted feedback
|
||||
- **THEN** the agent MUST show the complete draft to the user
|
||||
- **AND** ask for explicit approval before submitting
|
||||
- **AND** allow the user to request modifications
|
||||
- **AND** only submit after user confirms
|
||||
|
||||
### Requirement: Shell completions
|
||||
|
||||
The system SHALL provide shell completions for the feedback command.
|
||||
|
||||
#### Scenario: Command completion
|
||||
|
||||
- **WHEN** user types `openspec fee<TAB>`
|
||||
- **THEN** the shell completes to `openspec feedback`
|
||||
|
||||
#### Scenario: Flag completion
|
||||
|
||||
- **WHEN** user types `openspec feedback "msg" --<TAB>`
|
||||
- **THEN** the shell suggests available flags (`--body`)
|
||||
@@ -0,0 +1,30 @@
|
||||
## 1. Feedback Command
|
||||
|
||||
- [x] 1.1 Create `src/commands/feedback.ts` with command implementation
|
||||
- [x] 1.2 Check `gh` CLI availability using platform-appropriate command (`which` on Unix/macOS, `where` on Windows)
|
||||
- [x] 1.3 Check GitHub auth status with `gh auth status`
|
||||
- [x] 1.4 Execute `gh issue create` with formatted title and body using `execFileSync` to prevent shell injection
|
||||
- [x] 1.5 Display issue URL returned by `gh` CLI
|
||||
- [x] 1.6 Register `feedback <message>` command in `src/cli/index.ts`
|
||||
- [x] 1.7 Ensure cross-platform compatibility (macOS, Linux, Windows)
|
||||
|
||||
## 2. Shell Completions
|
||||
|
||||
- [x] 2.1 Add `feedback` command to command registry
|
||||
- [x] 2.2 Regenerate completion scripts for all shells
|
||||
|
||||
## 3. Feedback Skill
|
||||
|
||||
- [x] 3.1 Create feedback skill template in `skill-templates.ts`
|
||||
- [x] 3.2 Document context gathering workflow
|
||||
- [x] 3.3 Document anonymization rules
|
||||
- [x] 3.4 Document user confirmation flow
|
||||
|
||||
## 4. Testing
|
||||
|
||||
- [x] 4.1 Add unit tests for feedback command (mock `gh` subprocess calls)
|
||||
- [x] 4.2 Add integration test for full feedback flow with mocked `gh` CLI
|
||||
- [x] 4.3 Test error handling for missing `gh` CLI
|
||||
- [x] 4.4 Test error handling for unauthenticated `gh` session
|
||||
- [x] 4.5 Test cross-platform `gh` CLI detection (verify `which` on Unix, `where` on Windows)
|
||||
- [x] 4.6 Test platform metadata includes correct value for Windows (win32)
|
||||
@@ -0,0 +1,96 @@
|
||||
# Design: Add /opsx:verify Skill
|
||||
|
||||
## Architecture Decision: Dynamic Generation via Setup Command
|
||||
|
||||
### Context
|
||||
|
||||
All existing opsx experimental skills (explore, new, continue, apply, ff, sync, archive) are dynamically generated when users run `openspec artifact-experimental-setup`. They are not manually created files checked into the repository.
|
||||
|
||||
### Decision
|
||||
|
||||
**Integrate verify into the existing artifact-experimental-setup system rather than creating static skill files.**
|
||||
|
||||
### Rationale
|
||||
|
||||
1. **Consistency**: All 7 existing opsx skills follow this pattern. Adding verify as the 8th skill should follow the same architecture.
|
||||
|
||||
2. **Maintainability**: Template functions in `skill-templates.ts` are the single source of truth. Changes to skill definitions automatically propagate to all users when they re-run setup.
|
||||
|
||||
3. **Distribution**: Users get the verify skill automatically when running `openspec artifact-experimental-setup`, just like all other opsx skills. No special installation steps needed.
|
||||
|
||||
4. **Versioning**: Skills are generated from the installed npm package version, ensuring consistency between CLI version and skill behavior.
|
||||
|
||||
### Implementation Approach
|
||||
|
||||
#### 1. Template Functions
|
||||
|
||||
Add two template functions to `src/core/templates/skill-templates.ts`:
|
||||
|
||||
```typescript
|
||||
export function getVerifyChangeSkillTemplate(): SkillTemplate
|
||||
export function getOpsxVerifyCommandTemplate(): CommandTemplate
|
||||
```
|
||||
|
||||
These return the skill definition (for Agent Skills) and slash command definition (for explicit invocation).
|
||||
|
||||
#### 2. Setup Integration
|
||||
|
||||
Update `artifactExperimentalSetupCommand()` in `src/commands/artifact-workflow.ts`:
|
||||
|
||||
- Import both template functions
|
||||
- Add verify to the `skills` array (position 8)
|
||||
- Add verify to the `commands` array (position 8)
|
||||
- Update help text to list `/opsx:verify`
|
||||
|
||||
#### 3. Generated Artifacts
|
||||
|
||||
When users run `openspec artifact-experimental-setup`, the command creates:
|
||||
|
||||
- `.claude/skills/openspec-verify-change/SKILL.md` - Agent Skills format
|
||||
- `.claude/commands/opsx/verify.md` - Slash command format
|
||||
|
||||
Both are generated from the template functions, with YAML frontmatter automatically added.
|
||||
|
||||
### Alternatives Considered
|
||||
|
||||
**Alternative 1: Static skill files in repository**
|
||||
|
||||
Create `.claude/skills/openspec-verify-change/SKILL.md` as a static file in the OpenSpec repository.
|
||||
|
||||
**Rejected because:**
|
||||
- Inconsistent with all other opsx skills
|
||||
- Requires users to manually copy/update files
|
||||
- Versioning becomes complicated (repo version vs installed package version)
|
||||
- Breaks the established pattern
|
||||
|
||||
**Alternative 2: Separate verify setup command**
|
||||
|
||||
Add `openspec setup-verify` as a separate command.
|
||||
|
||||
**Rejected because:**
|
||||
- Fragments the setup experience
|
||||
- Users would need to run multiple commands
|
||||
- Doesn't scale if we add more skills in the future
|
||||
- Goes against the "setup once, get everything" philosophy
|
||||
|
||||
### Trade-offs
|
||||
|
||||
**Advantages:**
|
||||
- Consistent with existing architecture
|
||||
- Zero additional setup burden for users
|
||||
- Easy to update and maintain
|
||||
- Automatic version compatibility
|
||||
|
||||
**Disadvantages:**
|
||||
- Slightly more complex initial implementation (template functions + integration)
|
||||
- Requires understanding the setup system (but that's already documented)
|
||||
|
||||
### Verification
|
||||
|
||||
The implementation correctly follows this design if:
|
||||
|
||||
1. Both template functions exist in `skill-templates.ts`
|
||||
2. Verify appears in both skills and commands arrays in `artifact-workflow.ts`
|
||||
3. Help text mentions `/opsx:verify`
|
||||
4. Running `openspec artifact-experimental-setup` generates both skill and command files
|
||||
5. Build succeeds with no TypeScript errors
|
||||
@@ -0,0 +1,48 @@
|
||||
# Change: Add /opsx:verify Skill
|
||||
|
||||
## Why
|
||||
|
||||
Users need a way to validate that their implementation actually matches what was requested before archiving a change. Currently, there's no systematic way to check:
|
||||
- Whether all tasks are truly complete
|
||||
- Whether the implementation covers all spec requirements and scenarios
|
||||
- Whether the implementation follows the design decisions
|
||||
- Whether the code is coherent and makes sense
|
||||
|
||||
A user requested: "Can we get a :verify that will ensure that the implementation matches what was requested?"
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `getVerifyChangeSkillTemplate()` function to `skill-templates.ts`
|
||||
- Add `getOpsxVerifyCommandTemplate()` function to `skill-templates.ts`
|
||||
- Integrate verify skill into `artifactExperimentalSetupCommand` in `artifact-workflow.ts`
|
||||
- Add verify to the skills and commands arrays in the setup command
|
||||
- Update help text to include `/opsx:verify` in the list of available commands
|
||||
- Create `opsx-verify-skill` capability spec
|
||||
|
||||
## Verification Dimensions
|
||||
|
||||
The skill verifies across three dimensions:
|
||||
|
||||
1. **Completeness** - Are all tasks done? Are all specs addressed?
|
||||
2. **Correctness** - Does the implementation match specs? Are scenarios covered?
|
||||
3. **Coherence** - Does the implementation make sense? Does it follow design.md?
|
||||
|
||||
## Output Format
|
||||
|
||||
Produces a prioritized report with:
|
||||
- Summary scorecard (tasks, specs, design adherence)
|
||||
- Critical issues first (must fix before archive)
|
||||
- Warnings second (should fix)
|
||||
- Suggestions third (nice to have)
|
||||
- Actionable fix recommendations for each issue
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: New `opsx-verify-skill` spec
|
||||
- Affected code:
|
||||
- `src/core/templates/skill-templates.ts` - Added 2 new template functions
|
||||
- `src/commands/artifact-workflow.ts` - Integrated verify into experimental setup
|
||||
- Generated artifacts: When users run `openspec artifact-experimental-setup`:
|
||||
- Creates `.claude/skills/openspec-verify-change/SKILL.md`
|
||||
- Creates `.claude/commands/opsx/verify.md`
|
||||
- Related skills: Works alongside `/opsx:apply` and before `/opsx:archive`
|
||||
@@ -0,0 +1,190 @@
|
||||
# opsx-verify-skill Specification
|
||||
|
||||
## Purpose
|
||||
Defines the agent skill for verifying that implementation matches change artifacts (specs, tasks, design).
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Verify Skill Invocation
|
||||
The system SHALL provide an `/opsx:verify` skill that validates implementation against change artifacts.
|
||||
|
||||
#### Scenario: Verify with change name provided
|
||||
- **WHEN** agent executes `/opsx:verify <change-name>`
|
||||
- **THEN** the agent verifies implementation for that specific change
|
||||
- **AND** produces a verification report
|
||||
|
||||
#### Scenario: Verify without change name
|
||||
- **WHEN** agent executes `/opsx:verify` without a change name
|
||||
- **THEN** the agent prompts user to select from available changes
|
||||
- **AND** shows only changes that have implementation tasks
|
||||
|
||||
#### Scenario: Change has no tasks
|
||||
- **WHEN** selected change has no tasks.md or tasks are empty
|
||||
- **THEN** the agent reports "No tasks to verify"
|
||||
- **AND** suggests running `/opsx:continue` to create tasks
|
||||
|
||||
### Requirement: Completeness Verification
|
||||
The agent SHALL verify that all required work has been completed.
|
||||
|
||||
#### Scenario: Task completion check
|
||||
- **WHEN** verifying completeness
|
||||
- **THEN** the agent reads tasks.md
|
||||
- **AND** counts tasks marked `- [x]` (complete) vs `- [ ]` (incomplete)
|
||||
- **AND** reports completion status with specific incomplete tasks listed
|
||||
|
||||
#### Scenario: Spec coverage check
|
||||
- **WHEN** verifying completeness
|
||||
- **AND** delta specs exist in `openspec/changes/<name>/specs/`
|
||||
- **THEN** the agent extracts all requirements from delta specs
|
||||
- **AND** searches codebase for implementation of each requirement
|
||||
- **AND** reports which requirements appear to have implementation vs which are missing
|
||||
|
||||
#### Scenario: All tasks complete
|
||||
- **WHEN** all tasks are marked complete
|
||||
- **THEN** report "Tasks: N/N complete"
|
||||
- **AND** mark completeness dimension as passed
|
||||
|
||||
#### Scenario: Incomplete tasks found
|
||||
- **WHEN** some tasks are incomplete
|
||||
- **THEN** report "Tasks: X/N complete"
|
||||
- **AND** list each incomplete task
|
||||
- **AND** mark as CRITICAL issue
|
||||
- **AND** suggest: "Complete remaining tasks or mark as done if already implemented"
|
||||
|
||||
### Requirement: Correctness Verification
|
||||
The agent SHALL verify that implementation matches the specifications.
|
||||
|
||||
#### Scenario: Requirement implementation mapping
|
||||
- **WHEN** verifying correctness
|
||||
- **THEN** for each requirement in delta specs:
|
||||
- Search codebase for implementation
|
||||
- Identify relevant files and line numbers
|
||||
- Assess whether implementation satisfies the requirement
|
||||
|
||||
#### Scenario: Scenario coverage check
|
||||
- **WHEN** verifying correctness
|
||||
- **THEN** for each scenario in delta specs:
|
||||
- Check if the scenario's conditions are handled in code
|
||||
- Check if tests exist that cover the scenario
|
||||
- Report coverage status
|
||||
|
||||
#### Scenario: Implementation matches spec
|
||||
- **WHEN** implementation appears to satisfy a requirement
|
||||
- **THEN** report which files/lines implement it
|
||||
- **AND** mark requirement as covered
|
||||
|
||||
#### Scenario: Implementation diverges from spec
|
||||
- **WHEN** implementation exists but doesn't match spec intent
|
||||
- **THEN** report the divergence as WARNING
|
||||
- **AND** explain what differs
|
||||
- **AND** suggest: either update implementation or update spec to match reality
|
||||
|
||||
#### Scenario: Missing implementation
|
||||
- **WHEN** no implementation found for a requirement
|
||||
- **THEN** report as CRITICAL issue
|
||||
- **AND** suggest: "Implement requirement X" with guidance on what's needed
|
||||
|
||||
### Requirement: Coherence Verification
|
||||
The agent SHALL verify that implementation is sensible and follows design decisions.
|
||||
|
||||
#### Scenario: Design.md adherence check
|
||||
- **WHEN** verifying coherence
|
||||
- **AND** design.md exists for the change
|
||||
- **THEN** extract key decisions from design.md
|
||||
- **AND** verify implementation follows those decisions
|
||||
- **AND** report any deviations
|
||||
|
||||
#### Scenario: No design.md
|
||||
- **WHEN** verifying coherence
|
||||
- **AND** no design.md exists
|
||||
- **THEN** skip design adherence check
|
||||
- **AND** note "No design.md to verify against"
|
||||
|
||||
#### Scenario: Design decision followed
|
||||
- **WHEN** implementation follows a design decision
|
||||
- **THEN** report as confirmed
|
||||
- **AND** cite evidence from code
|
||||
|
||||
#### Scenario: Design decision violated
|
||||
- **WHEN** implementation contradicts a design decision
|
||||
- **THEN** report as WARNING
|
||||
- **AND** explain the contradiction
|
||||
- **AND** suggest: either update implementation or update design.md
|
||||
|
||||
#### Scenario: Code pattern consistency
|
||||
- **WHEN** verifying coherence
|
||||
- **THEN** check if new code follows existing project patterns
|
||||
- **AND** flag any significant deviations as suggestions
|
||||
|
||||
### Requirement: Verification Report Format
|
||||
The agent SHALL produce a structured, prioritized report.
|
||||
|
||||
#### Scenario: Report summary
|
||||
- **WHEN** verification completes
|
||||
- **THEN** display summary scorecard:
|
||||
```
|
||||
## Verification Report: <change-name>
|
||||
|
||||
### Summary
|
||||
| Dimension | Status |
|
||||
|--------------|----------|
|
||||
| Completeness | X/Y |
|
||||
| Correctness | X/Y |
|
||||
| Coherence | Followed |
|
||||
```
|
||||
|
||||
#### Scenario: Issue prioritization
|
||||
- **WHEN** issues are found
|
||||
- **THEN** group and display in priority order:
|
||||
1. CRITICAL - Must fix before archive (missing implementation, incomplete tasks)
|
||||
2. WARNING - Should fix (divergence from spec/design, missing tests)
|
||||
3. SUGGESTION - Nice to fix (pattern inconsistencies, minor improvements)
|
||||
|
||||
#### Scenario: Actionable recommendations
|
||||
- **WHEN** reporting an issue
|
||||
- **THEN** include specific, actionable fix recommendation
|
||||
- **AND** reference relevant files and line numbers where applicable
|
||||
- **AND** avoid vague suggestions like "consider reviewing"
|
||||
|
||||
#### Scenario: All checks pass
|
||||
- **WHEN** no issues found across all dimensions
|
||||
- **THEN** display:
|
||||
```
|
||||
All checks passed. Ready for archive.
|
||||
```
|
||||
|
||||
#### Scenario: Critical issues found
|
||||
- **WHEN** CRITICAL issues exist
|
||||
- **THEN** display:
|
||||
```
|
||||
X critical issue(s) found. Fix before archiving.
|
||||
```
|
||||
- **AND** do NOT suggest running archive
|
||||
|
||||
#### Scenario: Only warnings/suggestions
|
||||
- **WHEN** no CRITICAL issues but warnings exist
|
||||
- **THEN** display:
|
||||
```
|
||||
No critical issues. Y warning(s) to consider.
|
||||
Ready for archive (with noted improvements).
|
||||
```
|
||||
|
||||
### Requirement: Flexible Artifact Handling
|
||||
The agent SHALL gracefully handle changes with varying artifact completeness.
|
||||
|
||||
#### Scenario: Minimal change (tasks only)
|
||||
- **WHEN** change has only tasks.md
|
||||
- **THEN** verify task completion only
|
||||
- **AND** skip spec and design checks
|
||||
- **AND** note which checks were skipped
|
||||
|
||||
#### Scenario: Change with specs but no design
|
||||
- **WHEN** change has tasks.md and delta specs but no design.md
|
||||
- **THEN** verify completeness and correctness
|
||||
- **AND** skip design adherence
|
||||
- **AND** still check code coherence against project patterns
|
||||
|
||||
#### Scenario: Full change (all artifacts)
|
||||
- **WHEN** change has proposal, design, specs, and tasks
|
||||
- **THEN** perform all verification checks
|
||||
- **AND** cross-reference artifacts for consistency
|
||||
@@ -0,0 +1,15 @@
|
||||
# Tasks: Add /opsx:verify Skill
|
||||
|
||||
## 1. Skill Template Functions
|
||||
- [x] 1.1 Add `getVerifyChangeSkillTemplate()` to skill-templates.ts
|
||||
- [x] 1.2 Add `getOpsxVerifyCommandTemplate()` to skill-templates.ts
|
||||
|
||||
## 2. Integration with artifact-experimental-setup
|
||||
- [x] 2.1 Import verify template functions in artifact-workflow.ts
|
||||
- [x] 2.2 Add verify to skills array in artifactExperimentalSetupCommand
|
||||
- [x] 2.3 Add verify to commands array in artifactExperimentalSetupCommand
|
||||
- [x] 2.4 Add verify to help text output
|
||||
|
||||
## 3. Verification (Build & Test)
|
||||
- [x] 3.1 Verify TypeScript compilation succeeds
|
||||
- [x] 3.2 Verify all 8 skills are now included (was 7, now 8)
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-07
|
||||
@@ -0,0 +1,94 @@
|
||||
## Context
|
||||
|
||||
OpenSpec is a TypeScript CLI tool using pnpm for dependency management. The project requires Node.js ≥20.19.0. Nix uses its own build system that needs to understand how to fetch dependencies and build the project reproducibly.
|
||||
|
||||
The Nix ecosystem has specific patterns for packaging Node.js/pnpm projects that differ from the traditional npm ecosystem.
|
||||
|
||||
## Goals
|
||||
|
||||
- Enable OpenSpec to be run directly via `nix run github:Fission-AI/OpenSpec`
|
||||
- Support all major platforms (Linux x86/ARM, macOS x86/ARM)
|
||||
- Use existing pnpm-lock.yaml for reproducible builds
|
||||
- Provide development environment for Nix users
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Replace existing npm/pnpm publishing workflow
|
||||
- Publish to nixpkgs (can be done later as separate effort)
|
||||
- Support Windows (Nix doesn't run natively on Windows)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Use stdenv.mkDerivation instead of buildNpmPackage
|
||||
|
||||
**Decision**: Package OpenSpec using `stdenv.mkDerivation` with pnpm hooks.
|
||||
|
||||
**Rationale**: The zigbee2mqtt package in nixpkgs demonstrates the current best practice for pnpm projects. Using `buildNpmPackage` with pnpm requires complex configuration, while `mkDerivation` with the right hooks is more straightforward and better supported.
|
||||
|
||||
**Alternative considered**: Using `buildNpmPackage` with `npmConfigHook = pkgs.pnpmConfigHook` - this is the older pattern and causes issues with dependency fetching.
|
||||
|
||||
### Use fetchPnpmDeps with explicit pnpm version
|
||||
|
||||
**Decision**: Use `pkgs.fetchPnpmDeps` with `pnpm = pkgs.pnpm_9` and `fetcherVersion = 3`.
|
||||
|
||||
**Rationale**:
|
||||
- pnpm lockfile version 9.0 requires fetcherVersion 3
|
||||
- Explicit pnpm_9 ensures consistency between fetch and build
|
||||
- This is the documented way to handle pnpm projects in nixpkgs
|
||||
|
||||
### Multi-platform support without flake-utils
|
||||
|
||||
**Decision**: Implement multi-platform support using plain Nix with `nixpkgs.lib.genAttrs`.
|
||||
|
||||
**Rationale**: Per user request, avoid extra dependencies. The `genAttrs` pattern is simple and well-understood in the Nix community.
|
||||
|
||||
### Node.js 20 instead of latest
|
||||
|
||||
**Decision**: Pin to nodejs_20 to match package.json engines requirement.
|
||||
|
||||
**Rationale**: Ensures consistency with development environment and npm package requirements. Avoids potential compatibility issues with newer Node versions.
|
||||
|
||||
## Key Implementation Details
|
||||
|
||||
### Dependency Hash Management
|
||||
|
||||
The `pnpmDeps.hash` field must be updated whenever dependencies change. The workflow:
|
||||
1. Set hash to fake value (all zeros)
|
||||
2. Run `nix build`
|
||||
3. Nix fails with actual hash
|
||||
4. Update flake.nix with correct hash
|
||||
|
||||
This is standard Nix workflow for fixed-output derivations.
|
||||
|
||||
### Build Inputs
|
||||
|
||||
Required nativeBuildInputs:
|
||||
- `nodejs_20` - runtime
|
||||
- `npmHooks.npmInstallHook` - handles installation phase
|
||||
- `pnpmConfigHook` - configures pnpm environment
|
||||
- `pnpm_9` - pnpm executable
|
||||
|
||||
The `dontNpmPrune = true` is important to keep all dependencies after build.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk]** Hash needs updating when dependencies change → **Mitigation**: Document this clearly; error message from Nix provides correct hash
|
||||
|
||||
**[Risk]** Nix builds might lag behind npm releases → **Mitigation**: This is fine; Nix users can still use npm if they need bleeding edge
|
||||
|
||||
**[Trade-off]** Additional maintenance burden for hash updates → **Benefit**: Better experience for Nix ecosystem users
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Add flake.nix to repository
|
||||
2. Test builds on multiple platforms (can use GitHub Actions with Nix)
|
||||
3. Update README with Nix installation instructions
|
||||
4. Optionally add to CI pipeline to catch hash mismatches early
|
||||
|
||||
No breaking changes - this is purely additive.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should we add automatic hash updating to CI? (Could use nix-update-script)
|
||||
- Should we submit to nixpkgs after validation? (Separate decision)
|
||||
- Do we want to support older Node versions in flake? (Probably no - stick to package.json requirement)
|
||||
@@ -0,0 +1,25 @@
|
||||
## Why
|
||||
|
||||
OpenSpec users on NixOS or using the Nix package manager cannot easily install or run OpenSpec without going through npm. Adding a Nix flake makes OpenSpec a first-class citizen in the Nix ecosystem, enabling users to run `nix run github:Fission-AI/OpenSpec -- init` or include OpenSpec in their development environments declaratively.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `flake.nix` to repository root with multi-platform support (x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin)
|
||||
- Package uses pnpm for dependency management (matching existing development workflow)
|
||||
- Support both direct execution via `nix run` and installation via `nix profile install`
|
||||
- Provide dev shell for contributors using Nix
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `nix-flake-support`: Nix flake configuration for building and running OpenSpec
|
||||
|
||||
### Modified Capabilities
|
||||
- None
|
||||
|
||||
## Impact
|
||||
|
||||
- **New files**: `flake.nix` in repository root
|
||||
- **Documentation**: Should add installation instructions for Nix users
|
||||
- **CI/CD**: Could add flake checking to CI pipeline (optional)
|
||||
- **Maintenance**: Requires updating pnpmDeps hash when dependencies change
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Multi-platform Nix flake
|
||||
The system SHALL provide a Nix flake that builds OpenSpec for multiple platforms.
|
||||
|
||||
#### Scenario: Build on Linux x86_64
|
||||
- **WHEN** user runs `nix build` on x86_64-linux system
|
||||
- **THEN** system builds OpenSpec package successfully
|
||||
- **AND** package includes the `openspec` binary
|
||||
|
||||
#### Scenario: Build on macOS ARM
|
||||
- **WHEN** user runs `nix build` on aarch64-darwin system
|
||||
- **THEN** system builds OpenSpec package successfully
|
||||
- **AND** package includes the `openspec` binary
|
||||
|
||||
#### Scenario: Build on Linux ARM
|
||||
- **WHEN** user runs `nix build` on aarch64-linux system
|
||||
- **THEN** system builds OpenSpec package successfully
|
||||
|
||||
#### Scenario: Build on macOS x86_64
|
||||
- **WHEN** user runs `nix build` on x86_64-darwin system
|
||||
- **THEN** system builds OpenSpec package successfully
|
||||
|
||||
### Requirement: Direct execution via nix run
|
||||
The system SHALL allow users to run OpenSpec directly from GitHub without installing.
|
||||
|
||||
#### Scenario: Run init command from GitHub
|
||||
- **WHEN** user runs `nix run github:Fission-AI/OpenSpec -- init`
|
||||
- **THEN** system downloads and builds OpenSpec
|
||||
- **AND** executes `openspec init` command
|
||||
|
||||
#### Scenario: Run any OpenSpec command
|
||||
- **WHEN** user runs `nix run github:Fission-AI/OpenSpec -- <command> <args>`
|
||||
- **THEN** system executes `openspec <command> <args>`
|
||||
|
||||
### Requirement: pnpm dependency management
|
||||
The system SHALL use pnpm for building OpenSpec in the Nix flake.
|
||||
|
||||
#### Scenario: Fetch dependencies with pnpm
|
||||
- **WHEN** Nix builds the package
|
||||
- **THEN** system uses `fetchPnpmDeps` to download dependencies
|
||||
- **AND** uses pnpm-lock.yaml for reproducible builds
|
||||
- **AND** uses fetcherVersion 3 for lockfile version 9.0
|
||||
|
||||
#### Scenario: Build with pnpm
|
||||
- **WHEN** Nix runs the build phase
|
||||
- **THEN** system executes `pnpm run build`
|
||||
- **AND** produces dist directory with compiled TypeScript
|
||||
|
||||
### Requirement: Node.js version compatibility
|
||||
The system SHALL use Node.js 20 as specified in package.json engines field.
|
||||
|
||||
#### Scenario: Build with correct Node version
|
||||
- **WHEN** Nix builds OpenSpec
|
||||
- **THEN** system uses nodejs_20 from nixpkgs
|
||||
- **AND** build succeeds without version compatibility errors
|
||||
|
||||
### Requirement: Development shell
|
||||
The system SHALL provide a Nix development shell for contributors.
|
||||
|
||||
#### Scenario: Enter dev shell
|
||||
- **WHEN** user runs `nix develop` in OpenSpec repository
|
||||
- **THEN** system provides shell with nodejs_20 and pnpm_9
|
||||
- **AND** displays welcome message with versions
|
||||
- **AND** provides instructions to run `pnpm install`
|
||||
|
||||
### Requirement: Proper binary installation
|
||||
The system SHALL install the openspec binary correctly.
|
||||
|
||||
#### Scenario: Binary in PATH
|
||||
- **WHEN** package is built or installed
|
||||
- **THEN** `openspec` binary is available in `$out/bin/openspec`
|
||||
- **AND** binary is executable
|
||||
- **AND** binary can be invoked without full path when installed
|
||||
|
||||
#### Scenario: Binary executes correctly
|
||||
- **WHEN** user runs the installed `openspec` command
|
||||
- **THEN** system executes the CLI entry point
|
||||
- **AND** all subcommands work correctly
|
||||
@@ -0,0 +1,65 @@
|
||||
## 1. Create Flake Structure
|
||||
|
||||
- [x] 1.1 Create flake.nix in repository root
|
||||
- [x] 1.2 Define inputs (nixpkgs only, no flake-utils)
|
||||
- [x] 1.3 Set up supportedSystems list (4 platforms)
|
||||
- [x] 1.4 Create forAllSystems helper function
|
||||
|
||||
## 2. Configure Package Build
|
||||
|
||||
- [x] 2.1 Set up stdenv.mkDerivation with finalAttrs pattern
|
||||
- [x] 2.2 Configure pnpmDeps with fetchPnpmDeps
|
||||
- [x] 2.3 Set pnpm = pnpm_9 and fetcherVersion = 3
|
||||
- [x] 2.4 Add placeholder hash (all zeros)
|
||||
- [x] 2.5 Configure nativeBuildInputs (nodejs_20, hooks, pnpm_9)
|
||||
- [x] 2.6 Set dontNpmPrune = true
|
||||
|
||||
## 3. Define Build Phase
|
||||
|
||||
- [x] 3.1 Add buildPhase with runHook preBuild
|
||||
- [x] 3.2 Add pnpm run build command
|
||||
- [x] 3.3 Add runHook postBuild
|
||||
|
||||
## 4. Configure Installation
|
||||
|
||||
- [x] 4.1 Let npmInstallHook handle installation automatically
|
||||
- [x] 4.2 Verify binary ends up in $out/bin/openspec
|
||||
|
||||
## 5. Add Metadata
|
||||
|
||||
- [x] 5.1 Set meta.description
|
||||
- [x] 5.2 Set meta.homepage
|
||||
- [x] 5.3 Set meta.license (MIT)
|
||||
- [x] 5.4 Set meta.mainProgram = "openspec"
|
||||
|
||||
## 6. Configure App Entry Point
|
||||
|
||||
- [x] 6.1 Add apps output with forAllSystems
|
||||
- [x] 6.2 Set default app to openspec binary
|
||||
- [x] 6.3 Test that nix run works
|
||||
|
||||
## 7. Add Development Shell
|
||||
|
||||
- [x] 7.1 Add devShells output with forAllSystems
|
||||
- [x] 7.2 Include nodejs_20 and pnpm_9 in buildInputs
|
||||
- [x] 7.3 Add shellHook with welcome message and instructions
|
||||
|
||||
## 8. Get Correct Dependency Hash
|
||||
|
||||
- [x] 8.1 Run nix build to trigger hash mismatch
|
||||
- [x] 8.2 Copy correct hash from error message
|
||||
- [x] 8.3 Update pnpmDeps.hash in flake.nix
|
||||
- [x] 8.4 Verify build succeeds
|
||||
|
||||
## 9. Testing
|
||||
|
||||
- [x] 9.1 Test `nix build` on x86_64-linux
|
||||
- [x] 9.2 Test `nix run . -- --version` works
|
||||
- [x] 9.3 Test `nix develop` provides correct environment
|
||||
- [ ] 9.4 Test on macOS if available
|
||||
- [ ] 9.5 Test `nix run github:Fission-AI/OpenSpec -- init` after merge to main
|
||||
|
||||
## 10. Documentation
|
||||
|
||||
- [x] 10.1 Add Nix installation section to README
|
||||
- [x] 10.2 Include example commands for common Nix workflows in README
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-09
|
||||
@@ -0,0 +1,117 @@
|
||||
## Context
|
||||
|
||||
The Nix flake added in the previous change requires manual maintenance when:
|
||||
1. Package version changes (must update flake.nix version field)
|
||||
2. Dependencies change (must update pnpmDeps hash)
|
||||
|
||||
Currently this requires maintainers to:
|
||||
- Manually edit flake.nix version
|
||||
- Set placeholder hash
|
||||
- Run nix build to get error
|
||||
- Copy hash from error message
|
||||
- Update flake.nix again
|
||||
- Verify build works
|
||||
|
||||
This is tedious and error-prone, especially for maintainers unfamiliar with Nix.
|
||||
|
||||
## Goals
|
||||
|
||||
- Automate version and hash updates for flake.nix
|
||||
- Make script idempotent and safe to run multiple times
|
||||
- Provide clear feedback during execution
|
||||
- Integrate easily into release workflow
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Automatically commit changes (maintainer decides when to commit)
|
||||
- Support non-pnpm package managers
|
||||
- Handle complex Nix configurations beyond OpenSpec's use case
|
||||
|
||||
## Decisions
|
||||
|
||||
### Use Bash instead of Node.js script
|
||||
|
||||
**Decision**: Implement as bash script rather than Node.js.
|
||||
|
||||
**Rationale**:
|
||||
- Needs to call Nix commands which are bash-native
|
||||
- Parsing Nix output is simpler in bash with grep/sed
|
||||
- Maintainers updating flake.nix likely have Nix installed (bash environment)
|
||||
- Node.js would add unnecessary complexity for shell operations
|
||||
|
||||
**Alternative considered**: Node.js script with child_process - adds dependency on extra npm packages for shell operations, less natural for Nix tooling.
|
||||
|
||||
### Extract hash from build error output
|
||||
|
||||
**Decision**: Trigger intentional build failure with placeholder hash to get correct hash.
|
||||
|
||||
**Rationale**: This is the standard Nix workflow for updating fixed-output derivations. No API exists to compute the hash without building.
|
||||
|
||||
**Alternative considered**: Pre-compute hash from pnpm-lock.yaml - would require understanding Nix's hash algorithm and pnpm's lockfile structure, fragile and non-standard.
|
||||
|
||||
### Use sed for in-place file editing
|
||||
|
||||
**Decision**: Use `sed -i` for updating flake.nix in place.
|
||||
|
||||
**Rationale**: Simple, available on all Unix-like systems, handles the specific replacement patterns needed.
|
||||
|
||||
**Alternative considered**:
|
||||
- Using Node.js to parse/modify: Overkill for simple string replacement
|
||||
- Manual `sed` without `-i`: Requires temp files, more complex
|
||||
|
||||
### Verify build after hash update
|
||||
|
||||
**Decision**: Always run verification build after updating hash.
|
||||
|
||||
**Rationale**: Catches errors immediately, gives maintainer confidence the update worked.
|
||||
|
||||
**Trade-off**: Takes extra time (~30s) but prevents broken flake.nix commits.
|
||||
|
||||
## Key Implementation Details
|
||||
|
||||
### Path Resolution
|
||||
|
||||
Script calculates paths relative to its own location:
|
||||
```bash
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
```
|
||||
|
||||
This allows running from any working directory.
|
||||
|
||||
### Error Handling
|
||||
|
||||
Uses `set -euo pipefail` for strict error handling:
|
||||
- `-e`: Exit on any command failure
|
||||
- `-u`: Exit on undefined variable access
|
||||
- `-o pipefail`: Catch failures in pipes
|
||||
|
||||
### Hash Extraction Pattern
|
||||
|
||||
Uses grep with Perl regex to extract hash:
|
||||
```bash
|
||||
grep -oP 'got:\s+\Ksha256-[A-Za-z0-9+/=]+'
|
||||
```
|
||||
|
||||
This reliably extracts the hash regardless of surrounding text.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk]** Script assumes standard Nix error message format → **Mitigation**: If extraction fails, script exits with error and shows full output
|
||||
|
||||
**[Risk]** Build might fail for reasons other than hash mismatch → **Mitigation**: Script checks for hash in output before proceeding
|
||||
|
||||
**[Trade-off]** Requires Nix installed to run → **Benefit**: Only maintainers updating flake need to run this, and they have Nix
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Add script to scripts directory
|
||||
2. Document in scripts/README.md
|
||||
3. Use in next version bump to verify workflow
|
||||
4. Update CONTRIBUTING.md if needed to mention script
|
||||
|
||||
No breaking changes - purely additive tooling.
|
||||
|
||||
## Open Questions
|
||||
|
||||
None - straightforward automation script.
|
||||
@@ -0,0 +1,23 @@
|
||||
## Why
|
||||
|
||||
Maintaining the Nix flake requires manual updates to version and dependency hash when releasing new versions or updating dependencies. This is error-prone and requires maintainers to understand Nix internals. Automating this process ensures consistency and reduces friction for releases.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `scripts/update-flake.sh` to automatically update flake.nix version and dependency hash
|
||||
- Add `scripts/README.md` documenting all maintenance scripts
|
||||
- Script extracts version from package.json and determines correct pnpm dependency hash automatically
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `flake-update-script`: Automation script for maintaining flake.nix
|
||||
|
||||
### Modified Capabilities
|
||||
- None
|
||||
|
||||
## Impact
|
||||
|
||||
- **New files**: `scripts/update-flake.sh`, `scripts/README.md`
|
||||
- **Maintainer workflow**: Version bumps now include running `./scripts/update-flake.sh`
|
||||
- **Dependencies**: Script requires Node.js (already a dependency) and Nix (for maintainers using Nix)
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Automatic Version Update
|
||||
The script SHALL automatically update the version in flake.nix to match package.json.
|
||||
|
||||
#### Scenario: Version extraction from package.json
|
||||
- **WHEN** script runs
|
||||
- **THEN** version is read from package.json using Node.js
|
||||
- **AND** version field in flake.nix is updated to match
|
||||
|
||||
#### Scenario: Version already up-to-date
|
||||
- **WHEN** script runs and flake.nix version already matches package.json
|
||||
- **THEN** script reports version is up-to-date
|
||||
- **AND** continues to hash update
|
||||
|
||||
### Requirement: Automatic Hash Determination
|
||||
The script SHALL automatically determine and update the correct pnpm dependency hash.
|
||||
|
||||
#### Scenario: Trigger build to get hash
|
||||
- **WHEN** script needs to determine correct hash
|
||||
- **THEN** script sets placeholder hash in flake.nix
|
||||
- **AND** runs nix build which fails with correct hash
|
||||
- **AND** extracts correct hash from build error output
|
||||
|
||||
#### Scenario: Hash extraction from build output
|
||||
- **WHEN** nix build fails with hash mismatch
|
||||
- **THEN** script parses "got: sha256-..." from error output
|
||||
- **AND** updates flake.nix with correct hash
|
||||
|
||||
#### Scenario: Hash update failure
|
||||
- **WHEN** script cannot extract hash from build output
|
||||
- **THEN** script exits with error
|
||||
- **AND** displays build output for debugging
|
||||
|
||||
### Requirement: Build Verification
|
||||
The script SHALL verify that flake.nix builds successfully after updates.
|
||||
|
||||
#### Scenario: Successful verification
|
||||
- **WHEN** hash has been updated
|
||||
- **THEN** script runs nix build to verify
|
||||
- **AND** reports success if build completes
|
||||
|
||||
#### Scenario: Dirty git tree warning
|
||||
- **WHEN** build succeeds but git tree is dirty
|
||||
- **THEN** script reports warning about dirty tree
|
||||
- **AND** still indicates build success
|
||||
|
||||
### Requirement: User Feedback
|
||||
The script SHALL provide clear progress information and next steps.
|
||||
|
||||
#### Scenario: Progress reporting
|
||||
- **WHEN** script runs
|
||||
- **THEN** each step is reported with descriptive message
|
||||
- **AND** detected version and hash are displayed
|
||||
|
||||
#### Scenario: Success summary
|
||||
- **WHEN** script completes successfully
|
||||
- **THEN** summary shows updated version and hash
|
||||
- **AND** next steps are displayed (test, commit, etc.)
|
||||
|
||||
### Requirement: Script Safety
|
||||
The script SHALL fail fast on errors and use safe defaults.
|
||||
|
||||
#### Scenario: Bash error handling
|
||||
- **WHEN** script encounters an error
|
||||
- **THEN** script exits immediately (set -e)
|
||||
- **AND** undefined variables cause exit (set -u)
|
||||
- **AND** pipe failures are caught (set -o pipefail)
|
||||
|
||||
#### Scenario: File path resolution
|
||||
- **WHEN** script determines file locations
|
||||
- **THEN** paths are calculated relative to script location
|
||||
- **AND** script works regardless of working directory
|
||||
|
||||
### Requirement: Documentation
|
||||
The system SHALL provide documentation for the update script.
|
||||
|
||||
#### Scenario: Script usage documentation
|
||||
- **WHEN** maintainer needs to use update script
|
||||
- **THEN** scripts/README.md explains when and how to use it
|
||||
- **AND** example workflow is provided
|
||||
|
||||
#### Scenario: Script listing
|
||||
- **WHEN** maintainer views scripts/README.md
|
||||
- **THEN** all maintenance scripts are documented
|
||||
- **AND** purpose of each script is clear
|
||||
@@ -0,0 +1,55 @@
|
||||
## 1. Create Update Script
|
||||
|
||||
- [x] 1.1 Create scripts/update-flake.sh file
|
||||
- [x] 1.2 Add shebang and error handling (set -euo pipefail)
|
||||
- [x] 1.3 Add path resolution for project root and files
|
||||
- [x] 1.4 Make script executable (chmod +x)
|
||||
|
||||
## 2. Implement Version Update Logic
|
||||
|
||||
- [x] 2.1 Extract version from package.json using Node.js
|
||||
- [x] 2.2 Use sed to update version in flake.nix
|
||||
- [x] 2.3 Report if version already up-to-date
|
||||
- [x] 2.4 Display detected version to user
|
||||
|
||||
## 3. Implement Hash Update Logic
|
||||
|
||||
- [x] 3.1 Set placeholder hash in flake.nix
|
||||
- [x] 3.2 Run nix build and capture output (allow failure)
|
||||
- [x] 3.3 Extract correct hash from build error using grep
|
||||
- [x] 3.4 Handle case where hash extraction fails
|
||||
- [x] 3.5 Update flake.nix with correct hash
|
||||
- [x] 3.6 Display detected hash to user
|
||||
|
||||
## 4. Add Build Verification
|
||||
|
||||
- [x] 4.1 Run nix build after hash update
|
||||
- [x] 4.2 Check for dirty git tree warning
|
||||
- [x] 4.3 Report success or failure clearly
|
||||
|
||||
## 5. Add User Feedback
|
||||
|
||||
- [x] 5.1 Add progress messages for each step
|
||||
- [x] 5.2 Add success summary with version and hash
|
||||
- [x] 5.3 Add next steps instructions (test, commit)
|
||||
- [x] 5.4 Add error messages with context
|
||||
|
||||
## 6. Create Documentation
|
||||
|
||||
- [x] 6.1 Create scripts/README.md
|
||||
- [x] 6.2 Document update-flake.sh purpose and usage
|
||||
- [x] 6.3 Add example workflow
|
||||
- [x] 6.4 Document other existing scripts
|
||||
|
||||
## 7. Testing
|
||||
|
||||
- [x] 7.1 Test script runs successfully
|
||||
- [x] 7.2 Verify version is extracted correctly
|
||||
- [x] 7.3 Verify hash is updated correctly
|
||||
- [x] 7.4 Verify build succeeds after update
|
||||
- [x] 7.5 Test idempotency (running twice works)
|
||||
|
||||
## 8. Integration
|
||||
|
||||
- [ ] 8.1 Add note to release process documentation
|
||||
- [ ] 8.2 Use in next actual version bump to validate workflow
|
||||
@@ -0,0 +1,206 @@
|
||||
# Design: Nix CI Validation
|
||||
|
||||
## Context
|
||||
|
||||
OpenSpec recently added Nix flake support to enable Nix users to install the tool. This includes:
|
||||
- `flake.nix`: Nix package definition with pnpm dependency fetching
|
||||
- `scripts/update-flake.sh`: Automation script to update version and hash when releasing
|
||||
|
||||
Currently, there is no CI validation ensuring these Nix artifacts remain functional. The existing CI workflow (.github/workflows/ci.yml) validates Node.js builds, tests, and linting across multiple platforms (Linux, macOS, Windows) but does not validate Nix builds.
|
||||
|
||||
**Stakeholders**: Nix users, maintainers, contributors who need confidence that Nix support works.
|
||||
|
||||
**Constraints**:
|
||||
- Must work in GitHub Actions Linux runners
|
||||
- Should minimize CI runtime impact (<5 minutes added)
|
||||
- Should support local testing with `act` for rapid iteration
|
||||
- Must integrate with existing required checks
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals**:
|
||||
- Validate `nix build` succeeds on every PR/push
|
||||
- Validate `scripts/update-flake.sh` executes without errors
|
||||
- Ensure Nix support doesn't regress silently
|
||||
- Support local testing with `act`
|
||||
- Optimize with caching to minimize CI time
|
||||
|
||||
**Non-Goals**:
|
||||
- Testing on macOS (GitHub-hosted macOS runners are slower and more expensive; Nix flake already declares macOS support)
|
||||
- Building for all declared systems (x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin) - focus on most common platform
|
||||
- Validating Nix flake quality/style (nixpkgs-fmt, etc.) - can be added later if needed
|
||||
- Running OpenSpec's full test suite through Nix build - existing CI already does this
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: Use DeterminateSystems nix-installer-action
|
||||
|
||||
**What**: Use `determinatesystems/nix-installer-action` for installing Nix in CI.
|
||||
|
||||
**Why**:
|
||||
- Official GitHub Action maintained by Determinate Systems (Nix experts)
|
||||
- Handles GitHub Actions environment quirks automatically
|
||||
- Includes automatic caching configuration
|
||||
- More reliable than curl | sh installation script
|
||||
- Better error messages and diagnostics
|
||||
|
||||
**Alternatives considered**:
|
||||
- Official Nix installer (`curl -L https://nixos.org/nix/install | sh`): Works but requires manual setup of flakes, caching, and CI-specific configuration
|
||||
- `cachix/install-nix-action`: Popular alternative but determinatesystems is more actively maintained and has better GHA integration
|
||||
|
||||
### Decision 2: Use Magic Nix Cache for performance
|
||||
|
||||
**What**: Use `determinatesystems/magic-nix-cache-action` for automatic binary caching.
|
||||
|
||||
**Why**:
|
||||
- Zero-configuration caching for Nix store
|
||||
- Significantly reduces CI time on subsequent runs (from ~5min to ~1-2min)
|
||||
- Free for public repositories
|
||||
- Handles cache keys automatically
|
||||
|
||||
**Alternatives considered**:
|
||||
- Manual Nix store caching with GitHub Actions cache: More complex, requires manual cache key management
|
||||
- Cachix: Excellent tool but requires account setup and token management
|
||||
- No caching: Acceptable for initial implementation, but poor developer experience
|
||||
|
||||
### Decision 3: Separate job for Nix validation
|
||||
|
||||
**What**: Create a dedicated `nix-validate` job in .github/workflows/ci.yml that runs in parallel with other jobs.
|
||||
|
||||
**Why**:
|
||||
- Keeps Nix validation isolated from Node.js validation
|
||||
- Allows parallel execution for faster CI
|
||||
- Easier to debug when Nix-specific issues occur
|
||||
- Can be marked as required check independently
|
||||
|
||||
**Alternatives considered**:
|
||||
- Add Nix steps to existing jobs: Creates coupling between Node.js and Nix validation, harder to maintain
|
||||
- Separate workflow file: Overkill for a single job, harder to manage required checks
|
||||
|
||||
### Decision 4: Validate update script by executing it
|
||||
|
||||
**What**: Run `scripts/update-flake.sh` as part of CI validation.
|
||||
|
||||
**Why**:
|
||||
- Ensures the script doesn't break due to changes in package.json format, nix build output, or dependencies
|
||||
- Tests the full workflow users will follow when releasing
|
||||
- Catches errors early
|
||||
|
||||
**Implementation approach**:
|
||||
- Execute script in a way that doesn't modify git state (or discard changes after)
|
||||
- Verify script exits with code 0
|
||||
- Optionally validate that flake.nix contains expected patterns after execution
|
||||
|
||||
**Alternatives considered**:
|
||||
- Mock/dry-run mode: Would require modifying the script significantly
|
||||
- Skip validation: Risky - script could break and only be discovered at release time
|
||||
- Only run on release branches: Misses issues early in development
|
||||
|
||||
### Decision 5: Run on pull_request and push to main
|
||||
|
||||
**What**: Configure Nix validation job to run on:
|
||||
- `pull_request` events (any PR to main)
|
||||
- `push` events (direct pushes to main)
|
||||
- `workflow_dispatch` (manual trigger for testing)
|
||||
|
||||
**Why**:
|
||||
- Catches issues before merge (pull_request)
|
||||
- Validates main branch stays healthy (push)
|
||||
- Allows manual testing without creating PRs (workflow_dispatch)
|
||||
|
||||
### Decision 6: Support act for local testing
|
||||
|
||||
**What**: Ensure workflow is compatible with `act` tool for local CI testing.
|
||||
|
||||
**Why**:
|
||||
- Faster iteration when developing CI changes
|
||||
- Allows testing without pushing to GitHub
|
||||
- Reduces commit noise from CI debugging
|
||||
|
||||
**Requirements**:
|
||||
- Use standard GitHub Actions syntax
|
||||
- Document any act-specific configuration needed
|
||||
- Test that Nix can be installed in act's Docker containers
|
||||
|
||||
**Limitations**:
|
||||
- act may not perfectly replicate GitHub's runners, but close enough for validation
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### Risk: CI runtime increase
|
||||
|
||||
**Impact**: Adding Nix validation will increase total CI time by 2-5 minutes per run.
|
||||
|
||||
**Mitigation**:
|
||||
- Run Nix job in parallel with existing jobs (no blocking delay)
|
||||
- Use magic-nix-cache for subsequent runs (~1-2 min with cache)
|
||||
- Configure appropriate timeout (10 minutes max)
|
||||
|
||||
**Acceptance**: The benefit of preventing Nix regressions outweighs the cost.
|
||||
|
||||
### Risk: Nix installer failures in CI
|
||||
|
||||
**Impact**: Transient failures in Nix installation could block PRs.
|
||||
|
||||
**Mitigation**:
|
||||
- Use determinatesystems action which has retry logic
|
||||
- Monitor for flaky failures and adjust if needed
|
||||
- Document troubleshooting steps
|
||||
|
||||
**Acceptance**: Nix installation is generally stable in GHA; this is low risk.
|
||||
|
||||
### Risk: Update script modifies git state
|
||||
|
||||
**Impact**: Running update-flake.sh modifies flake.nix, which could cause CI to fail if git state is checked.
|
||||
|
||||
**Mitigation**:
|
||||
- Run script in isolation without committing changes
|
||||
- Add `git checkout -- flake.nix` after validation
|
||||
- Or accept dirty git state in CI (doesn't affect build validation)
|
||||
|
||||
**Acceptance**: Script validation is important enough to handle this carefully.
|
||||
|
||||
### Risk: act compatibility issues
|
||||
|
||||
**Impact**: Workflow might not work perfectly with act due to Docker environment differences.
|
||||
|
||||
**Mitigation**:
|
||||
- Document known limitations
|
||||
- Focus on GitHub Actions as primary validation target
|
||||
- Use act as best-effort local testing
|
||||
|
||||
**Acceptance**: act support is nice-to-have, not required.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Add Nix job (new, non-required)
|
||||
1. Add `nix-validate` job to .github/workflows/ci.yml
|
||||
2. Configure to run in parallel with existing jobs
|
||||
3. Do NOT mark as required check initially
|
||||
4. Monitor for ~1 week to ensure stability
|
||||
|
||||
### Phase 2: Make required
|
||||
1. After validation is stable, add to required checks
|
||||
2. Update branch protection rules in GitHub settings
|
||||
3. Document in CONTRIBUTING.md or README
|
||||
|
||||
### Rollback Plan
|
||||
If Nix validation causes issues:
|
||||
1. Remove job from required checks in GitHub settings (immediate)
|
||||
2. Comment out or remove job from workflow (permanent fix)
|
||||
3. Investigate and fix issues
|
||||
4. Re-enable following same phased approach
|
||||
|
||||
## Open Questions
|
||||
|
||||
- **Q**: Should we test update-flake.sh on every CI run, or only when package.json or pnpm-lock.yaml changes?
|
||||
- **A**: Test on every run for simplicity. The script is fast (<30 seconds) and catching regressions is valuable.
|
||||
|
||||
- **Q**: Should we validate on macOS as well?
|
||||
- **A**: No for initial implementation. Linux validation is sufficient and macOS runners are slower/more expensive. Can add later if users report macOS-specific issues.
|
||||
|
||||
- **Q**: Should we run full OpenSpec tests through the Nix build?
|
||||
- **A**: No. The Nix build already runs `pnpm test` as part of its build phase. Existing CI jobs cover testing thoroughly. Nix validation focuses on build success.
|
||||
|
||||
- **Q**: What timeout should we use for the Nix validation job?
|
||||
- **A**: Start with 10 minutes. With caching, jobs should complete in 1-3 minutes. Without cache (first run), 5-7 minutes is expected.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add Nix CI Validation
|
||||
|
||||
## Why
|
||||
|
||||
The project recently added Nix flake support (flake.nix) and an automated update script (scripts/update-flake.sh) to enable Nix users to install OpenSpec. However, there is no CI validation ensuring these Nix artifacts continue to work as the project evolves. This creates risk that breaking changes could be merged without detection.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a new GitHub Actions workflow job to validate Nix flake builds successfully
|
||||
- Add validation that the update-flake.sh script executes without errors
|
||||
- Test on Linux (where Nix support is most common)
|
||||
- Ensure CI fails if Nix build or update script breaks
|
||||
- Enable local testing with `act` for developers
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: New capability `ci-nix-validation`
|
||||
- Affected code: `.github/workflows/ci.yml` (add new job)
|
||||
- Affected infrastructure: GitHub Actions runners with Nix installed
|
||||
- Benefits: Prevents regressions in Nix support, gives confidence to Nix users
|
||||
- Trade-offs: Adds ~2-3 minutes to CI runtime
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# CI Nix Validation Specification
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Nix Flake Build Validation
|
||||
|
||||
The CI system SHALL validate that the Nix flake builds successfully on every pull request and push to main.
|
||||
|
||||
#### Scenario: Successful flake build
|
||||
|
||||
- **WHEN** a pull request or push to main is made
|
||||
- **THEN** the CI SHALL execute `nix build` and verify it completes with exit code 0
|
||||
- **AND** the build output SHALL contain the openspec binary
|
||||
|
||||
#### Scenario: Flake build failure
|
||||
|
||||
- **WHEN** the Nix flake configuration is broken
|
||||
- **THEN** the CI job SHALL fail with a non-zero exit code
|
||||
- **AND** the CI SHALL prevent merging of the pull request
|
||||
|
||||
#### Scenario: Multi-platform support check
|
||||
|
||||
- **WHEN** the flake declares support for multiple systems
|
||||
- **THEN** the CI SHALL validate the flake builds on at least Linux (x86_64-linux)
|
||||
|
||||
### Requirement: Update Script Validation
|
||||
|
||||
The CI system SHALL validate that the update-flake.sh script executes successfully and produces valid output.
|
||||
|
||||
#### Scenario: Update script execution
|
||||
|
||||
- **WHEN** the CI runs the update script validation
|
||||
- **THEN** the script SHALL execute without errors
|
||||
- **AND** the script SHALL correctly extract the version from package.json
|
||||
- **AND** the script SHALL update flake.nix with the correct version
|
||||
|
||||
#### Scenario: Update script with mock hash
|
||||
|
||||
- **WHEN** validating the update script in CI
|
||||
- **THEN** the script SHALL be able to detect and extract the correct pnpm dependency hash
|
||||
- **AND** the flake.nix SHALL be updated with a valid sha256 hash
|
||||
|
||||
### Requirement: CI Job Integration
|
||||
|
||||
The Nix validation jobs SHALL be integrated into the existing GitHub Actions workflow and required for merge.
|
||||
|
||||
#### Scenario: PR merge requirements
|
||||
|
||||
- **WHEN** a pull request is created
|
||||
- **THEN** the Nix validation job SHALL be included in required checks
|
||||
- **AND** the PR SHALL NOT be mergeable until Nix validation passes
|
||||
|
||||
#### Scenario: Job execution triggers
|
||||
|
||||
- **WHEN** code is pushed to a pull request OR pushed to main OR manually triggered
|
||||
- **THEN** the Nix validation job SHALL execute automatically
|
||||
|
||||
### Requirement: Local Testing Support
|
||||
|
||||
The CI workflow SHALL be testable locally using the `act` tool to enable rapid iteration.
|
||||
|
||||
#### Scenario: Local CI execution with act
|
||||
|
||||
- **WHEN** a developer runs `act` with the Nix validation workflow
|
||||
- **THEN** the workflow SHALL execute in the local Docker environment
|
||||
- **AND** the developer SHALL receive feedback on Nix build status without pushing to GitHub
|
||||
|
||||
#### Scenario: Act configuration compatibility
|
||||
|
||||
- **WHEN** the workflow is designed
|
||||
- **THEN** it SHALL use standard GitHub Actions syntax compatible with `act`
|
||||
- **AND** any Nix-specific setup SHALL work in the act Docker environment
|
||||
|
||||
### Requirement: Nix Installation in CI
|
||||
|
||||
The CI environment SHALL have Nix properly installed and configured before running validation.
|
||||
|
||||
#### Scenario: Nix installation step
|
||||
|
||||
- **WHEN** the Nix validation job starts
|
||||
- **THEN** Nix SHALL be installed using the official Nix installer or determinatesystems/nix-installer-action
|
||||
- **AND** the Nix installation SHALL be cached for subsequent runs to improve performance
|
||||
|
||||
#### Scenario: Nix configuration for CI
|
||||
|
||||
- **WHEN** Nix is installed in CI
|
||||
- **THEN** it SHALL be configured to work in the GitHub Actions environment
|
||||
- **AND** experimental features (flakes, nix-command) SHALL be enabled
|
||||
|
||||
### Requirement: CI Performance Optimization
|
||||
|
||||
The Nix validation SHALL be optimized to minimize CI runtime impact.
|
||||
|
||||
#### Scenario: Acceptable runtime
|
||||
|
||||
- **WHEN** the Nix validation job runs
|
||||
- **THEN** it SHALL complete in under 5 minutes on a clean run
|
||||
- **AND** with caching, it SHALL complete in under 3 minutes on subsequent runs
|
||||
|
||||
#### Scenario: Parallel execution
|
||||
|
||||
- **WHEN** multiple CI jobs are running
|
||||
- **THEN** the Nix validation job SHALL run in parallel with other validation jobs (tests, lint)
|
||||
- **AND** SHALL NOT block other independent checks
|
||||
@@ -0,0 +1,49 @@
|
||||
# Implementation Tasks
|
||||
|
||||
## 1. Add Nix Installation to CI
|
||||
|
||||
- [x] 1.1 Research Nix installation options for GitHub Actions (nix-installer-action vs manual install)
|
||||
- [x] 1.2 Add Nix installation step to .github/workflows/ci.yml
|
||||
- [x] 1.3 Configure Nix with experimental features enabled (flakes, nix-command)
|
||||
- [x] 1.4 Add Nix store caching to improve CI performance
|
||||
|
||||
## 2. Create Nix Build Validation Job
|
||||
|
||||
- [x] 2.1 Add new `nix-flake-validate` job to .github/workflows/ci.yml
|
||||
- [x] 2.2 Implement `nix build` step with proper error handling
|
||||
- [x] 2.3 Add verification step to confirm binary exists in build output
|
||||
- [x] 2.4 Add step to test binary execution (`nix run . -- --version`)
|
||||
|
||||
## 3. Add Update Script Validation
|
||||
|
||||
- [x] 3.1 Add job step to run scripts/update-flake.sh in dry-run or test mode
|
||||
- [x] 3.2 Verify script executes without errors
|
||||
- [x] 3.3 Add validation that version is correctly extracted from package.json
|
||||
- [x] 3.4 Verify flake.nix is updated with correct format (version and hash)
|
||||
|
||||
## 4. Configure Job Dependencies and Requirements
|
||||
|
||||
- [x] 4.1 Configure Nix validation job to run on pull_request and push events
|
||||
- [x] 4.2 Add Nix validation to required checks list
|
||||
- [x] 4.3 Configure job to run in parallel with existing test/lint jobs
|
||||
- [x] 4.4 Set appropriate timeout (5-10 minutes)
|
||||
|
||||
## 5. Test with act Locally
|
||||
|
||||
- [x] 5.1 Install act locally if not already available
|
||||
- [x] 5.2 Test Nix validation job using `act pull_request`
|
||||
- [x] 5.3 Verify act can run the workflow with Nix installed
|
||||
- [x] 5.4 Document any act-specific configuration needed in .actrc or README
|
||||
|
||||
## 6. Documentation and Finalization
|
||||
|
||||
- [x] 6.1 Add documentation about Nix CI validation to README or CONTRIBUTING.md
|
||||
- [x] 6.2 Document how to test CI locally with act
|
||||
- [ ] 6.3 Update CI badge or status indicators if needed
|
||||
- [ ] 6.4 Test end-to-end by creating a test PR
|
||||
|
||||
## 7. Archive Change
|
||||
|
||||
- [x] 7.1 After merge and verification, create new spec file at openspec/specs/ci-nix-validation/spec.md
|
||||
- [x] 7.2 Move change directory to openspec/changes/archive/[date]-add-nix-ci-validation/
|
||||
- [x] 7.3 Run `openspec validate --strict` to confirm archived change passes
|
||||
@@ -0,0 +1,107 @@
|
||||
# ci-nix-validation Specification
|
||||
|
||||
## Purpose
|
||||
|
||||
Validates Nix flake builds and maintenance scripts in CI to ensure Nix users can reliably install and use OpenSpec. Prevents regressions in Nix support by testing builds and the update-flake.sh script on every pull request and push to main.
|
||||
## Requirements
|
||||
### Requirement: Nix Flake Build Validation
|
||||
|
||||
The CI system SHALL validate that the Nix flake builds successfully on every pull request and push to main.
|
||||
|
||||
#### Scenario: Successful flake build
|
||||
|
||||
- **WHEN** a pull request or push to main is made
|
||||
- **THEN** the CI SHALL execute `nix build` and verify it completes with exit code 0
|
||||
- **AND** the build output SHALL contain the openspec binary
|
||||
|
||||
#### Scenario: Flake build failure
|
||||
|
||||
- **WHEN** the Nix flake configuration is broken
|
||||
- **THEN** the CI job SHALL fail with a non-zero exit code
|
||||
- **AND** the CI SHALL prevent merging of the pull request
|
||||
|
||||
#### Scenario: Multi-platform support check
|
||||
|
||||
- **WHEN** the flake declares support for multiple systems
|
||||
- **THEN** the CI SHALL validate the flake builds on at least Linux (x86_64-linux)
|
||||
|
||||
### Requirement: Update Script Validation
|
||||
|
||||
The CI system SHALL validate that the update-flake.sh script executes successfully and produces valid output.
|
||||
|
||||
#### Scenario: Update script execution
|
||||
|
||||
- **WHEN** the CI runs the update script validation
|
||||
- **THEN** the script SHALL execute without errors
|
||||
- **AND** the script SHALL correctly extract the version from package.json
|
||||
- **AND** the script SHALL update flake.nix with the correct version
|
||||
|
||||
#### Scenario: Update script with mock hash
|
||||
|
||||
- **WHEN** validating the update script in CI
|
||||
- **THEN** the script SHALL be able to detect and extract the correct pnpm dependency hash
|
||||
- **AND** the flake.nix SHALL be updated with a valid sha256 hash
|
||||
|
||||
### Requirement: CI Job Integration
|
||||
|
||||
The Nix validation jobs SHALL be integrated into the existing GitHub Actions workflow and required for merge.
|
||||
|
||||
#### Scenario: PR merge requirements
|
||||
|
||||
- **WHEN** a pull request is created
|
||||
- **THEN** the Nix validation job SHALL be included in required checks
|
||||
- **AND** the PR SHALL NOT be mergeable until Nix validation passes
|
||||
|
||||
#### Scenario: Job execution triggers
|
||||
|
||||
- **WHEN** code is pushed to a pull request OR pushed to main OR manually triggered
|
||||
- **THEN** the Nix validation job SHALL execute automatically
|
||||
|
||||
### Requirement: Local Testing Support
|
||||
|
||||
The CI workflow SHALL be testable locally using the `act` tool to enable rapid iteration.
|
||||
|
||||
#### Scenario: Local CI execution with act
|
||||
|
||||
- **WHEN** a developer runs `act` with the Nix validation workflow
|
||||
- **THEN** the workflow SHALL execute in the local Docker environment
|
||||
- **AND** the developer SHALL receive feedback on Nix build status without pushing to GitHub
|
||||
|
||||
#### Scenario: Act configuration compatibility
|
||||
|
||||
- **WHEN** the workflow is designed
|
||||
- **THEN** it SHALL use standard GitHub Actions syntax compatible with `act`
|
||||
- **AND** any Nix-specific setup SHALL work in the act Docker environment
|
||||
|
||||
### Requirement: Nix Installation in CI
|
||||
|
||||
The CI environment SHALL have Nix properly installed and configured before running validation.
|
||||
|
||||
#### Scenario: Nix installation step
|
||||
|
||||
- **WHEN** the Nix validation job starts
|
||||
- **THEN** Nix SHALL be installed using the official Nix installer or determinatesystems/nix-installer-action
|
||||
- **AND** the Nix installation SHALL be cached for subsequent runs to improve performance
|
||||
|
||||
#### Scenario: Nix configuration for CI
|
||||
|
||||
- **WHEN** Nix is installed in CI
|
||||
- **THEN** it SHALL be configured to work in the GitHub Actions environment
|
||||
- **AND** experimental features (flakes, nix-command) SHALL be enabled
|
||||
|
||||
### Requirement: CI Performance Optimization
|
||||
|
||||
The Nix validation SHALL be optimized to minimize CI runtime impact.
|
||||
|
||||
#### Scenario: Acceptable runtime
|
||||
|
||||
- **WHEN** the Nix validation job runs
|
||||
- **THEN** it SHALL complete in under 5 minutes on a clean run
|
||||
- **AND** with caching, it SHALL complete in under 3 minutes on subsequent runs
|
||||
|
||||
#### Scenario: Parallel execution
|
||||
|
||||
- **WHEN** multiple CI jobs are running
|
||||
- **THEN** the Nix validation job SHALL run in parallel with other validation jobs (tests, lint)
|
||||
- **AND** SHALL NOT block other independent checks
|
||||
|
||||
@@ -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.21.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
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# OpenSpec Scripts
|
||||
|
||||
Utility scripts for OpenSpec maintenance and development.
|
||||
|
||||
## update-flake.sh
|
||||
|
||||
Updates `flake.nix` version and dependency hash automatically.
|
||||
|
||||
**When to use**: After updating dependencies or releasing a new version.
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
./scripts/update-flake.sh
|
||||
```
|
||||
|
||||
**What it does**:
|
||||
1. Extracts version from `package.json`
|
||||
2. Updates version in `flake.nix`
|
||||
3. Automatically determines the correct pnpm dependency hash
|
||||
4. Updates the hash in `flake.nix`
|
||||
5. Verifies the build succeeds
|
||||
|
||||
**Example workflow**:
|
||||
```bash
|
||||
# After version bump and dependency updates
|
||||
pnpm install
|
||||
./scripts/update-flake.sh
|
||||
git add flake.nix
|
||||
git commit -m "chore: update flake.nix for v0.18.0"
|
||||
```
|
||||
|
||||
## postinstall.js
|
||||
|
||||
Post-installation script that runs after package installation.
|
||||
|
||||
## pack-version-check.mjs
|
||||
|
||||
Validates package version consistency before publishing.
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Script to update flake.nix version and dependency hash
|
||||
# Run this after updating package.json version
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
FLAKE_FILE="$PROJECT_ROOT/flake.nix"
|
||||
PACKAGE_JSON="$PROJECT_ROOT/package.json"
|
||||
|
||||
# Detect OS and set sed in-place flag
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS (BSD sed) requires empty string argument for -i
|
||||
SED_INPLACE=(-i '')
|
||||
else
|
||||
# Linux (GNU sed)
|
||||
SED_INPLACE=(-i)
|
||||
fi
|
||||
|
||||
echo "==> Updating flake.nix..."
|
||||
|
||||
# Extract version from package.json
|
||||
VERSION=$(node -p "require('$PACKAGE_JSON').version")
|
||||
echo " Detected version: $VERSION"
|
||||
|
||||
# Update version in flake.nix
|
||||
if ! grep -q "version = \"$VERSION\"" "$FLAKE_FILE"; then
|
||||
echo " Updating version in flake.nix..."
|
||||
sed "${SED_INPLACE[@]}" "s|version = \"[^\"]*\"|version = \"$VERSION\"|" "$FLAKE_FILE"
|
||||
else
|
||||
echo " Version already up-to-date in flake.nix"
|
||||
fi
|
||||
|
||||
# Set placeholder hash to trigger error
|
||||
echo " Setting placeholder hash..."
|
||||
PLACEHOLDER="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
sed "${SED_INPLACE[@]}" "s|hash = \"sha256-[^\"]*\"|hash = \"$PLACEHOLDER\"|" "$FLAKE_FILE"
|
||||
|
||||
# Try to build and capture the correct hash
|
||||
echo " Building to get correct hash (this will fail)..."
|
||||
BUILD_OUTPUT=$(nix build 2>&1 || true)
|
||||
|
||||
# Extract the correct hash from error output
|
||||
CORRECT_HASH=$(echo "$BUILD_OUTPUT" | grep -oP 'got:\s+\Ksha256-[A-Za-z0-9+/=]+' | head -1)
|
||||
|
||||
if [ -z "$CORRECT_HASH" ]; then
|
||||
echo "❌ Error: Could not extract hash from build output"
|
||||
echo "Build output:"
|
||||
echo "$BUILD_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Detected hash: $CORRECT_HASH"
|
||||
|
||||
# Update flake.nix with correct hash
|
||||
sed "${SED_INPLACE[@]}" "s|hash = \"$PLACEHOLDER\"|hash = \"$CORRECT_HASH\"|" "$FLAKE_FILE"
|
||||
|
||||
# Verify the build works
|
||||
echo " Verifying build..."
|
||||
if nix build 2>&1 | grep -q "warning: Git tree.*is dirty"; then
|
||||
echo "⚠️ Warning: Git tree is dirty, but build succeeded"
|
||||
else
|
||||
echo "✅ Build successful"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ flake.nix updated successfully!"
|
||||
echo " Version: $VERSION"
|
||||
echo " Hash: $CORRECT_HASH"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Test: nix run . -- --version"
|
||||
echo " 2. Commit: git add flake.nix"
|
||||
echo " 3. Include in version bump commit"
|
||||
+23
-3
@@ -13,6 +13,7 @@ import { ChangeCommand } from '../commands/change.js';
|
||||
import { ValidateCommand } from '../commands/validate.js';
|
||||
import { ShowCommand } from '../commands/show.js';
|
||||
import { CompletionCommand } from '../commands/completion.js';
|
||||
import { FeedbackCommand } from '../commands/feedback.js';
|
||||
import { registerConfigCommand } from '../commands/config.js';
|
||||
import { registerArtifactWorkflowCommands } from '../commands/artifact-workflow.js';
|
||||
import { maybeShowTelemetryNotice, trackCommand, shutdown } from '../telemetry/index.js';
|
||||
@@ -50,7 +51,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 +63,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);
|
||||
});
|
||||
|
||||
@@ -290,6 +294,22 @@ program
|
||||
}
|
||||
});
|
||||
|
||||
// Feedback command
|
||||
program
|
||||
.command('feedback <message>')
|
||||
.description('Submit feedback about OpenSpec')
|
||||
.option('--body <text>', 'Detailed description for the feedback')
|
||||
.action(async (message: string, options?: { body?: string }) => {
|
||||
try {
|
||||
const feedbackCommand = new FeedbackCommand();
|
||||
await feedbackCommand.execute(message, options);
|
||||
} catch (error) {
|
||||
console.log();
|
||||
ora().fail(`Error: ${(error as Error).message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Completion command with subcommands
|
||||
const completionCmd = program
|
||||
.command('completion')
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
type SchemaInfo,
|
||||
} from '../core/artifact-graph/index.js';
|
||||
import { createChange, validateChangeName } from '../utils/change-utils.js';
|
||||
import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate } from '../core/templates/skill-templates.js';
|
||||
import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxVerifyCommandTemplate } from '../core/templates/skill-templates.js';
|
||||
import { FileSystemUtils } from '../utils/file-system.js';
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -800,6 +800,7 @@ async function artifactExperimentalSetupCommand(): Promise<void> {
|
||||
const ffChangeSkill = getFfChangeSkillTemplate();
|
||||
const syncSpecsSkill = getSyncSpecsSkillTemplate();
|
||||
const archiveChangeSkill = getArchiveChangeSkillTemplate();
|
||||
const verifyChangeSkill = getVerifyChangeSkillTemplate();
|
||||
|
||||
// Get command templates
|
||||
const exploreCommand = getOpsxExploreCommandTemplate();
|
||||
@@ -809,6 +810,7 @@ async function artifactExperimentalSetupCommand(): Promise<void> {
|
||||
const ffCommand = getOpsxFfCommandTemplate();
|
||||
const syncCommand = getOpsxSyncCommandTemplate();
|
||||
const archiveCommand = getOpsxArchiveCommandTemplate();
|
||||
const verifyCommand = getOpsxVerifyCommandTemplate();
|
||||
|
||||
// Create skill directories and SKILL.md files
|
||||
const skills = [
|
||||
@@ -819,6 +821,7 @@ async function artifactExperimentalSetupCommand(): Promise<void> {
|
||||
{ template: ffChangeSkill, dirName: 'openspec-ff-change' },
|
||||
{ template: syncSpecsSkill, dirName: 'openspec-sync-specs' },
|
||||
{ template: archiveChangeSkill, dirName: 'openspec-archive-change' },
|
||||
{ template: verifyChangeSkill, dirName: 'openspec-verify-change' },
|
||||
];
|
||||
|
||||
const createdSkillFiles: string[] = [];
|
||||
@@ -850,6 +853,7 @@ ${template.instructions}
|
||||
{ template: ffCommand, fileName: 'ff.md' },
|
||||
{ template: syncCommand, fileName: 'sync.md' },
|
||||
{ template: archiveCommand, fileName: 'archive.md' },
|
||||
{ template: verifyCommand, fileName: 'verify.md' },
|
||||
];
|
||||
|
||||
const createdCommandFiles: string[] = [];
|
||||
@@ -908,6 +912,7 @@ ${template.content}
|
||||
console.log(' • /opsx:apply - Implement tasks');
|
||||
console.log(' • /opsx:ff - Fast-forward: create all artifacts at once');
|
||||
console.log(' • /opsx:sync - Sync delta specs to main specs');
|
||||
console.log(' • /opsx:verify - Verify implementation matches artifacts');
|
||||
console.log(' • /opsx:archive - Archive a completed change');
|
||||
console.log();
|
||||
console.log(chalk.yellow('💡 This is an experimental feature.'));
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import { execSync, execFileSync } from 'child_process';
|
||||
import { createRequire } from 'module';
|
||||
import os from 'os';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
/**
|
||||
* Check if gh CLI is installed and available in PATH
|
||||
* Uses platform-appropriate command: 'where' on Windows, 'which' on Unix/macOS
|
||||
*/
|
||||
function isGhInstalled(): boolean {
|
||||
try {
|
||||
const command = process.platform === 'win32' ? 'where gh' : 'which gh';
|
||||
execSync(command, { stdio: 'pipe' });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if gh CLI is authenticated
|
||||
*/
|
||||
function isGhAuthenticated(): boolean {
|
||||
try {
|
||||
execSync('gh auth status', { stdio: 'pipe' });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenSpec version from package.json
|
||||
*/
|
||||
function getVersion(): string {
|
||||
try {
|
||||
const { version } = require('../../package.json');
|
||||
return version;
|
||||
} catch {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get platform name
|
||||
*/
|
||||
function getPlatform(): string {
|
||||
return os.platform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current timestamp in ISO format
|
||||
*/
|
||||
function getTimestamp(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate metadata footer for feedback
|
||||
*/
|
||||
function generateMetadata(): string {
|
||||
const version = getVersion();
|
||||
const platform = getPlatform();
|
||||
const timestamp = getTimestamp();
|
||||
|
||||
return `---
|
||||
Submitted via OpenSpec CLI
|
||||
- Version: ${version}
|
||||
- Platform: ${platform}
|
||||
- Timestamp: ${timestamp}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the feedback title
|
||||
*/
|
||||
function formatTitle(message: string): string {
|
||||
return `Feedback: ${message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the full feedback body
|
||||
*/
|
||||
function formatBody(bodyText?: string): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (bodyText) {
|
||||
parts.push(bodyText);
|
||||
parts.push(''); // Empty line before metadata
|
||||
}
|
||||
|
||||
parts.push(generateMetadata());
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a pre-filled GitHub issue URL for manual submission
|
||||
*/
|
||||
function generateManualSubmissionUrl(title: string, body: string): string {
|
||||
const repo = 'Fission-AI/OpenSpec';
|
||||
const encodedTitle = encodeURIComponent(title);
|
||||
const encodedBody = encodeURIComponent(body);
|
||||
const encodedLabels = encodeURIComponent('feedback');
|
||||
|
||||
return `https://github.com/${repo}/issues/new?title=${encodedTitle}&body=${encodedBody}&labels=${encodedLabels}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display formatted feedback content for manual submission
|
||||
*/
|
||||
function displayFormattedFeedback(title: string, body: string): void {
|
||||
console.log('\n--- FORMATTED FEEDBACK ---');
|
||||
console.log(`Title: ${title}`);
|
||||
console.log(`Labels: feedback`);
|
||||
console.log('\nBody:');
|
||||
console.log(body);
|
||||
console.log('--- END FEEDBACK ---\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit feedback via gh CLI
|
||||
* Uses execFileSync to prevent shell injection vulnerabilities
|
||||
*/
|
||||
function submitViaGhCli(title: string, body: string): void {
|
||||
try {
|
||||
const result = execFileSync(
|
||||
'gh',
|
||||
[
|
||||
'issue',
|
||||
'create',
|
||||
'--repo',
|
||||
'Fission-AI/OpenSpec',
|
||||
'--title',
|
||||
title,
|
||||
'--body',
|
||||
body,
|
||||
'--label',
|
||||
'feedback',
|
||||
],
|
||||
{ encoding: 'utf-8', stdio: 'pipe' }
|
||||
);
|
||||
|
||||
const issueUrl = result.trim();
|
||||
console.log(`\n✓ Feedback submitted successfully!`);
|
||||
console.log(`Issue URL: ${issueUrl}\n`);
|
||||
} catch (error: any) {
|
||||
// Display the error output from gh CLI
|
||||
if (error.stderr) {
|
||||
console.error(error.stderr.toString());
|
||||
} else if (error.message) {
|
||||
console.error(error.message);
|
||||
}
|
||||
|
||||
// Exit with the same code as gh CLI
|
||||
process.exit(error.status ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle fallback when gh CLI is not available or not authenticated
|
||||
*/
|
||||
function handleFallback(title: string, body: string, reason: 'missing' | 'unauthenticated'): void {
|
||||
if (reason === 'missing') {
|
||||
console.log('⚠️ GitHub CLI not found. Manual submission required.');
|
||||
} else {
|
||||
console.log('⚠️ GitHub authentication required. Manual submission required.');
|
||||
}
|
||||
|
||||
displayFormattedFeedback(title, body);
|
||||
|
||||
const manualUrl = generateManualSubmissionUrl(title, body);
|
||||
console.log('Please submit your feedback manually:');
|
||||
console.log(manualUrl);
|
||||
|
||||
if (reason === 'unauthenticated') {
|
||||
console.log('\nTo auto-submit in the future: gh auth login');
|
||||
}
|
||||
|
||||
// Exit with success code (fallback is successful)
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Feedback command implementation
|
||||
*/
|
||||
export class FeedbackCommand {
|
||||
async execute(message: string, options?: { body?: string }): Promise<void> {
|
||||
// Format title and body once for all code paths
|
||||
const title = formatTitle(message);
|
||||
const body = formatBody(options?.body);
|
||||
|
||||
// Check if gh CLI is installed
|
||||
if (!isGhInstalled()) {
|
||||
handleFallback(title, body, 'missing');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if gh CLI is authenticated
|
||||
if (!isGhAuthenticated()) {
|
||||
handleFallback(title, body, 'unauthenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
// Submit via gh CLI
|
||||
submitViaGhCli(title, body);
|
||||
}
|
||||
}
|
||||
@@ -155,6 +155,18 @@ export const COMMAND_REGISTRY: CommandDefinition[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'feedback',
|
||||
description: 'Submit feedback about OpenSpec',
|
||||
acceptsPositional: true,
|
||||
flags: [
|
||||
{
|
||||
name: 'body',
|
||||
description: 'Detailed description for the feedback',
|
||||
takesValue: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'change',
|
||||
description: 'Manage OpenSpec change proposals (deprecated)',
|
||||
|
||||
@@ -8,6 +8,11 @@ import { POWERSHELL_DYNAMIC_HELPERS } from '../templates/powershell-templates.js
|
||||
export class PowerShellGenerator implements CompletionGenerator {
|
||||
readonly shell = 'powershell' as const;
|
||||
|
||||
private stripTrailingCommaFromLastLine(lines: string[]): void {
|
||||
if (lines.length === 0) return;
|
||||
lines[lines.length - 1] = lines[lines.length - 1].replace(/,\s*$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a PowerShell completion script
|
||||
*
|
||||
@@ -20,6 +25,7 @@ export class PowerShellGenerator implements CompletionGenerator {
|
||||
for (const cmd of commands) {
|
||||
commandLines.push(` @{Name="${cmd.name}"; Description="${this.escapeDescription(cmd.description)}"},`);
|
||||
}
|
||||
this.stripTrailingCommaFromLastLine(commandLines);
|
||||
const topLevelCommands = commandLines.join('\n');
|
||||
|
||||
// Build command cases using push() for loop clarity
|
||||
@@ -88,6 +94,7 @@ Register-ArgumentCompleter -CommandName openspec -ScriptBlock $openspecCompleter
|
||||
lines.push(`${indent} @{Name="${longFlag}"; Description="${this.escapeDescription(flag.description)}"},`);
|
||||
}
|
||||
}
|
||||
this.stripTrailingCommaFromLastLine(lines);
|
||||
lines.push(`${indent} )`);
|
||||
lines.push(`${indent} $flags | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {`);
|
||||
lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, "ParameterName", $_.Description)`);
|
||||
@@ -103,6 +110,7 @@ Register-ArgumentCompleter -CommandName openspec -ScriptBlock $openspecCompleter
|
||||
for (const subcmd of cmd.subcommands) {
|
||||
lines.push(`${indent} @{Name="${subcmd.name}"; Description="${this.escapeDescription(subcmd.description)}"},`);
|
||||
}
|
||||
this.stripTrailingCommaFromLastLine(lines);
|
||||
lines.push(`${indent} )`);
|
||||
lines.push(`${indent} $subcommands | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {`);
|
||||
lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, "ParameterValue", $_.Description)`);
|
||||
@@ -148,6 +156,7 @@ Register-ArgumentCompleter -CommandName openspec -ScriptBlock $openspecCompleter
|
||||
lines.push(`${indent} @{Name="${longFlag}"; Description="${this.escapeDescription(flag.description)}"},`);
|
||||
}
|
||||
}
|
||||
this.stripTrailingCommaFromLastLine(lines);
|
||||
lines.push(`${indent} )`);
|
||||
lines.push(`${indent} $flags | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {`);
|
||||
lines.push(`${indent} [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, "ParameterName", $_.Description)`);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -9,7 +9,7 @@ Instructions for AI coding assistants using OpenSpec for spec-driven development
|
||||
- Pick a unique \`change-id\`: kebab-case, verb-led (\`add-\`, \`update-\`, \`remove-\`, \`refactor-\`)
|
||||
- Scaffold: \`proposal.md\`, \`tasks.md\`, \`design.md\` (only if needed), and delta specs per affected capability
|
||||
- Write deltas: use \`## ADDED|MODIFIED|REMOVED|RENAMED Requirements\`; include at least one \`#### Scenario:\` per requirement
|
||||
- Validate: \`openspec validate [change-id] --strict\` and fix issues
|
||||
- Validate: \`openspec validate [change-id] --strict --no-interactive\` and fix issues
|
||||
- Request approval: Do not start implementation until proposal is approved
|
||||
|
||||
## Three-Stage Workflow
|
||||
@@ -44,7 +44,7 @@ Skip proposal for:
|
||||
1. Review \`openspec/project.md\`, \`openspec list\`, and \`openspec list --specs\` to understand current context.
|
||||
2. Choose a unique verb-led \`change-id\` and scaffold \`proposal.md\`, \`tasks.md\`, optional \`design.md\`, and spec deltas under \`openspec/changes/<id>/\`.
|
||||
3. Draft spec deltas using \`## ADDED|MODIFIED|REMOVED Requirements\` with at least one \`#### Scenario:\` per requirement.
|
||||
4. Run \`openspec validate <id> --strict\` and resolve any issues before sharing the proposal.
|
||||
4. Run \`openspec validate <id> --strict --no-interactive\` and resolve any issues before sharing the proposal.
|
||||
|
||||
### Stage 2: Implementing Changes
|
||||
Track these steps as TODOs and complete them one by one.
|
||||
@@ -61,7 +61,7 @@ After deployment, create separate PR to:
|
||||
- Move \`changes/[name]/\` → \`changes/archive/YYYY-MM-DD-[name]/\`
|
||||
- Update \`specs/\` if capabilities changed
|
||||
- Use \`openspec archive <change-id> --skip-specs --yes\` for tooling-only changes (always pass the change ID explicitly)
|
||||
- Run \`openspec validate --strict\` to confirm the archived change passes checks
|
||||
- Run \`openspec validate --strict --no-interactive\` to confirm the archived change passes checks
|
||||
|
||||
## Before Any Task
|
||||
|
||||
@@ -108,7 +108,7 @@ openspec validate # Bulk validation mode
|
||||
|
||||
# Debugging
|
||||
openspec show [change] --json --deltas-only
|
||||
openspec validate [change] --strict
|
||||
openspec validate [change] --strict --no-interactive
|
||||
\`\`\`
|
||||
|
||||
### Command Flags
|
||||
@@ -306,7 +306,7 @@ Example for RENAMED:
|
||||
|
||||
\`\`\`bash
|
||||
# Always use strict mode for comprehensive checks
|
||||
openspec validate [change] --strict
|
||||
openspec validate [change] --strict --no-interactive
|
||||
|
||||
# Debug delta parsing
|
||||
openspec show [change] --json | jq '.deltas'
|
||||
@@ -343,7 +343,7 @@ Users MUST provide a second factor during login.
|
||||
EOF
|
||||
|
||||
# 4) Validate
|
||||
openspec validate $CHANGE --strict
|
||||
openspec validate $CHANGE --strict --no-interactive
|
||||
\`\`\`
|
||||
|
||||
## Multi-Capability Example
|
||||
@@ -449,7 +449,7 @@ Only add complexity with:
|
||||
\`\`\`bash
|
||||
openspec list # What's in progress?
|
||||
openspec show [item] # View details
|
||||
openspec validate --strict # Is it correct?
|
||||
openspec validate --strict --no-interactive # Is it correct?
|
||||
openspec archive <change-id> [--yes|-y] # Mark complete (add --yes for automation)
|
||||
\`\`\`
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ export function getExploreSkillTemplate(): SkillTemplate {
|
||||
description: 'Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.',
|
||||
instructions: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
|
||||
|
||||
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first (e.g., start a change with \`/opsx:new\` or \`/opsx:ff\`). You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||
|
||||
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
|
||||
|
||||
---
|
||||
@@ -31,6 +33,7 @@ export function getExploreSkillTemplate(): SkillTemplate {
|
||||
## The Stance
|
||||
|
||||
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
|
||||
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
|
||||
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
|
||||
- **Adaptive** - Follow interesting threads, pivot when new information emerges
|
||||
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
|
||||
@@ -290,6 +293,7 @@ But this summary is optional. Sometimes the thinking IS the value.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
|
||||
- **Don't fake understanding** - If something is unclear, dig deeper
|
||||
- **Don't rush** - Discovery is thinking time, not task time
|
||||
- **Don't force structure** - Let patterns emerge naturally
|
||||
@@ -385,7 +389,7 @@ export function getContinueChangeSkillTemplate(): SkillTemplate {
|
||||
description: 'Continue working on an OpenSpec change by creating the next artifact. Use when the user wants to progress their change, create the next artifact, or continue their workflow.',
|
||||
instructions: `Continue working on a change by creating the next artifact.
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, MUST prompt for available changes.
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
@@ -499,19 +503,18 @@ export function getApplyChangeSkillTemplate(): SkillTemplate {
|
||||
description: 'Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.',
|
||||
instructions: `Implement tasks from an OpenSpec change.
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, MUST prompt for available changes.
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no change name provided, prompt for selection**
|
||||
1. **Select the change**
|
||||
|
||||
Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select.
|
||||
If a name is provided, use it. Otherwise:
|
||||
- Infer from conversation context if the user mentioned a change
|
||||
- Auto-select if only one active change exists
|
||||
- If ambiguous, run \`openspec list --json\` to get available changes and use the **AskUserQuestion tool** to let the user select
|
||||
|
||||
Show changes that are implementation-ready (have tasks artifact).
|
||||
Include the schema used for each change if available.
|
||||
Mark changes with incomplete tasks as "(In Progress)".
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
Always announce: "Using change: <name>" and how to override (e.g., \`/opsx:apply <other>\`).
|
||||
|
||||
2. **Check status to understand the schema**
|
||||
\`\`\`bash
|
||||
@@ -754,7 +757,7 @@ export function getSyncSpecsSkillTemplate(): SkillTemplate {
|
||||
|
||||
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, MUST prompt for available changes.
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
@@ -904,6 +907,8 @@ export function getOpsxExploreCommandTemplate(): CommandTemplate {
|
||||
tags: ['workflow', 'explore', 'experimental', 'thinking'],
|
||||
content: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
|
||||
|
||||
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first (e.g., start a change with \`/opsx:new\` or \`/opsx:ff\`). You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||
|
||||
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
|
||||
|
||||
**Input**: The argument after \`/opsx:explore\` is whatever the user wants to think about. Could be:
|
||||
@@ -918,6 +923,7 @@ export function getOpsxExploreCommandTemplate(): CommandTemplate {
|
||||
## The Stance
|
||||
|
||||
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
|
||||
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
|
||||
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
|
||||
- **Adaptive** - Follow interesting threads, pivot when new information emerges
|
||||
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
|
||||
@@ -1058,6 +1064,7 @@ When things crystallize, you might offer a summary - but it's optional. Sometime
|
||||
|
||||
## Guardrails
|
||||
|
||||
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
|
||||
- **Don't fake understanding** - If something is unclear, dig deeper
|
||||
- **Don't rush** - Discovery is thinking time, not task time
|
||||
- **Don't force structure** - Let patterns emerge naturally
|
||||
@@ -1154,7 +1161,7 @@ export function getOpsxContinueCommandTemplate(): CommandTemplate {
|
||||
tags: ['workflow', 'artifacts', 'experimental'],
|
||||
content: `Continue working on a change by creating the next artifact.
|
||||
|
||||
**Input**: Optionally specify \`--change <name>\` after \`/opsx:continue\`. If omitted, MUST prompt for available changes.
|
||||
**Input**: Optionally specify a change name after \`/opsx:continue\` (e.g., \`/opsx:continue add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
@@ -1269,19 +1276,18 @@ export function getOpsxApplyCommandTemplate(): CommandTemplate {
|
||||
tags: ['workflow', 'artifacts', 'experimental'],
|
||||
content: `Implement tasks from an OpenSpec change.
|
||||
|
||||
**Input**: Optionally specify \`--change <name>\` after \`/opsx:apply\`. If omitted, MUST prompt for available changes.
|
||||
**Input**: Optionally specify a change name (e.g., \`/opsx:apply add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no change name provided, prompt for selection**
|
||||
1. **Select the change**
|
||||
|
||||
Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select.
|
||||
If a name is provided, use it. Otherwise:
|
||||
- Infer from conversation context if the user mentioned a change
|
||||
- Auto-select if only one active change exists
|
||||
- If ambiguous, run \`openspec list --json\` to get available changes and use the **AskUserQuestion tool** to let the user select
|
||||
|
||||
Show changes that are implementation-ready (have tasks artifact).
|
||||
Include the schema used for each change if available.
|
||||
Mark changes with incomplete tasks as "(In Progress)".
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
Always announce: "Using change: <name>" and how to override (e.g., \`/opsx:apply <other>\`).
|
||||
|
||||
2. **Check status to understand the schema**
|
||||
\`\`\`bash
|
||||
@@ -1524,7 +1530,7 @@ export function getArchiveChangeSkillTemplate(): SkillTemplate {
|
||||
description: 'Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.',
|
||||
instructions: `Archive a completed change in the experimental workflow.
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, MUST prompt for available changes.
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
@@ -1563,38 +1569,20 @@ export function getArchiveChangeSkillTemplate(): SkillTemplate {
|
||||
|
||||
**If no tasks file exists:** Proceed without task-related warning.
|
||||
|
||||
4. **Check if delta specs need syncing**
|
||||
4. **Assess delta spec sync state**
|
||||
|
||||
Check if \`specs/\` directory exists in the change with spec files.
|
||||
Check for delta specs at \`openspec/changes/<name>/specs/\`. If none exist, proceed without sync prompt.
|
||||
|
||||
**If delta specs exist, perform a quick sync check:**
|
||||
**If delta specs exist:**
|
||||
- Compare each delta spec with its corresponding main spec at \`openspec/specs/<capability>/spec.md\`
|
||||
- Determine what changes would be applied (adds, modifications, removals, renames)
|
||||
- Show a combined summary before prompting
|
||||
|
||||
a. **For each delta spec** at \`openspec/changes/<name>/specs/<capability>/spec.md\`:
|
||||
- Extract requirement names (lines matching \`### Requirement: <name>\`)
|
||||
- Note which sections exist (ADDED, MODIFIED, REMOVED)
|
||||
**Prompt options:**
|
||||
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||
|
||||
b. **Check corresponding main spec** at \`openspec/specs/<capability>/spec.md\`:
|
||||
- If main spec doesn't exist → needs sync
|
||||
- If main spec exists, check if ADDED requirement names appear in it
|
||||
- If any ADDED requirements are missing from main spec → needs sync
|
||||
|
||||
c. **Report findings:**
|
||||
|
||||
**If sync needed:**
|
||||
\`\`\`
|
||||
⚠️ Delta specs may not be synced:
|
||||
- specs/auth/spec.md → Main spec missing requirement "Token Refresh"
|
||||
- specs/api/spec.md → Main spec doesn't exist yet
|
||||
|
||||
Would you like to sync now before archiving?
|
||||
\`\`\`
|
||||
- Use **AskUserQuestion tool** with options: "Sync now", "Archive without syncing"
|
||||
- If user chooses sync, execute /opsx:sync logic (use the openspec-sync-specs skill)
|
||||
|
||||
**If already synced (all requirements found):**
|
||||
- Proceed without prompting (specs appear to be in sync)
|
||||
|
||||
**If no delta specs exist:** Proceed without sync-related checks.
|
||||
If user chooses sync, execute /opsx:sync logic (use the openspec-sync-specs skill). Proceed to archive regardless of choice.
|
||||
|
||||
5. **Perform the archive**
|
||||
|
||||
@@ -1630,7 +1618,7 @@ export function getArchiveChangeSkillTemplate(): SkillTemplate {
|
||||
**Change:** <change-name>
|
||||
**Schema:** <schema-name>
|
||||
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
|
||||
**Specs:** ✓ Synced to main specs (or "No delta specs" or "⚠️ Not synced")
|
||||
**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped")
|
||||
|
||||
All artifacts complete. All tasks complete.
|
||||
\`\`\`
|
||||
@@ -1642,7 +1630,7 @@ All artifacts complete. All tasks complete.
|
||||
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
|
||||
- Show clear summary of what happened
|
||||
- If sync is requested, use openspec-sync-specs approach (agent-driven)
|
||||
- Quick sync check: look for requirement names in delta specs, verify they exist in main specs`
|
||||
- If delta specs exist, always run the sync assessment and show the combined summary before prompting`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1659,7 +1647,7 @@ export function getOpsxSyncCommandTemplate(): CommandTemplate {
|
||||
|
||||
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
|
||||
|
||||
**Input**: Optionally specify \`--change <name>\` after \`/opsx:sync\`. If omitted, MUST prompt for available changes.
|
||||
**Input**: Optionally specify a change name after \`/opsx:sync\` (e.g., \`/opsx:sync add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
@@ -1785,6 +1773,174 @@ Main specs are now updated. The change remains active - archive when implementat
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Template for openspec-verify-change skill
|
||||
* For verifying implementation matches change artifacts before archiving
|
||||
*/
|
||||
export function getVerifyChangeSkillTemplate(): SkillTemplate {
|
||||
return {
|
||||
name: 'openspec-verify-change',
|
||||
description: 'Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving.',
|
||||
instructions: `Verify that an implementation matches the change artifacts (specs, tasks, design).
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no change name provided, prompt for selection**
|
||||
|
||||
Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select.
|
||||
|
||||
Show changes that have implementation tasks (tasks artifact exists).
|
||||
Include the schema used for each change if available.
|
||||
Mark changes with incomplete tasks as "(In Progress)".
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
|
||||
2. **Check status to understand the schema**
|
||||
\`\`\`bash
|
||||
openspec status --change "<name>" --json
|
||||
\`\`\`
|
||||
Parse the JSON to understand:
|
||||
- \`schemaName\`: The workflow being used (e.g., "spec-driven", "tdd")
|
||||
- Which artifacts exist for this change
|
||||
|
||||
3. **Get the change directory and load artifacts**
|
||||
|
||||
\`\`\`bash
|
||||
openspec instructions apply --change "<name>" --json
|
||||
\`\`\`
|
||||
|
||||
This returns the change directory and context files. Read all available artifacts from \`contextFiles\`.
|
||||
|
||||
4. **Initialize verification report structure**
|
||||
|
||||
Create a report structure with three dimensions:
|
||||
- **Completeness**: Track tasks and spec coverage
|
||||
- **Correctness**: Track requirement implementation and scenario coverage
|
||||
- **Coherence**: Track design adherence and pattern consistency
|
||||
|
||||
Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
|
||||
|
||||
5. **Verify Completeness**
|
||||
|
||||
**Task Completion**:
|
||||
- If tasks.md exists in contextFiles, read it
|
||||
- Parse checkboxes: \`- [ ]\` (incomplete) vs \`- [x]\` (complete)
|
||||
- Count complete vs total tasks
|
||||
- If incomplete tasks exist:
|
||||
- Add CRITICAL issue for each incomplete task
|
||||
- Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
|
||||
|
||||
**Spec Coverage**:
|
||||
- If delta specs exist in \`openspec/changes/<name>/specs/\`:
|
||||
- Extract all requirements (marked with "### Requirement:")
|
||||
- For each requirement:
|
||||
- Search codebase for keywords related to the requirement
|
||||
- Assess if implementation likely exists
|
||||
- If requirements appear unimplemented:
|
||||
- Add CRITICAL issue: "Requirement not found: <requirement name>"
|
||||
- Recommendation: "Implement requirement X: <description>"
|
||||
|
||||
6. **Verify Correctness**
|
||||
|
||||
**Requirement Implementation Mapping**:
|
||||
- For each requirement from delta specs:
|
||||
- Search codebase for implementation evidence
|
||||
- If found, note file paths and line ranges
|
||||
- Assess if implementation matches requirement intent
|
||||
- If divergence detected:
|
||||
- Add WARNING: "Implementation may diverge from spec: <details>"
|
||||
- Recommendation: "Review <file>:<lines> against requirement X"
|
||||
|
||||
**Scenario Coverage**:
|
||||
- For each scenario in delta specs (marked with "#### Scenario:"):
|
||||
- Check if conditions are handled in code
|
||||
- Check if tests exist covering the scenario
|
||||
- If scenario appears uncovered:
|
||||
- Add WARNING: "Scenario not covered: <scenario name>"
|
||||
- Recommendation: "Add test or implementation for scenario: <description>"
|
||||
|
||||
7. **Verify Coherence**
|
||||
|
||||
**Design Adherence**:
|
||||
- If design.md exists in contextFiles:
|
||||
- Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
|
||||
- Verify implementation follows those decisions
|
||||
- If contradiction detected:
|
||||
- Add WARNING: "Design decision not followed: <decision>"
|
||||
- Recommendation: "Update implementation or revise design.md to match reality"
|
||||
- If no design.md: Skip design adherence check, note "No design.md to verify against"
|
||||
|
||||
**Code Pattern Consistency**:
|
||||
- Review new code for consistency with project patterns
|
||||
- Check file naming, directory structure, coding style
|
||||
- If significant deviations found:
|
||||
- Add SUGGESTION: "Code pattern deviation: <details>"
|
||||
- Recommendation: "Consider following project pattern: <example>"
|
||||
|
||||
8. **Generate Verification Report**
|
||||
|
||||
**Summary Scorecard**:
|
||||
\`\`\`
|
||||
## Verification Report: <change-name>
|
||||
|
||||
### Summary
|
||||
| Dimension | Status |
|
||||
|--------------|------------------|
|
||||
| Completeness | X/Y tasks, N reqs|
|
||||
| Correctness | M/N reqs covered |
|
||||
| Coherence | Followed/Issues |
|
||||
\`\`\`
|
||||
|
||||
**Issues by Priority**:
|
||||
|
||||
1. **CRITICAL** (Must fix before archive):
|
||||
- Incomplete tasks
|
||||
- Missing requirement implementations
|
||||
- Each with specific, actionable recommendation
|
||||
|
||||
2. **WARNING** (Should fix):
|
||||
- Spec/design divergences
|
||||
- Missing scenario coverage
|
||||
- Each with specific recommendation
|
||||
|
||||
3. **SUGGESTION** (Nice to fix):
|
||||
- Pattern inconsistencies
|
||||
- Minor improvements
|
||||
- Each with specific recommendation
|
||||
|
||||
**Final Assessment**:
|
||||
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
|
||||
- If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
|
||||
- If all clear: "All checks passed. Ready for archive."
|
||||
|
||||
**Verification Heuristics**
|
||||
|
||||
- **Completeness**: Focus on objective checklist items (checkboxes, requirements list)
|
||||
- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty
|
||||
- **Coherence**: Look for glaring inconsistencies, don't nitpick style
|
||||
- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
|
||||
- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
|
||||
|
||||
**Graceful Degradation**
|
||||
|
||||
- If only tasks.md exists: verify task completion only, skip spec/design checks
|
||||
- If tasks + specs exist: verify completeness and correctness, skip design
|
||||
- If full artifacts: verify all three dimensions
|
||||
- Always note which checks were skipped and why
|
||||
|
||||
**Output Format**
|
||||
|
||||
Use clear markdown with:
|
||||
- Table for summary scorecard
|
||||
- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION)
|
||||
- Code references in format: \`file.ts:123\`
|
||||
- Specific, actionable recommendations
|
||||
- No vague suggestions like "consider reviewing"`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Template for /opsx:archive slash command
|
||||
*/
|
||||
@@ -1796,7 +1952,7 @@ export function getOpsxArchiveCommandTemplate(): CommandTemplate {
|
||||
tags: ['workflow', 'archive', 'experimental'],
|
||||
content: `Archive a completed change in the experimental workflow.
|
||||
|
||||
**Input**: Optionally specify \`--change <name>\` after \`/opsx:archive\`. If omitted, MUST prompt for available changes.
|
||||
**Input**: Optionally specify a change name after \`/opsx:archive\` (e.g., \`/opsx:archive add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
@@ -1835,38 +1991,20 @@ export function getOpsxArchiveCommandTemplate(): CommandTemplate {
|
||||
|
||||
**If no tasks file exists:** Proceed without task-related warning.
|
||||
|
||||
4. **Check if delta specs need syncing**
|
||||
4. **Assess delta spec sync state**
|
||||
|
||||
Check if \`specs/\` directory exists in the change with spec files.
|
||||
Check for delta specs at \`openspec/changes/<name>/specs/\`. If none exist, proceed without sync prompt.
|
||||
|
||||
**If delta specs exist, perform a quick sync check:**
|
||||
**If delta specs exist:**
|
||||
- Compare each delta spec with its corresponding main spec at \`openspec/specs/<capability>/spec.md\`
|
||||
- Determine what changes would be applied (adds, modifications, removals, renames)
|
||||
- Show a combined summary before prompting
|
||||
|
||||
a. **For each delta spec** at \`openspec/changes/<name>/specs/<capability>/spec.md\`:
|
||||
- Extract requirement names (lines matching \`### Requirement: <name>\`)
|
||||
- Note which sections exist (ADDED, MODIFIED, REMOVED)
|
||||
**Prompt options:**
|
||||
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||
|
||||
b. **Check corresponding main spec** at \`openspec/specs/<capability>/spec.md\`:
|
||||
- If main spec doesn't exist → needs sync
|
||||
- If main spec exists, check if ADDED requirement names appear in it
|
||||
- If any ADDED requirements are missing from main spec → needs sync
|
||||
|
||||
c. **Report findings:**
|
||||
|
||||
**If sync needed:**
|
||||
\`\`\`
|
||||
⚠️ Delta specs may not be synced:
|
||||
- specs/auth/spec.md → Main spec missing requirement "Token Refresh"
|
||||
- specs/api/spec.md → Main spec doesn't exist yet
|
||||
|
||||
Would you like to sync now before archiving?
|
||||
\`\`\`
|
||||
- Use **AskUserQuestion tool** with options: "Sync now", "Archive without syncing"
|
||||
- If user chooses sync, execute \`/opsx:sync\` logic
|
||||
|
||||
**If already synced (all requirements found):**
|
||||
- Proceed without prompting (specs appear to be in sync)
|
||||
|
||||
**If no delta specs exist:** Proceed without sync-related checks.
|
||||
If user chooses sync, execute \`/opsx:sync\` logic. Proceed to archive regardless of choice.
|
||||
|
||||
5. **Perform the archive**
|
||||
|
||||
@@ -1891,7 +2029,7 @@ export function getOpsxArchiveCommandTemplate(): CommandTemplate {
|
||||
- Change name
|
||||
- Schema that was used
|
||||
- Archive location
|
||||
- Spec sync status (synced / not synced / no delta specs)
|
||||
- Spec sync status (synced / sync skipped / no delta specs)
|
||||
- Note about any warnings (incomplete artifacts/tasks)
|
||||
|
||||
**Output On Success**
|
||||
@@ -1928,12 +2066,12 @@ All artifacts complete. All tasks complete.
|
||||
**Change:** <change-name>
|
||||
**Schema:** <schema-name>
|
||||
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
|
||||
**Specs:** ⚠️ Not synced
|
||||
**Specs:** Sync skipped (user chose to skip)
|
||||
|
||||
**Warnings:**
|
||||
- Archived with 2 incomplete artifacts
|
||||
- Archived with 3 incomplete tasks
|
||||
- Delta specs were not synced (user chose to skip)
|
||||
- Delta spec sync was skipped (user chose to skip)
|
||||
|
||||
Review the archive if this was not intentional.
|
||||
\`\`\`
|
||||
@@ -1959,8 +2097,285 @@ Target archive directory already exists.
|
||||
- Use artifact graph (openspec status --json) for completion checking
|
||||
- Don't block archive on warnings - just inform and confirm
|
||||
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
|
||||
- Quick sync check: look for requirement names in delta specs, verify they exist in main specs
|
||||
- Show clear summary of what happened
|
||||
- If sync is requested, use /opsx:sync approach (agent-driven)`
|
||||
- If sync is requested, use /opsx:sync approach (agent-driven)
|
||||
- If delta specs exist, always run the sync assessment and show the combined summary before prompting`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Template for /opsx:verify slash command
|
||||
*/
|
||||
export function getOpsxVerifyCommandTemplate(): CommandTemplate {
|
||||
return {
|
||||
name: 'OPSX: Verify',
|
||||
description: 'Verify implementation matches change artifacts before archiving',
|
||||
category: 'Workflow',
|
||||
tags: ['workflow', 'verify', 'experimental'],
|
||||
content: `Verify that an implementation matches the change artifacts (specs, tasks, design).
|
||||
|
||||
**Input**: Optionally specify a change name after \`/opsx:verify\` (e.g., \`/opsx:verify add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no change name provided, prompt for selection**
|
||||
|
||||
Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select.
|
||||
|
||||
Show changes that have implementation tasks (tasks artifact exists).
|
||||
Include the schema used for each change if available.
|
||||
Mark changes with incomplete tasks as "(In Progress)".
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
|
||||
2. **Check status to understand the schema**
|
||||
\`\`\`bash
|
||||
openspec status --change "<name>" --json
|
||||
\`\`\`
|
||||
Parse the JSON to understand:
|
||||
- \`schemaName\`: The workflow being used (e.g., "spec-driven", "tdd")
|
||||
- Which artifacts exist for this change
|
||||
|
||||
3. **Get the change directory and load artifacts**
|
||||
|
||||
\`\`\`bash
|
||||
openspec instructions apply --change "<name>" --json
|
||||
\`\`\`
|
||||
|
||||
This returns the change directory and context files. Read all available artifacts from \`contextFiles\`.
|
||||
|
||||
4. **Initialize verification report structure**
|
||||
|
||||
Create a report structure with three dimensions:
|
||||
- **Completeness**: Track tasks and spec coverage
|
||||
- **Correctness**: Track requirement implementation and scenario coverage
|
||||
- **Coherence**: Track design adherence and pattern consistency
|
||||
|
||||
Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
|
||||
|
||||
5. **Verify Completeness**
|
||||
|
||||
**Task Completion**:
|
||||
- If tasks.md exists in contextFiles, read it
|
||||
- Parse checkboxes: \`- [ ]\` (incomplete) vs \`- [x]\` (complete)
|
||||
- Count complete vs total tasks
|
||||
- If incomplete tasks exist:
|
||||
- Add CRITICAL issue for each incomplete task
|
||||
- Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
|
||||
|
||||
**Spec Coverage**:
|
||||
- If delta specs exist in \`openspec/changes/<name>/specs/\`:
|
||||
- Extract all requirements (marked with "### Requirement:")
|
||||
- For each requirement:
|
||||
- Search codebase for keywords related to the requirement
|
||||
- Assess if implementation likely exists
|
||||
- If requirements appear unimplemented:
|
||||
- Add CRITICAL issue: "Requirement not found: <requirement name>"
|
||||
- Recommendation: "Implement requirement X: <description>"
|
||||
|
||||
6. **Verify Correctness**
|
||||
|
||||
**Requirement Implementation Mapping**:
|
||||
- For each requirement from delta specs:
|
||||
- Search codebase for implementation evidence
|
||||
- If found, note file paths and line ranges
|
||||
- Assess if implementation matches requirement intent
|
||||
- If divergence detected:
|
||||
- Add WARNING: "Implementation may diverge from spec: <details>"
|
||||
- Recommendation: "Review <file>:<lines> against requirement X"
|
||||
|
||||
**Scenario Coverage**:
|
||||
- For each scenario in delta specs (marked with "#### Scenario:"):
|
||||
- Check if conditions are handled in code
|
||||
- Check if tests exist covering the scenario
|
||||
- If scenario appears uncovered:
|
||||
- Add WARNING: "Scenario not covered: <scenario name>"
|
||||
- Recommendation: "Add test or implementation for scenario: <description>"
|
||||
|
||||
7. **Verify Coherence**
|
||||
|
||||
**Design Adherence**:
|
||||
- If design.md exists in contextFiles:
|
||||
- Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
|
||||
- Verify implementation follows those decisions
|
||||
- If contradiction detected:
|
||||
- Add WARNING: "Design decision not followed: <decision>"
|
||||
- Recommendation: "Update implementation or revise design.md to match reality"
|
||||
- If no design.md: Skip design adherence check, note "No design.md to verify against"
|
||||
|
||||
**Code Pattern Consistency**:
|
||||
- Review new code for consistency with project patterns
|
||||
- Check file naming, directory structure, coding style
|
||||
- If significant deviations found:
|
||||
- Add SUGGESTION: "Code pattern deviation: <details>"
|
||||
- Recommendation: "Consider following project pattern: <example>"
|
||||
|
||||
8. **Generate Verification Report**
|
||||
|
||||
**Summary Scorecard**:
|
||||
\`\`\`
|
||||
## Verification Report: <change-name>
|
||||
|
||||
### Summary
|
||||
| Dimension | Status |
|
||||
|--------------|------------------|
|
||||
| Completeness | X/Y tasks, N reqs|
|
||||
| Correctness | M/N reqs covered |
|
||||
| Coherence | Followed/Issues |
|
||||
\`\`\`
|
||||
|
||||
**Issues by Priority**:
|
||||
|
||||
1. **CRITICAL** (Must fix before archive):
|
||||
- Incomplete tasks
|
||||
- Missing requirement implementations
|
||||
- Each with specific, actionable recommendation
|
||||
|
||||
2. **WARNING** (Should fix):
|
||||
- Spec/design divergences
|
||||
- Missing scenario coverage
|
||||
- Each with specific recommendation
|
||||
|
||||
3. **SUGGESTION** (Nice to fix):
|
||||
- Pattern inconsistencies
|
||||
- Minor improvements
|
||||
- Each with specific recommendation
|
||||
|
||||
**Final Assessment**:
|
||||
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
|
||||
- If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
|
||||
- If all clear: "All checks passed. Ready for archive."
|
||||
|
||||
**Verification Heuristics**
|
||||
|
||||
- **Completeness**: Focus on objective checklist items (checkboxes, requirements list)
|
||||
- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty
|
||||
- **Coherence**: Look for glaring inconsistencies, don't nitpick style
|
||||
- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
|
||||
- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
|
||||
|
||||
**Graceful Degradation**
|
||||
|
||||
- If only tasks.md exists: verify task completion only, skip spec/design checks
|
||||
- If tasks + specs exist: verify completeness and correctness, skip design
|
||||
- If full artifacts: verify all three dimensions
|
||||
- Always note which checks were skipped and why
|
||||
|
||||
**Output Format**
|
||||
|
||||
Use clear markdown with:
|
||||
- Table for summary scorecard
|
||||
- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION)
|
||||
- Code references in format: \`file.ts:123\`
|
||||
- Specific, actionable recommendations
|
||||
- No vague suggestions like "consider reviewing"`
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Template for feedback skill
|
||||
* For collecting and submitting user feedback with context enrichment
|
||||
*/
|
||||
export function getFeedbackSkillTemplate(): SkillTemplate {
|
||||
return {
|
||||
name: 'feedback',
|
||||
description: 'Collect and submit user feedback about OpenSpec with context enrichment and anonymization.',
|
||||
instructions: `Help the user submit feedback about OpenSpec.
|
||||
|
||||
**Goal**: Guide the user through collecting, enriching, and submitting feedback while ensuring privacy through anonymization.
|
||||
|
||||
**Process**
|
||||
|
||||
1. **Gather context from the conversation**
|
||||
- Review recent conversation history for context
|
||||
- Identify what task was being performed
|
||||
- Note what worked well or poorly
|
||||
- Capture specific friction points or praise
|
||||
|
||||
2. **Draft enriched feedback**
|
||||
- Create a clear, descriptive title (single sentence, no "Feedback:" prefix needed)
|
||||
- Write a body that includes:
|
||||
- What the user was trying to do
|
||||
- What happened (good or bad)
|
||||
- Relevant context from the conversation
|
||||
- Any specific suggestions or requests
|
||||
|
||||
3. **Anonymize sensitive information**
|
||||
- Replace file paths with \`<path>\` or generic descriptions
|
||||
- Replace API keys, tokens, secrets with \`<redacted>\`
|
||||
- Replace company/organization names with \`<company>\`
|
||||
- Replace personal names with \`<user>\`
|
||||
- Replace specific URLs with \`<url>\` unless public/relevant
|
||||
- Keep technical details that help understand the issue
|
||||
|
||||
4. **Present draft for approval**
|
||||
- Show the complete draft to the user
|
||||
- Display both title and body clearly
|
||||
- Ask for explicit approval before submitting
|
||||
- Allow the user to request modifications
|
||||
|
||||
5. **Submit on confirmation**
|
||||
- Use the \`openspec feedback\` command to submit
|
||||
- Format: \`openspec feedback "title" --body "body content"\`
|
||||
- The command will automatically add metadata (version, platform, timestamp)
|
||||
|
||||
**Example Draft**
|
||||
|
||||
\`\`\`
|
||||
Title: Error handling in artifact workflow needs improvement
|
||||
|
||||
Body:
|
||||
I was working on creating a new change and encountered an issue with
|
||||
the artifact workflow. When I tried to continue after creating the
|
||||
proposal, the system didn't clearly indicate that I needed to complete
|
||||
the specs first.
|
||||
|
||||
Suggestion: Add clearer error messages that explain dependency chains
|
||||
in the artifact workflow. Something like "Cannot create design.md
|
||||
because specs are not complete (0/2 done)."
|
||||
|
||||
Context: Using the spec-driven schema with <path>/my-project
|
||||
\`\`\`
|
||||
|
||||
**Anonymization Examples**
|
||||
|
||||
Before:
|
||||
\`\`\`
|
||||
Working on /Users/john/mycompany/auth-service/src/oauth.ts
|
||||
Failed with API key: sk_live_abc123xyz
|
||||
Working at Acme Corp
|
||||
\`\`\`
|
||||
|
||||
After:
|
||||
\`\`\`
|
||||
Working on <path>/oauth.ts
|
||||
Failed with API key: <redacted>
|
||||
Working at <company>
|
||||
\`\`\`
|
||||
|
||||
**Guardrails**
|
||||
|
||||
- MUST show complete draft before submitting
|
||||
- MUST ask for explicit approval
|
||||
- MUST anonymize sensitive information
|
||||
- ALLOW user to modify draft before submitting
|
||||
- DO NOT submit without user confirmation
|
||||
- DO include relevant technical context
|
||||
- DO keep conversation-specific insights
|
||||
|
||||
**User Confirmation Required**
|
||||
|
||||
Always ask:
|
||||
\`\`\`
|
||||
Here's the feedback I've drafted:
|
||||
|
||||
Title: [title]
|
||||
|
||||
Body:
|
||||
[body]
|
||||
|
||||
Does this look good? I can modify it if you'd like, or submit it as-is.
|
||||
\`\`\`
|
||||
|
||||
Only proceed with submission after user confirms.`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const proposalSteps = `**Steps**
|
||||
4. Capture architectural reasoning in \`design.md\` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs.
|
||||
5. Draft spec deltas in \`changes/<id>/specs/<capability>/spec.md\` (one folder per capability) using \`## ADDED|MODIFIED|REMOVED Requirements\` with at least one \`#### Scenario:\` per requirement and cross-reference related capabilities when relevant.
|
||||
6. Draft \`tasks.md\` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work.
|
||||
7. Validate with \`openspec validate <id> --strict\` and resolve every issue before sharing the proposal.`;
|
||||
7. Validate with \`openspec validate <id> --strict --no-interactive\` and resolve every issue before sharing the proposal.`;
|
||||
|
||||
|
||||
const proposalReferences = `**Reference**
|
||||
@@ -43,7 +43,7 @@ const archiveSteps = `**Steps**
|
||||
2. Validate the change ID by running \`openspec list\` (or \`openspec show <id>\`) and stop if the change is missing, already archived, or otherwise not ready to archive.
|
||||
3. Run \`openspec archive <id> --yes\` so the CLI moves the change and applies spec updates without prompts (use \`--skip-specs\` only for tooling-only work).
|
||||
4. Review the command output to confirm the target specs were updated and the change landed in \`changes/archive/\`.
|
||||
5. Validate with \`openspec validate --strict\` and inspect with \`openspec show <id>\` if anything looks off.`;
|
||||
5. Validate with \`openspec validate --strict --no-interactive\` and inspect with \`openspec show <id>\` if anything looks off.`;
|
||||
|
||||
const archiveReferences = `**Reference**
|
||||
- Use \`openspec list\` to confirm change IDs before archiving.
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { FeedbackCommand } from '../../src/commands/feedback.js';
|
||||
import { execSync, execFileSync } from 'child_process';
|
||||
|
||||
// Mock child_process functions
|
||||
vi.mock('child_process', () => ({
|
||||
execSync: vi.fn(),
|
||||
execFileSync: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('FeedbackCommand', () => {
|
||||
let feedbackCommand: FeedbackCommand;
|
||||
let consoleLogSpy: any;
|
||||
let consoleErrorSpy: any;
|
||||
let processExitSpy: any;
|
||||
const mockExecSync = execSync as unknown as ReturnType<typeof vi.fn>;
|
||||
const mockExecFileSync = execFileSync as unknown as ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
feedbackCommand = new FeedbackCommand();
|
||||
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
processExitSpy = vi.spyOn(process, 'exit').mockImplementation((code?: string | number | null) => {
|
||||
throw new Error(`process.exit(${code})`);
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('gh CLI availability check', () => {
|
||||
it('should use which command on Unix/macOS platforms', async () => {
|
||||
// Mock platform as darwin
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
||||
|
||||
mockExecSync.mockImplementation((cmd: string) => {
|
||||
if (cmd === 'which gh') {
|
||||
return Buffer.from('/usr/local/bin/gh');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
return Buffer.from('Logged in');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
mockExecFileSync.mockReturnValue('https://github.com/Fission-AI/OpenSpec/issues/123\n');
|
||||
|
||||
await feedbackCommand.execute('Test');
|
||||
|
||||
// Verify 'which gh' was called
|
||||
expect(mockExecSync).toHaveBeenCalledWith('which gh', expect.any(Object));
|
||||
|
||||
// Restore original platform
|
||||
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
||||
});
|
||||
|
||||
it('should use where command on Windows platform', async () => {
|
||||
// Mock platform as win32
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, 'platform', { value: 'win32' });
|
||||
|
||||
mockExecSync.mockImplementation((cmd: string) => {
|
||||
if (cmd === 'where gh') {
|
||||
return Buffer.from('C:\\Program Files\\GitHub CLI\\gh.exe');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
return Buffer.from('Logged in');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
mockExecFileSync.mockReturnValue('https://github.com/Fission-AI/OpenSpec/issues/123\n');
|
||||
|
||||
await feedbackCommand.execute('Test');
|
||||
|
||||
// Verify 'where gh' was called
|
||||
expect(mockExecSync).toHaveBeenCalledWith('where gh', expect.any(Object));
|
||||
|
||||
// Restore original platform
|
||||
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
||||
});
|
||||
|
||||
it('should handle missing gh CLI with fallback', async () => {
|
||||
// Simulate gh not installed
|
||||
mockExecSync.mockImplementation((cmd: string) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
throw new Error('Command not found');
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await feedbackCommand.execute('Test feedback');
|
||||
} catch (error: any) {
|
||||
// Should exit with code 0 (successful fallback)
|
||||
expect(error.message).toBe('process.exit(0)');
|
||||
}
|
||||
|
||||
// Should display warning
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('GitHub CLI not found')
|
||||
);
|
||||
|
||||
// Should show formatted feedback
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('--- FORMATTED FEEDBACK ---')
|
||||
);
|
||||
|
||||
// Should show manual submission URL
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('https://github.com/Fission-AI/OpenSpec/issues/new')
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle unauthenticated gh CLI with fallback', async () => {
|
||||
// Simulate gh installed but not authenticated
|
||||
mockExecSync.mockImplementation((cmd: string) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
return Buffer.from('/usr/local/bin/gh');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await feedbackCommand.execute('Test feedback');
|
||||
} catch (error: any) {
|
||||
// Should exit with code 0 (successful fallback)
|
||||
expect(error.message).toBe('process.exit(0)');
|
||||
}
|
||||
|
||||
// Should display warning
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('GitHub authentication required')
|
||||
);
|
||||
|
||||
// Should show auth instructions
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('To auto-submit in the future: gh auth login')
|
||||
);
|
||||
|
||||
// Should show formatted feedback
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('--- FORMATTED FEEDBACK ---')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('successful feedback submission', () => {
|
||||
it('should submit feedback via gh CLI when authenticated', async () => {
|
||||
const issueUrl = 'https://github.com/Fission-AI/OpenSpec/issues/123';
|
||||
|
||||
// Simulate gh installed and authenticated
|
||||
mockExecSync.mockImplementation((cmd: string, options?: any) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
return Buffer.from('/usr/local/bin/gh');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
return Buffer.from('Logged in');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
mockExecFileSync.mockReturnValue(`${issueUrl}\n`);
|
||||
|
||||
await feedbackCommand.execute('Great tool!');
|
||||
|
||||
// Should call gh with correct arguments using execFileSync
|
||||
expect(mockExecFileSync).toHaveBeenCalledWith(
|
||||
'gh',
|
||||
[
|
||||
'issue',
|
||||
'create',
|
||||
'--repo',
|
||||
'Fission-AI/OpenSpec',
|
||||
'--title',
|
||||
'Feedback: Great tool!',
|
||||
'--body',
|
||||
expect.stringContaining('Submitted via OpenSpec CLI'),
|
||||
'--label',
|
||||
'feedback',
|
||||
],
|
||||
expect.objectContaining({
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
})
|
||||
);
|
||||
|
||||
// Should display success message
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Feedback submitted successfully')
|
||||
);
|
||||
|
||||
// Should display issue URL
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(issueUrl)
|
||||
);
|
||||
});
|
||||
|
||||
it('should include --body flag when body is provided', async () => {
|
||||
const issueUrl = 'https://github.com/Fission-AI/OpenSpec/issues/124';
|
||||
|
||||
mockExecSync.mockImplementation((cmd: string, options?: any) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
return Buffer.from('/usr/local/bin/gh');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
return Buffer.from('Logged in');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
mockExecFileSync.mockReturnValue(`${issueUrl}\n`);
|
||||
|
||||
await feedbackCommand.execute('Title here', { body: 'Detailed description' });
|
||||
|
||||
// Verify body is included in the arguments
|
||||
expect(mockExecFileSync).toHaveBeenCalledWith(
|
||||
'gh',
|
||||
expect.arrayContaining([
|
||||
'--body',
|
||||
expect.stringContaining('Detailed description'),
|
||||
]),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('should format title with "Feedback:" prefix', async () => {
|
||||
mockExecSync.mockImplementation((cmd: string, options?: any) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
return Buffer.from('/usr/local/bin/gh');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
return Buffer.from('Logged in');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
mockExecFileSync.mockReturnValue('https://github.com/Fission-AI/OpenSpec/issues/125\n');
|
||||
|
||||
await feedbackCommand.execute('Test message');
|
||||
|
||||
// Verify title has "Feedback:" prefix
|
||||
expect(mockExecFileSync).toHaveBeenCalledWith(
|
||||
'gh',
|
||||
expect.arrayContaining([
|
||||
'--title',
|
||||
'Feedback: Test message',
|
||||
]),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('should include metadata in issue body', async () => {
|
||||
mockExecSync.mockImplementation((cmd: string, options?: any) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
return Buffer.from('/usr/local/bin/gh');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
return Buffer.from('Logged in');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
mockExecFileSync.mockReturnValue('https://github.com/Fission-AI/OpenSpec/issues/126\n');
|
||||
|
||||
await feedbackCommand.execute('Test', { body: 'Body text' });
|
||||
|
||||
// Verify metadata is included in body
|
||||
expect(mockExecFileSync).toHaveBeenCalledWith(
|
||||
'gh',
|
||||
expect.arrayContaining([
|
||||
'--body',
|
||||
expect.stringMatching(/Submitted via OpenSpec CLI[\s\S]*Version:[\s\S]*Platform:[\s\S]*Timestamp:/),
|
||||
]),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('should add feedback label to the issue', async () => {
|
||||
mockExecSync.mockImplementation((cmd: string, options?: any) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
return Buffer.from('/usr/local/bin/gh');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
return Buffer.from('Logged in');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
mockExecFileSync.mockReturnValue('https://github.com/Fission-AI/OpenSpec/issues/127\n');
|
||||
|
||||
await feedbackCommand.execute('Test');
|
||||
|
||||
// Verify feedback label is added
|
||||
expect(mockExecFileSync).toHaveBeenCalledWith(
|
||||
'gh',
|
||||
expect.arrayContaining([
|
||||
'--label',
|
||||
'feedback',
|
||||
]),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should handle gh CLI execution failure', async () => {
|
||||
mockExecSync.mockImplementation((cmd: string, options?: any) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
return Buffer.from('/usr/local/bin/gh');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
return Buffer.from('Logged in');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// Mock execFileSync to throw error
|
||||
mockExecFileSync.mockImplementation(() => {
|
||||
const error: any = new Error('Network error');
|
||||
error.status = 1;
|
||||
error.stderr = Buffer.from('Error: Network connectivity issue');
|
||||
throw error;
|
||||
});
|
||||
|
||||
try {
|
||||
await feedbackCommand.execute('Test');
|
||||
} catch (error: any) {
|
||||
// Should exit with the same code as gh CLI
|
||||
expect(error.message).toBe('process.exit(1)');
|
||||
}
|
||||
|
||||
// Should display the error from gh CLI
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Network connectivity issue')
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle quotes in title and body without escaping (no shell injection)', async () => {
|
||||
mockExecSync.mockImplementation((cmd: string, options?: any) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
return Buffer.from('/usr/local/bin/gh');
|
||||
}
|
||||
if (cmd === 'gh auth status') {
|
||||
return Buffer.from('Logged in');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
mockExecFileSync.mockReturnValue('https://github.com/Fission-AI/OpenSpec/issues/128\n');
|
||||
|
||||
await feedbackCommand.execute('Test with "quotes"', {
|
||||
body: 'Body with "quotes"',
|
||||
});
|
||||
|
||||
// Verify quotes are passed as-is (no escaping needed with execFileSync)
|
||||
expect(mockExecFileSync).toHaveBeenCalledWith(
|
||||
'gh',
|
||||
expect.arrayContaining([
|
||||
'--title',
|
||||
'Feedback: Test with "quotes"',
|
||||
'--body',
|
||||
expect.stringContaining('Body with "quotes"'),
|
||||
]),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatted feedback output', () => {
|
||||
it('should display formatted feedback with proper structure', async () => {
|
||||
mockExecSync.mockImplementation((cmd: string) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
throw new Error('Command not found');
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await feedbackCommand.execute('Test message', { body: 'Test body' });
|
||||
} catch (error: any) {
|
||||
// Expected to exit
|
||||
}
|
||||
|
||||
// Verify formatted output structure
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('--- FORMATTED FEEDBACK ---')
|
||||
);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Title: Feedback: Test message')
|
||||
);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Labels: feedback')
|
||||
);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('--- END FEEDBACK ---')
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate correct manual submission URL', async () => {
|
||||
mockExecSync.mockImplementation((cmd: string) => {
|
||||
if (cmd === 'which gh' || cmd === 'where gh') {
|
||||
throw new Error('Command not found');
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await feedbackCommand.execute('Test');
|
||||
} catch (error: any) {
|
||||
// Expected to exit
|
||||
}
|
||||
|
||||
// Verify URL is shown
|
||||
const urlCall = consoleLogSpy.mock.calls.find((call: any[]) =>
|
||||
call[0]?.includes('https://github.com/Fission-AI/OpenSpec/issues/new')
|
||||
);
|
||||
expect(urlCall).toBeDefined();
|
||||
|
||||
// Verify URL has proper parameters
|
||||
const url = urlCall?.[0];
|
||||
expect(url).toContain('title=');
|
||||
expect(url).toContain('body=');
|
||||
expect(url).toContain('labels=feedback');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import {describe, it, expect, beforeEach} from 'vitest';
|
||||
import {PowerShellGenerator} from '../../../../src/core/completions/generators/powershell-generator.js';
|
||||
import {CommandDefinition} from '../../../../src/core/completions/types.js';
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { PowerShellGenerator } from '../../../../src/core/completions/generators/powershell-generator.js';
|
||||
import { CommandDefinition } from '../../../../src/core/completions/types.js';
|
||||
|
||||
describe('PowerShellGenerator', () => {
|
||||
let generator: PowerShellGenerator;
|
||||
@@ -444,6 +444,34 @@ describe('PowerShellGenerator', () => {
|
||||
expect(script).toContain('Get-OpenSpecSpecs');
|
||||
});
|
||||
|
||||
it('should not emit trailing commas in @() arrays', () => {
|
||||
const commands: CommandDefinition[] = [
|
||||
{
|
||||
name: 'config',
|
||||
description: 'Manage configuration',
|
||||
flags: [
|
||||
{
|
||||
name: 'scope',
|
||||
short: 's',
|
||||
description: 'Configuration scope',
|
||||
},
|
||||
],
|
||||
subcommands: [
|
||||
{
|
||||
name: 'get',
|
||||
description: 'Get a config value',
|
||||
flags: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const script = generator.generate(commands);
|
||||
|
||||
// PowerShell array literals (@(...)) can't have a trailing comma on the last element.
|
||||
expect(script).not.toMatch(/\},\s*\r?\n\s*\)/);
|
||||
});
|
||||
|
||||
it('should handle empty command list', () => {
|
||||
const commands: CommandDefinition[] = [];
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ Old slash content
|
||||
expect(updated).toContain('name: OpenSpec: Proposal');
|
||||
expect(updated).toContain('**Guardrails**');
|
||||
expect(updated).toContain(
|
||||
'Validate with `openspec validate <id> --strict`'
|
||||
'Validate with `openspec validate <id> --strict --no-interactive`'
|
||||
);
|
||||
expect(updated).not.toContain('Old slash content');
|
||||
|
||||
@@ -317,7 +317,7 @@ Old slash content
|
||||
expect(updated).toContain('# OpenSpec: Proposal');
|
||||
expect(updated).toContain('**Guardrails**');
|
||||
expect(updated).toContain(
|
||||
'Validate with `openspec validate <id> --strict`'
|
||||
'Validate with `openspec validate <id> --strict --no-interactive`'
|
||||
);
|
||||
expect(updated).not.toContain('Old slash content');
|
||||
|
||||
@@ -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,
|
||||
@@ -933,7 +1007,7 @@ Old slash content
|
||||
expect(updated).toContain('name: OpenSpec: Proposal');
|
||||
expect(updated).toContain('**Guardrails**');
|
||||
expect(updated).toContain(
|
||||
'Validate with `openspec validate <id> --strict`'
|
||||
'Validate with `openspec validate <id> --strict --no-interactive`'
|
||||
);
|
||||
expect(updated).not.toContain('Old slash content');
|
||||
|
||||
@@ -1011,7 +1085,7 @@ Old slash content
|
||||
expect(updated).toContain('name: OpenSpec: Proposal');
|
||||
expect(updated).toContain('**Guardrails**');
|
||||
expect(updated).toContain(
|
||||
'Validate with `openspec validate <id> --strict`'
|
||||
'Validate with `openspec validate <id> --strict --no-interactive`'
|
||||
);
|
||||
expect(updated).not.toContain('Old slash content');
|
||||
|
||||
@@ -1089,7 +1163,7 @@ Old body
|
||||
expect(updated).toContain('argument-hint: old-hint');
|
||||
expect(updated).toContain('**Guardrails**');
|
||||
expect(updated).toContain(
|
||||
'Validate with `openspec validate <id> --strict`'
|
||||
'Validate with `openspec validate <id> --strict --no-interactive`'
|
||||
);
|
||||
expect(updated).not.toContain('Old body');
|
||||
|
||||
@@ -1130,7 +1204,7 @@ Old slash content
|
||||
expect(updated).toContain('name: OpenSpec: Proposal');
|
||||
expect(updated).toContain('**Guardrails**');
|
||||
expect(updated).toContain(
|
||||
'Validate with `openspec validate <id> --strict`'
|
||||
'Validate with `openspec validate <id> --strict --no-interactive`'
|
||||
);
|
||||
expect(updated).not.toContain('Old slash content');
|
||||
|
||||
@@ -1170,7 +1244,7 @@ Old body
|
||||
expect(updated).toContain('# OpenSpec: Proposal');
|
||||
expect(updated).toContain('**Guardrails**');
|
||||
expect(updated).toContain(
|
||||
'Validate with `openspec validate <id> --strict`'
|
||||
'Validate with `openspec validate <id> --strict --no-interactive`'
|
||||
);
|
||||
expect(updated).not.toContain('Old body');
|
||||
|
||||
@@ -1357,7 +1431,7 @@ More instructions after.`;
|
||||
expect(updated).toContain('## Custom Intro Title');
|
||||
expect(updated).toContain('Footer stays');
|
||||
expect(updated).not.toContain('Old body');
|
||||
expect(updated).toContain('Validate with `openspec validate <id> --strict`');
|
||||
expect(updated).toContain('Validate with `openspec validate <id> --strict --no-interactive`');
|
||||
});
|
||||
|
||||
it('should handle configurator errors gracefully for CoStrict', async () => {
|
||||
@@ -1413,7 +1487,7 @@ More instructions after.`;
|
||||
expect(updated).toContain('## Custom Intro Title');
|
||||
expect(updated).toContain('Footer stays');
|
||||
expect(updated).not.toContain('Old body');
|
||||
expect(updated).toContain('Validate with `openspec validate <id> --strict`');
|
||||
expect(updated).toContain('Validate with `openspec validate <id> --strict --no-interactive`');
|
||||
});
|
||||
|
||||
it('should not create missing Windsurf workflows on update', async () => {
|
||||
|
||||
+23
-2
@@ -1,12 +1,33 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import os from 'node:os';
|
||||
|
||||
function resolveMaxWorkers(): number | undefined {
|
||||
// Allow callers (CI/agents) to override without editing config.
|
||||
const raw = process.env.VITEST_MAX_WORKERS;
|
||||
if (raw) {
|
||||
const parsed = Number(raw);
|
||||
if (Number.isFinite(parsed) && parsed > 0) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
// Vitest v3 defaults to `pool: "forks"` and scales worker processes with CPU.
|
||||
// This repo's tests can spawn many Node processes (CLI invocations, temp FS),
|
||||
// so cap parallelism to avoid runaway CPU/memory usage in automation.
|
||||
const cpuCount = typeof os.availableParallelism === 'function'
|
||||
? os.availableParallelism()
|
||||
: os.cpus().length;
|
||||
return Math.min(4, Math.max(1, cpuCount));
|
||||
}
|
||||
|
||||
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
|
||||
// Tests rely on per-file process isolation (e.g., `process.cwd()` assumptions).
|
||||
pool: 'forks',
|
||||
maxWorkers: resolveMaxWorkers(),
|
||||
include: ['test/**/*.test.ts'],
|
||||
coverage: {
|
||||
reporter: ['text', 'json', 'html'],
|
||||
|
||||
Reference in New Issue
Block a user