mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-04 14:38:54 +08:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0084be39a7 | ||
|
|
fe83be5d61 | ||
|
|
f3eed6fab8 | ||
|
|
345f9dbb45 | ||
|
|
cc9d5402ff | ||
|
|
108bcd66d8 | ||
|
|
a50105e03c | ||
|
|
312e1d6d7c | ||
|
|
56d57da119 | ||
|
|
f56189a8f7 | ||
|
|
d7e0ce85e5 | ||
|
|
eb0d50c094 | ||
|
|
c482f1b47a | ||
|
|
2ae0484ac7 | ||
|
|
8c65b47abe |
@@ -1,5 +1,23 @@
|
||||
# @fission-ai/openspec
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 312e1d6: Add Amazon Q Developer CLI integration. OpenSpec now supports Amazon Q Developer with automatic prompt generation in `.amazonq/prompts/` directory, allowing you to use OpenSpec slash commands with Amazon Q's @-syntax.
|
||||
|
||||
## 0.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d7e0ce8: Improve init wizard Enter key behavior to allow proceeding through prompts more naturally
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2ae0484: Fix cross-platform path handling issues. This release includes fixes for joinPath behavior and slash command path resolution to ensure OpenSpec works correctly across all platforms.
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -97,6 +97,7 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe
|
||||
| **Windsurf** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.windsurf/workflows/`) |
|
||||
| **Codex** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (global: `~/.codex/prompts`, auto-installed) |
|
||||
| **GitHub Copilot** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.github/prompts/`) |
|
||||
| **Amazon Q Developer** | `@openspec-proposal`, `@openspec-apply`, `@openspec-archive` (`.amazonq/prompts/`) |
|
||||
|
||||
Kilo Code discovers team workflows automatically. Save the generated files under `.kilocode/workflows/` and trigger them from the command palette with `/openspec-proposal.md`, `/openspec-apply.md`, or `/openspec-archive.md`.
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# OpenSpec Parallel Delta Remediation Plan
|
||||
|
||||
## Problem Summary
|
||||
- Active changes apply requirement-level replacements when archiving. When two changes touch the same requirement, the second archive overwrites the first and silently drops scenarios (e.g., Windsurf vs. Kilo Code slash command updates).
|
||||
- The archive workflow (`src/core/archive.ts:191` and `src/core/archive.ts:501`) rebuilds main specs by replacing entire requirement blocks with the content contained in the change delta. The delta format (`src/core/parsers/requirement-blocks.ts:113`) has no notion of base versions or scenario-level operations.
|
||||
- The tooling cannot detect divergence between the change author’s starting point and the live spec, so parallel development corrupts the source of truth without warning.
|
||||
|
||||
## Observed Failure Mode
|
||||
- Change A (`add-windsurf-workflows`) adds a Windsurf scenario under `Slash Command Configuration`.
|
||||
- Change B (`add-kilocode-workflows`) adds a Kilo Code scenario to the same requirement, starting from the pre-Windsurf spec.
|
||||
- After Change A archives, the main spec contains both scenarios.
|
||||
- When Change B archives, `buildUpdatedSpec` sees a `MODIFIED` block for `Slash Command Configuration` and replaces the requirement with the four-scenario variant shipped in that change. Because that file never learned about Windsurf, the Windsurf scenario disappears.
|
||||
- There is no warning, diff, or conflict indicator—the archive completes successfully, and the source-of-truth spec now omits a shipped scenario.
|
||||
|
||||
## Root Causes
|
||||
1. **Replace-only semantics.** `buildUpdatedSpec` performs hash-map substitution of requirement blocks and cannot merge or compare individual scenarios (`src/core/archive.ts:455`-`src/core/archive.ts:526`).
|
||||
2. **Missing base fingerprint.** Changes do not persist the requirement content they were authored against, so the archive step cannot tell if the live spec diverged.
|
||||
3. **Single-level granularity.** The delta language only understands requirements. Even if we introduced scenario-level parsing, we would still lose sibling edits without an accompanying merge strategy.
|
||||
4. **Lack of conflict UX.** The CLI never forces contributors to reconcile parallel updates. There is no equivalent of `git merge`, `git rebase`, or conflict markers.
|
||||
|
||||
## Design Objectives
|
||||
- Preserve every approved scenario regardless of archive order.
|
||||
- Detect and block speculative archives when the live spec diverges from the author’s base.
|
||||
- Provide a deterministic, reviewable conflict resolution flow that mirrors source-control best practices.
|
||||
- Keep the authoring experience ergonomic: deltas should remain human-editable markdown.
|
||||
- Support incremental adoption so existing repositories can roll forward without breaking active work.
|
||||
|
||||
## Proposed Fix: Layered Remediation
|
||||
|
||||
### Phase 0 – Stop the Bleeding (Detection & Guardrails)
|
||||
1. **Persist requirement fingerprints alongside each change.**
|
||||
- When scaffolding or validating a change, capture the current requirement body for every `MODIFIED`/`REMOVED`/`RENAMED` entry and write it to `changes/<id>/meta.json`.
|
||||
- Store a stable hash (e.g., SHA-256) of the base requirement content and the raw text itself for later merges.
|
||||
2. **Validate fingerprints during archive.**
|
||||
- Before `buildUpdatedSpec` mutates specs, recompute the requirement hash from the live spec.
|
||||
- If the hash differs from the stored base, abort and instruct the user to rebase. This makes the destructive path impossible.
|
||||
3. **Surface intent in CLI output.**
|
||||
- Show which requirements are stale, when they diverged, and which change last touched them.
|
||||
4. **Document interim manual mitigation.**
|
||||
- Update `openspec/AGENTS.md` and docs so contributors know to rerun `openspec change sync` (see Phase 1) whenever another change lands.
|
||||
|
||||
_Outcome:_ We prevent data loss immediately while we work on a richer merge story.
|
||||
|
||||
### Phase 1 – Add a Rebase Workflow (Author-Side Merge)
|
||||
1. **Introduce `openspec change sync <id>` (or `rebase`).**
|
||||
- Reads the stored base snapshot, the current spec, and the author’s delta.
|
||||
- Performs a 3-way merge per requirement. A naive diff3 on markdown lines is acceptable initially because we already operate on requirement-sized chunks.
|
||||
- If the merge is clean, rewrite the `MODIFIED` block with the merged text and refresh the stored fingerprint.
|
||||
- On conflict, write conflict markers inside the change delta (similar to Git) and require the author to hand-edit before re-running validation.
|
||||
2. **Enrich validator messages.**
|
||||
- `openspec validate` should flag unresolved conflict markers or fingerprint mismatches so errors appear early in the workflow.
|
||||
3. **Improve diff tooling.**
|
||||
- Extend `openspec diff` to compare change deltas against the live spec and highlight pending merges.
|
||||
4. **Optional:** Offer a `--rewrite-scenarios` helper that merges bullet lists of scenarios to reduce manual editing noise.
|
||||
|
||||
_Outcome:_ Contributors can safely reconcile their work with the latest spec before archiving, restoring true parallel development.
|
||||
|
||||
### Phase 2 – Increase Delta Granularity
|
||||
1. **Extend the delta language with scenario-level directives.**
|
||||
- Allow `## MODIFIED Requirements` + `## ADDED Scenarios` / `## MODIFIED Scenarios` sections nested under the requirement header.
|
||||
- Backed by stable scenario identifiers (explicit IDs or generated hashes) stored in `meta.json`. This lets the system reason about individual scenarios.
|
||||
2. **Teach the parser to understand nested operations.**
|
||||
- Update `parseDeltaSpec` to emit scenario-level operations in addition to requirement blocks.
|
||||
- Update `buildUpdatedSpec` (or its replacement) to merge scenario lists, preserving order while inserting new entries in a deterministic fashion.
|
||||
3. **Automate migration.**
|
||||
- Provide a one-time command that inspects each existing spec, injects scenario IDs, and rewrites in-flight change deltas into the richer format.
|
||||
4. **Continue to rely on the Phase 1 rebase flow for conflicts when two changes edit the same scenario body or description.**
|
||||
|
||||
_Outcome:_ Most concurrent updates become commutative, drastically reducing the odds of human merges.
|
||||
|
||||
### Phase 3 – Structured Spec Graph (Long-Term)
|
||||
1. **Define stable requirement IDs.**
|
||||
- Embed `Requirement ID: <uuid>` markers in specs so renames and moves are trackable.
|
||||
- This enables future features like cross-capability references and better diff visualizations.
|
||||
2. **Model spec edits as operations over an AST.**
|
||||
- Build an intermediate representation (IR) for requirements/scenarios/metadata.
|
||||
- Use operational transforms or CRDT-like techniques to guarantee merge associativity.
|
||||
3. **Integrate with Git directly.**
|
||||
- Offer optional `openspec branch` scaffolding that aligns spec changes with Git branches, letting teams leverage Git’s conflict editor for the markdown IR.
|
||||
|
||||
_Outcome:_ OpenSpec graduates from replace-based updates to a resilient, intent-preserving spec management platform.
|
||||
|
||||
## Migration & Product Impacts
|
||||
- **Backfill metadata:** add hashes for all active changes and the current main specs during the initial rollout.
|
||||
- **CLI UX:** new commands (`change sync`, enhanced `archive`) require documentation, help text, and release notes.
|
||||
- **Docs & AGENTS updates:** reinforce the rebase workflow and explain conflict resolution to AI assistants.
|
||||
- **Testing:** introduce fixtures covering divergent requirement fingerprints and merge resolution logic.
|
||||
- **Telemetry (optional):** log fingerprint mismatches so we can see how often teams hit conflicts after the rollout.
|
||||
|
||||
## Open Questions / Risks
|
||||
- How should we order scenarios when multiple changes insert at different points? (Consider optional `position` metadata or deterministic alphabetical fallbacks.)
|
||||
- What is the graceful failure mode if contributors delete the `meta.json` file? (CLI should recreate fingerprints on demand.)
|
||||
- Do we need to support offline authors who cannot easily re-run the sync command before archiving? (Potential `--accept-outdated` escape hatch for emergencies.)
|
||||
- How will archived historical changes be handled? We may need a migration script to embed fingerprints retroactively so re-validation succeeds.
|
||||
|
||||
## Immediate Next Steps
|
||||
1. Prototype fingerprint capture during `openspec change validate` and block archive on mismatches.
|
||||
2. Ship `openspec change sync` with line-based diff3 merging and conflict markers.
|
||||
3. Update contributor docs and AI instructions to mandate running `sync` before archiving.
|
||||
4. Plan the scenario-level delta extension and migration path as a follow-up RFC.
|
||||
@@ -1,8 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Updates
|
||||
The update command SHALL refresh existing slash command files for configured tools without creating new ones.
|
||||
#### Scenario: Updating slash commands for Kilo Code
|
||||
- **WHEN** `.kilocode/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
@@ -1,8 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Updates
|
||||
The update command SHALL refresh existing slash command files for configured tools without creating new ones.
|
||||
#### Scenario: Updating slash commands for Windsurf
|
||||
- **WHEN** `.windsurf/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
@@ -1,17 +0,0 @@
|
||||
## 1. CLI wiring
|
||||
- [ ] 1.1 Add Windsurf to the selectable AI tools in `openspec init`, including "already configured" detection.
|
||||
- [ ] 1.2 Register a `WindsurfSlashCommandConfigurator` that writes workflows to `.windsurf/workflows/` and ensures the directory exists.
|
||||
- [ ] 1.3 Ensure `openspec update` pulls the Windsurf configurator when winds is selected and skips creation when files are absent.
|
||||
|
||||
## 2. Workflow templates
|
||||
- [ ] 2.1 Reuse the shared proposal/apply/archive bodies, adding Windsurf-specific headings/description before the OpenSpec markers.
|
||||
- [ ] 2.2 Confirm generated Markdown (per file) stays comfortably under the 12k character ceiling noted in the Windsurf docs.
|
||||
|
||||
## 3. Tests & safeguards
|
||||
- [ ] 3.1 Extend init tests to assert creation of `.windsurf/workflows/openspec-*.md` when Windsurf is chosen.
|
||||
- [ ] 3.2 Extend update tests to assert existing Windsurf workflows are refreshed and non-existent files are ignored.
|
||||
- [ ] 3.3 Add regression coverage for marker preservation inside Windsurf workflow files.
|
||||
|
||||
## 4. Documentation
|
||||
- [ ] 4.1 Update README (and any user-facing docs) to list Windsurf under native slash/workflow integrations.
|
||||
- [ ] 4.2 Call out Windsurf workflow support in release notes or CHANGELOG if applicable.
|
||||
+18
@@ -21,6 +21,24 @@ 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 Windsurf
|
||||
- **WHEN** the user selects Windsurf during initialization
|
||||
- **THEN** create `.windsurf/workflows/openspec-proposal.md`, `.windsurf/workflows/openspec-apply.md`, and `.windsurf/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates (wrapped in OpenSpec markers) so workflow text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Kilo Code
|
||||
- **WHEN** the user selects Kilo Code during initialization
|
||||
- **THEN** create `.kilocode/workflows/openspec-proposal.md`, `.kilocode/workflows/openspec-apply.md`, and `.kilocode/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates (wrapped in OpenSpec markers) so workflow text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Codex
|
||||
- **WHEN** the user selects Codex during initialization
|
||||
- **THEN** create global prompt files at `~/.codex/prompts/openspec-proposal.md`, `~/.codex/prompts/openspec-apply.md`, and `~/.codex/prompts/openspec-archive.md` (or under `$CODEX_HOME/prompts` if set)
|
||||
- **AND** populate each file from shared templates that map the first numbered placeholder (`$1`) to the primary user input (e.g., change identifier or question text)
|
||||
- **AND** wrap the generated content in OpenSpec markers so `openspec update` can refresh the prompts without touching surrounding custom notes
|
||||
|
||||
#### Scenario: Generating slash commands for GitHub Copilot
|
||||
- **WHEN** the user selects GitHub Copilot during initialization
|
||||
- **THEN** create `.github/prompts/openspec-proposal.prompt.md`, `.github/prompts/openspec-apply.prompt.md`, and `.github/prompts/openspec-archive.prompt.md`
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Slash Command Updates
|
||||
The update command SHALL refresh existing slash command files for configured tools without creating new ones.
|
||||
|
||||
#### Scenario: Updating slash commands for Claude Code
|
||||
- **WHEN** `.claude/commands/openspec/` contains `proposal.md`, `apply.md`, and `archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Cursor
|
||||
- **WHEN** `.cursor/commands/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for OpenCode
|
||||
- **WHEN** `.opencode/command/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Windsurf
|
||||
- **WHEN** `.windsurf/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
|
||||
#### Scenario: Updating slash commands for Kilo Code
|
||||
- **WHEN** `.kilocode/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
|
||||
#### Scenario: Updating slash commands for Codex
|
||||
- **GIVEN** the global Codex prompt directory contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **WHEN** a user runs `openspec update`
|
||||
- **THEN** refresh each file using the shared slash-command templates (including placeholder guidance)
|
||||
- **AND** preserve any unmanaged content outside the OpenSpec marker block
|
||||
- **AND** skip creation when a Codex prompt file is missing
|
||||
|
||||
#### Scenario: Updating slash commands for GitHub Copilot
|
||||
- **WHEN** `.github/prompts/` contains `openspec-proposal.prompt.md`, `openspec-apply.prompt.md`, and `openspec-archive.prompt.md`
|
||||
- **THEN** refresh each file using shared templates while preserving the YAML frontmatter
|
||||
- **AND** update only the OpenSpec-managed block between markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Missing slash command file
|
||||
- **WHEN** a tool lacks a slash command file
|
||||
- **THEN** do not create a new file during update
|
||||
+19
@@ -17,6 +17,25 @@ The command SHALL configure AI coding assistants with OpenSpec instructions usin
|
||||
|
||||
### Requirement: Slash Command Configuration
|
||||
The init command SHALL generate slash command files for supported editors using shared templates.
|
||||
|
||||
#### Scenario: Generating slash commands for Claude Code
|
||||
- **WHEN** the user selects Claude Code during initialization
|
||||
- **THEN** create `.claude/commands/openspec/proposal.md`, `.claude/commands/openspec/apply.md`, and `.claude/commands/openspec/archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Cursor
|
||||
- **WHEN** the user selects Cursor during initialization
|
||||
- **THEN** create `.cursor/commands/openspec-proposal.md`, `.cursor/commands/openspec-apply.md`, and `.cursor/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for OpenCode
|
||||
- **WHEN** the user selects OpenCode during initialization
|
||||
- **THEN** create `.opencode/commands/openspec-proposal.md`, `.opencode/commands/openspec-apply.md`, and `.opencode/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Kilo Code
|
||||
- **WHEN** the user selects Kilo Code during initialization
|
||||
- **THEN** create `.kilocode/workflows/openspec-proposal.md`, `.kilocode/workflows/openspec-apply.md`, and `.kilocode/workflows/openspec-archive.md`
|
||||
+3
-5
@@ -1,5 +1,4 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Slash Command Updates
|
||||
The update command SHALL refresh existing slash command files for configured tools without creating new ones.
|
||||
|
||||
@@ -18,10 +17,9 @@ The update command SHALL refresh existing slash command files for configured too
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for GitHub Copilot
|
||||
- **WHEN** `.github/prompts/` contains `openspec-proposal.prompt.md`, `openspec-apply.prompt.md`, and `openspec-archive.prompt.md`
|
||||
- **THEN** refresh each file using shared templates while preserving the YAML frontmatter
|
||||
- **AND** update only the OpenSpec-managed block between markers
|
||||
#### Scenario: Updating slash commands for Kilo Code
|
||||
- **WHEN** `.kilocode/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Missing slash command file
|
||||
@@ -0,0 +1,12 @@
|
||||
## Why
|
||||
The current `openspec init` command requires interactive prompts, preventing automation in CI/CD pipelines and scripted setups. Adding non-interactive options will enable programmatic initialization for automated workflows while maintaining the existing interactive experience as the default.
|
||||
|
||||
## What Changes
|
||||
- Replace the multiple flag design with a single `--tools` option that accepts `all`, `none`, or a comma-separated list of tool IDs
|
||||
- Update InitCommand to bypass interactive prompts when `--tools` is supplied and apply single-flag validation rules
|
||||
- Document the non-interactive behavior via the CLI init spec delta (scenarios for `all`, `none`, list parsing, and invalid entries)
|
||||
- Generate CLI help text dynamically from `AI_TOOLS` so supported tools stay in sync
|
||||
|
||||
## Impact
|
||||
- Affected specs: `specs/cli-init/spec.md`
|
||||
- Affected code: `src/cli/index.ts`, `src/core/init.ts`
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Delta for CLI Init Specification
|
||||
|
||||
## ADDED Requirements
|
||||
### Requirement: Non-Interactive Mode
|
||||
The command SHALL support non-interactive operation through command-line options for automation and CI/CD use cases.
|
||||
|
||||
#### Scenario: Select all tools non-interactively
|
||||
- **WHEN** run with `--tools all`
|
||||
- **THEN** automatically select every available AI tool without prompting
|
||||
- **AND** proceed with initialization using the selected tools
|
||||
|
||||
#### Scenario: Select specific tools non-interactively
|
||||
- **WHEN** run with `--tools claude,cursor`
|
||||
- **THEN** parse the comma-separated tool IDs and validate against available tools
|
||||
- **AND** proceed with initialization using only the specified valid tools
|
||||
|
||||
#### Scenario: Skip tool configuration non-interactively
|
||||
- **WHEN** run with `--tools none`
|
||||
- **THEN** skip AI tool configuration entirely
|
||||
- **AND** only create the OpenSpec directory structure and template files
|
||||
|
||||
#### Scenario: Invalid tool specification
|
||||
- **WHEN** run with `--tools` containing any IDs not present in the AI tool registry
|
||||
- **THEN** exit with code 1 and display available values (`all`, `none`, or the supported tool IDs)
|
||||
|
||||
#### Scenario: Help text lists available tool IDs
|
||||
- **WHEN** displaying CLI help for `openspec init`
|
||||
- **THEN** show the `--tools` option description with the valid values derived from the AI tool registry
|
||||
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Interactive Mode
|
||||
The command SHALL provide an interactive menu for AI tool selection with clear navigation instructions.
|
||||
|
||||
#### Scenario: Displaying interactive menu
|
||||
- **WHEN** run in fresh or extend mode without non-interactive options
|
||||
- **THEN** present a looping select menu that lets users toggle tools with Enter and finish via a "Done" option
|
||||
- **AND** label already configured tools with "(already configured)" while keeping disabled options marked "coming soon"
|
||||
- **AND** change the prompt copy in extend mode to "Which AI tools would you like to add or refresh?"
|
||||
- **AND** display inline instructions clarifying that Enter toggles a tool and selecting "Done" confirms the list
|
||||
@@ -0,0 +1,17 @@
|
||||
## 1. CLI Option Registration
|
||||
- [x] 1.1 Replace the multiple flag design with a single `--tools <value>` option supporting `all|none|a,b,c` and keep strict argument validation.
|
||||
- [x] 1.2 Populate the `--tools` help text dynamically from the `AI_TOOLS` registry.
|
||||
|
||||
## 2. InitCommand Modifications
|
||||
- [x] 2.1 Accept the single tools option in the InitCommand constructor and plumb it through existing flows.
|
||||
- [x] 2.2 Update tool selection logic to shortcut prompts for `all`, `none`, and explicit lists.
|
||||
- [x] 2.3 Fail fast with exit code 1 and a helpful message when the parsed list contains unsupported tool IDs.
|
||||
|
||||
## 3. Specification Updates
|
||||
- [x] 3.1 Capture the non-interactive scenarios (`all`, `none`, list, invalid) in the change delta without modifying `specs/cli-init/spec.md` directly.
|
||||
- [x] 3.2 Document that CLI help reflects the available tool IDs managed by `AI_TOOLS`.
|
||||
|
||||
## 4. Testing
|
||||
- [x] 4.1 Add unit coverage for parsing `--tools` values, including invalid entries.
|
||||
- [x] 4.2 Add integration coverage ensuring non-interactive runs generate the expected files and exit codes.
|
||||
- [x] 4.3 Verify the interactive flow remains unchanged when `--tools` is omitted.
|
||||
+19
@@ -16,6 +16,25 @@ The command SHALL configure AI coding assistants with OpenSpec instructions usin
|
||||
|
||||
### Requirement: Slash Command Configuration
|
||||
The init command SHALL generate slash command files for supported editors using shared templates.
|
||||
|
||||
#### Scenario: Generating slash commands for Claude Code
|
||||
- **WHEN** the user selects Claude Code during initialization
|
||||
- **THEN** create `.claude/commands/openspec/proposal.md`, `.claude/commands/openspec/apply.md`, and `.claude/commands/openspec/archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Cursor
|
||||
- **WHEN** the user selects Cursor during initialization
|
||||
- **THEN** create `.cursor/commands/openspec-proposal.md`, `.cursor/commands/openspec-apply.md`, and `.cursor/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for OpenCode
|
||||
- **WHEN** the user selects OpenCode during initialization
|
||||
- **THEN** create `.opencode/commands/openspec-proposal.md`, `.opencode/commands/openspec-apply.md`, and `.opencode/commands/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates so command text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Windsurf
|
||||
- **WHEN** the user selects Windsurf during initialization
|
||||
- **THEN** create `.windsurf/workflows/openspec-proposal.md`, `.windsurf/workflows/openspec-apply.md`, and `.windsurf/workflows/openspec-archive.md`
|
||||
@@ -0,0 +1,27 @@
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Slash Command Updates
|
||||
The update command SHALL refresh existing slash command files for configured tools without creating new ones.
|
||||
|
||||
#### Scenario: Updating slash commands for Claude Code
|
||||
- **WHEN** `.claude/commands/openspec/` contains `proposal.md`, `apply.md`, and `archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Cursor
|
||||
- **WHEN** `.cursor/commands/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for OpenCode
|
||||
- **WHEN** `.opencode/command/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Windsurf
|
||||
- **WHEN** `.windsurf/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Missing slash command file
|
||||
- **WHEN** a tool lacks a slash command file
|
||||
- **THEN** do not create a new file during update
|
||||
@@ -0,0 +1,17 @@
|
||||
## 1. CLI wiring
|
||||
- [x] 1.1 Add Windsurf to the selectable AI tools in `openspec init`, including "already configured" detection.
|
||||
- [x] 1.2 Register a `WindsurfSlashCommandConfigurator` that writes workflows to `.windsurf/workflows/` and ensures the directory exists.
|
||||
- [x] 1.3 Ensure `openspec update` pulls the Windsurf configurator when winds is selected and skips creation when files are absent.
|
||||
|
||||
## 2. Workflow templates
|
||||
- [x] 2.1 Reuse the shared proposal/apply/archive bodies, adding Windsurf-specific headings/description before the OpenSpec markers.
|
||||
- [x] 2.2 Confirm generated Markdown (per file) stays comfortably under the 12k character ceiling noted in the Windsurf docs.
|
||||
|
||||
## 3. Tests & safeguards
|
||||
- [x] 3.1 Extend init tests to assert creation of `.windsurf/workflows/openspec-*.md` when Windsurf is chosen.
|
||||
- [x] 3.2 Extend update tests to assert existing Windsurf workflows are refreshed and non-existent files are ignored.
|
||||
- [x] 3.3 Add regression coverage for marker preservation inside Windsurf workflow files.
|
||||
|
||||
## 4. Documentation
|
||||
- [x] 4.1 Update README (and any user-facing docs) to list Windsurf under native slash/workflow integrations.
|
||||
- [x] 4.2 Call out Windsurf workflow support in release notes or CHANGELOG if applicable.
|
||||
@@ -0,0 +1,12 @@
|
||||
## 1. Messaging enhancements
|
||||
- [x] 1.1 Inventory current validation failures and map each to the desired message improvements.
|
||||
- [x] 1.2 Implement structured error builders that include file paths, normalized header names, and example fixes.
|
||||
- [x] 1.3 Ensure `openspec validate --help` and troubleshooting docs mention the richer messages and debug tips.
|
||||
|
||||
## 2. Tests
|
||||
- [x] 2.1 Add unit tests for representative errors (no deltas, missing requirement body, missing scenarios) asserting the new wording.
|
||||
- [x] 2.2 Add integration coverage verifying the Next steps footer reflects contextual guidance.
|
||||
|
||||
## 3. Documentation
|
||||
- [x] 3.1 Update troubleshooting sections and CLI docs with sample output from the enhanced errors.
|
||||
- [x] 3.2 Note the change in CHANGELOG or release notes if applicable.
|
||||
@@ -0,0 +1,11 @@
|
||||
## 1. Instruction redesign
|
||||
- [x] 1.1 Draft a quick-reference section that surfaces file templates and formatting rules at the top of `openspec/AGENTS.md`.
|
||||
- [x] 1.2 Reorganize the workflow narrative with inline examples and progressive disclosure for advanced topics.
|
||||
|
||||
## 2. Templates and checklists
|
||||
- [x] 2.1 Add copy/paste templates for proposal, tasks, design, and spec delta files.
|
||||
- [x] 2.2 Insert a pre-validation checklist capturing common lint failures before running `openspec validate`.
|
||||
|
||||
## 3. Documentation updates
|
||||
- [x] 3.1 Update supporting docs or README pointers so contributors find the redesigned instructions.
|
||||
- [x] 3.2 Confirm examples and references stay in sync with the new scaffold command guidance.
|
||||
@@ -0,0 +1,14 @@
|
||||
## Why
|
||||
- Users frequently scroll to a tool and press Enter without toggling it, resulting in no configuration changes.
|
||||
- The current workflow deviates from common CLI expectations where Enter confirms the highlighted item.
|
||||
- Aligning behavior with user expectations reduces friction during onboarding.
|
||||
|
||||
## What Changes
|
||||
- Update the init wizard so pressing Enter on a highlighted tool selects it before moving to the review step.
|
||||
- Adjust interactive instructions to clarify Enter selects the current tool and Space still toggles selections.
|
||||
- Refresh specs to capture the clarified behavior for the interactive menu.
|
||||
|
||||
## Impact
|
||||
- Users who press Enter without toggling now configure the highlighted tool instead of exiting with no selections.
|
||||
- Spacebar multi-select support remains unchanged for power users.
|
||||
- Documentation better reflects how the wizard behaves.
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
## MODIFIED Requirements
|
||||
### Requirement: Interactive Mode
|
||||
The command SHALL provide an interactive menu for AI tool selection with clear navigation instructions.
|
||||
#### Scenario: Displaying interactive menu
|
||||
- **WHEN** run in fresh or extend mode
|
||||
- **THEN** present a looping select menu that lets users toggle tools with Space and review selections with Enter
|
||||
- **AND** when Enter is pressed on a highlighted selectable tool that is not already selected, automatically add it to the selection before moving to review so the highlighted tool is configured
|
||||
- **AND** label already configured tools with "(already configured)" while keeping disabled options marked "coming soon"
|
||||
- **AND** change the prompt copy in extend mode to "Which AI tools would you like to add or refresh?"
|
||||
- **AND** display inline instructions clarifying that Space toggles tools and Enter selects the highlighted tool before reviewing selections
|
||||
@@ -0,0 +1,8 @@
|
||||
## 1. Implementation
|
||||
- [x] Update the tool selection wizard to auto-select the highlighted tool when Enter is pressed without prior toggles.
|
||||
- [x] Refresh inline instructions copy so Enter behavior is clear.
|
||||
- [x] Adjust or add tests if needed to cover the new selection flow.
|
||||
|
||||
## 2. Validation
|
||||
- [x] Run `pnpm run build`.
|
||||
- [x] Run `pnpm test` (or targeted suite) if applicable.
|
||||
@@ -0,0 +1,11 @@
|
||||
## 1. Implementation
|
||||
- [x] 1.1 Refactor `openspec init` to always generate the root `AGENTS.md` stub (initial run and extend mode) via shared helper logic.
|
||||
- [x] 1.2 Rework the AI tool selection wizard to surface "Natively supported" vs "Other tools" groupings and make the stub non-optional.
|
||||
- [x] 1.3 Update CLI messaging, templates, and configurators so the new flow stays in sync across init and update commands.
|
||||
- [x] 1.4 Refresh unit/integration tests to cover the unconditional stub and the regrouped prompt layout.
|
||||
- [x] 1.5 Update documentation, README snippets, and CHANGELOG entries that mention the opt-in `AGENTS.md` experience.
|
||||
|
||||
## 2. Validation
|
||||
- [x] 2.1 Run `pnpm test` targeting CLI init/update suites.
|
||||
- [x] 2.2 Execute `openspec validate update-cli-init-root-agents --strict`.
|
||||
- [x] 2.3 Perform a manual smoke test: run `openspec init` in a temp directory, confirm stub + grouped prompts, rerun in extend mode.
|
||||
+7
-7
@@ -1,12 +1,12 @@
|
||||
## 1. Release workflow automation
|
||||
- [ ] 1.1 Add a `.github/workflows/release.yml` that runs on pushes to `main`, sets up pnpm + Node 20, installs dependencies, and invokes `changesets/action@v1` with `publish: pnpm run release`.
|
||||
- [ ] 1.2 Configure the action with `createGithubReleases: true` and document required secrets (`NPM_TOKEN`, default `GITHUB_TOKEN`) plus recommended concurrency safeguards.
|
||||
- [ ] 1.3 Validate the workflow using `act` or a dry-run push to confirm the action opens release PRs when changesets exist and publishes when the release PR merge lands.
|
||||
- [x] 1.1 Add a `.github/workflows/release.yml` that runs on pushes to `main`, sets up pnpm + Node 20, installs dependencies, and invokes `changesets/action@v1` with `publish: pnpm run release`.
|
||||
- [x] 1.2 Configure the action with `createGithubReleases: true` and document required secrets (`NPM_TOKEN`, default `GITHUB_TOKEN`) plus recommended concurrency safeguards.
|
||||
- [x] 1.3 Validate the workflow using `act` or a dry-run push to confirm the action opens release PRs when changesets exist and publishes when the release PR merge lands.
|
||||
|
||||
## 2. Package release script
|
||||
- [ ] 2.1 Add a `release` script to `package.json` that builds the project and runs `changeset publish` using pnpm.
|
||||
- [ ] 2.2 Ensure the script respects the existing `prepare`/`prepublishOnly` hooks to avoid duplicate builds and update documentation or scripts if adjustments are needed.
|
||||
- [x] 2.1 Add a `release` script to `package.json` that builds the project and runs `changeset publish` using pnpm.
|
||||
- [x] 2.2 Ensure the script respects the existing `prepare`/`prepublishOnly` hooks to avoid duplicate builds and update documentation or scripts if adjustments are needed.
|
||||
|
||||
## 3. Documentation and recovery steps
|
||||
- [ ] 3.1 Update maintainer docs (e.g., README or `/docs`) with the end-to-end automated release flow, explicitly removing the manual tag/release steps that are no longer required and explaining how changesets drive the release PR.
|
||||
- [ ] 3.2 Document fallback steps for failed publishes (rerun workflow, manual publish) and the hotfix path when a release must be cut without pending changesets.
|
||||
- [x] 3.1 Update maintainer docs (e.g., README or `/docs`) with the end-to-end automated release flow, explicitly removing the manual tag/release steps that are no longer required and explaining how changesets drive the release PR.
|
||||
- [x] 3.2 Document fallback steps for failed publishes (rerun workflow, manual publish) and the hotfix path when a release must be cut without pending changesets.
|
||||
@@ -1,12 +0,0 @@
|
||||
## 1. Messaging enhancements
|
||||
- [ ] 1.1 Inventory current validation failures and map each to the desired message improvements.
|
||||
- [ ] 1.2 Implement structured error builders that include file paths, normalized header names, and example fixes.
|
||||
- [ ] 1.3 Ensure `openspec validate --help` and troubleshooting docs mention the richer messages and debug tips.
|
||||
|
||||
## 2. Tests
|
||||
- [ ] 2.1 Add unit tests for representative errors (no deltas, missing requirement body, missing scenarios) asserting the new wording.
|
||||
- [ ] 2.2 Add integration coverage verifying the Next steps footer reflects contextual guidance.
|
||||
|
||||
## 3. Documentation
|
||||
- [ ] 3.1 Update troubleshooting sections and CLI docs with sample output from the enhanced errors.
|
||||
- [ ] 3.2 Note the change in CHANGELOG or release notes if applicable.
|
||||
@@ -1,11 +0,0 @@
|
||||
## 1. Instruction redesign
|
||||
- [ ] 1.1 Draft a quick-reference section that surfaces file templates and formatting rules at the top of `openspec/AGENTS.md`.
|
||||
- [ ] 1.2 Reorganize the workflow narrative with inline examples and progressive disclosure for advanced topics.
|
||||
|
||||
## 2. Templates and checklists
|
||||
- [ ] 2.1 Add copy/paste templates for proposal, tasks, design, and spec delta files.
|
||||
- [ ] 2.2 Insert a pre-validation checklist capturing common lint failures before running `openspec validate`.
|
||||
|
||||
## 3. Documentation updates
|
||||
- [ ] 3.1 Update supporting docs or README pointers so contributors find the redesigned instructions.
|
||||
- [ ] 3.2 Confirm examples and references stay in sync with the new scaffold command guidance.
|
||||
@@ -1,11 +0,0 @@
|
||||
## 1. Implementation
|
||||
- [ ] 1.1 Refactor `openspec init` to always generate the root `AGENTS.md` stub (initial run and extend mode) via shared helper logic.
|
||||
- [ ] 1.2 Rework the AI tool selection wizard to surface "Natively supported" vs "Other tools" groupings and make the stub non-optional.
|
||||
- [ ] 1.3 Update CLI messaging, templates, and configurators so the new flow stays in sync across init and update commands.
|
||||
- [ ] 1.4 Refresh unit/integration tests to cover the unconditional stub and the regrouped prompt layout.
|
||||
- [ ] 1.5 Update documentation, README snippets, and CHANGELOG entries that mention the opt-in `AGENTS.md` experience.
|
||||
|
||||
## 2. Validation
|
||||
- [ ] 2.1 Run `pnpm test` targeting CLI init/update suites.
|
||||
- [ ] 2.2 Execute `openspec validate update-cli-init-root-agents --strict`.
|
||||
- [ ] 2.3 Perform a manual smoke test: run `openspec init` in a temp directory, confirm stub + grouped prompts, rerun in extend mode.
|
||||
@@ -42,20 +42,17 @@ The command SHALL generate required template files with appropriate content for
|
||||
- **AND** generate `project.md` with project context template
|
||||
|
||||
### Requirement: AI Tool Configuration
|
||||
|
||||
The command SHALL configure AI coding assistants with OpenSpec instructions based on user selection.
|
||||
The command SHALL configure AI coding assistants with OpenSpec instructions using a grouped selection experience so teams can enable native integrations while always provisioning guidance for other assistants.
|
||||
|
||||
#### Scenario: Prompting for AI tool selection
|
||||
|
||||
- **WHEN** run interactively
|
||||
- **THEN** prompt the user with "Which AI tools do you use?" using a multi-select menu
|
||||
- **AND** list every available tool with a checkbox:
|
||||
- Claude Code (creates or refreshes CLAUDE.md and slash commands)
|
||||
- Cursor (creates or refreshes `.cursor/commands/*` slash commands)
|
||||
- AGENTS.md standard (creates or refreshes AGENTS.md stub with OpenSpec markers)
|
||||
- **AND** show "(already configured)" beside tools whose managed files exist so users understand selections will refresh content
|
||||
- **AND** treat disabled tools as "coming soon" and keep them unselectable
|
||||
- **AND** allow confirming with Enter after selecting one or more tools
|
||||
- **THEN** present a multi-select wizard that separates options into two headings:
|
||||
- **Natively supported providers** shows each available first-party integration (Claude Code, Cursor, OpenCode, …) with checkboxes
|
||||
- **Other tools** explains that the root-level `AGENTS.md` stub is always generated for AGENTS-compatible assistants and cannot be deselected
|
||||
- **AND** mark already configured native tools with "(already configured)" to signal that choosing them will refresh managed content
|
||||
- **AND** keep disabled or unavailable providers labelled as "coming soon" so users know they cannot opt in yet
|
||||
- **AND** allow confirming the selection even when no native provider is chosen because the root stub remains enabled by default
|
||||
- **AND** change the base prompt copy in extend mode to "Which natively supported AI tools would you like to add or refresh?"
|
||||
|
||||
### Requirement: AI Tool Configuration Details
|
||||
|
||||
@@ -84,13 +81,13 @@ This project uses OpenSpec to manage AI assistant workflows.
|
||||
|
||||
### Requirement: Interactive Mode
|
||||
The command SHALL provide an interactive menu for AI tool selection with clear navigation instructions.
|
||||
|
||||
#### Scenario: Displaying interactive menu
|
||||
- **WHEN** run in fresh or extend mode
|
||||
- **THEN** present a looping select menu that lets users toggle tools with Enter and finish via a "Done" option
|
||||
- **THEN** present a looping select menu that lets users toggle tools with Space and review selections with Enter
|
||||
- **AND** when Enter is pressed on a highlighted selectable tool that is not already selected, automatically add it to the selection before moving to review so the highlighted tool is configured
|
||||
- **AND** label already configured tools with "(already configured)" while keeping disabled options marked "coming soon"
|
||||
- **AND** change the prompt copy in extend mode to "Which AI tools would you like to add or refresh?"
|
||||
- **AND** display inline instructions clarifying that Enter toggles a tool and selecting "Done" confirms the list
|
||||
- **AND** display inline instructions clarifying that Space toggles tools and Enter selects the highlighted tool before reviewing selections
|
||||
|
||||
### Requirement: Safety Checks
|
||||
The command SHALL perform safety checks to prevent overwriting existing structures and ensure proper permissions.
|
||||
@@ -141,11 +138,12 @@ The command SHALL use consistent exit codes to indicate different failure modes.
|
||||
- **AND** personalize the "Next steps" header using the names of the selected tools, defaulting to a generic label when none remain
|
||||
|
||||
### Requirement: Exit Code Adjustments
|
||||
`openspec init` SHALL treat extend mode with no selected tools as a guarded error.
|
||||
`openspec init` SHALL treat extend mode without new native tool selections as a successful refresh.
|
||||
|
||||
#### Scenario: Preventing empty extend runs
|
||||
- **WHEN** OpenSpec is already initialized and the user selects no additional tools
|
||||
- **THEN** exit with code 1 after showing the existing-initialization guidance message
|
||||
#### Scenario: Allowing empty extend runs
|
||||
- **WHEN** OpenSpec is already initialized and the user selects no additional natively supported tools
|
||||
- **THEN** complete successfully while refreshing the root `AGENTS.md` stub
|
||||
- **AND** exit with code 0
|
||||
|
||||
### Requirement: Slash Command Configuration
|
||||
The init command SHALL generate slash command files for supported editors using shared templates.
|
||||
@@ -168,6 +166,68 @@ 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 Windsurf
|
||||
- **WHEN** the user selects Windsurf during initialization
|
||||
- **THEN** create `.windsurf/workflows/openspec-proposal.md`, `.windsurf/workflows/openspec-apply.md`, and `.windsurf/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates (wrapped in OpenSpec markers) so workflow text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Kilo Code
|
||||
- **WHEN** the user selects Kilo Code during initialization
|
||||
- **THEN** create `.kilocode/workflows/openspec-proposal.md`, `.kilocode/workflows/openspec-apply.md`, and `.kilocode/workflows/openspec-archive.md`
|
||||
- **AND** populate each file from shared templates (wrapped in OpenSpec markers) so workflow text matches other tools
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
#### Scenario: Generating slash commands for Codex
|
||||
- **WHEN** the user selects Codex during initialization
|
||||
- **THEN** create global prompt files at `~/.codex/prompts/openspec-proposal.md`, `~/.codex/prompts/openspec-apply.md`, and `~/.codex/prompts/openspec-archive.md` (or under `$CODEX_HOME/prompts` if set)
|
||||
- **AND** populate each file from shared templates that map the first numbered placeholder (`$1`) to the primary user input (e.g., change identifier or question text)
|
||||
- **AND** wrap the generated content in OpenSpec markers so `openspec update` can refresh the prompts without touching surrounding custom notes
|
||||
|
||||
#### Scenario: Generating slash commands for GitHub Copilot
|
||||
- **WHEN** the user selects GitHub Copilot during initialization
|
||||
- **THEN** create `.github/prompts/openspec-proposal.prompt.md`, `.github/prompts/openspec-apply.prompt.md`, and `.github/prompts/openspec-archive.prompt.md`
|
||||
- **AND** populate each file with YAML frontmatter containing a `description` field that summarizes the workflow stage
|
||||
- **AND** include `$ARGUMENTS` placeholder to capture user input
|
||||
- **AND** wrap the shared template body with OpenSpec markers so `openspec update` can refresh the content
|
||||
- **AND** each template includes instructions for the relevant OpenSpec workflow stage
|
||||
|
||||
### Requirement: Non-Interactive Mode
|
||||
The command SHALL support non-interactive operation through command-line options for automation and CI/CD use cases.
|
||||
|
||||
#### Scenario: Select all tools non-interactively
|
||||
- **WHEN** run with `--tools all`
|
||||
- **THEN** automatically select every available AI tool without prompting
|
||||
- **AND** proceed with initialization using the selected tools
|
||||
|
||||
#### Scenario: Select specific tools non-interactively
|
||||
- **WHEN** run with `--tools claude,cursor`
|
||||
- **THEN** parse the comma-separated tool IDs and validate against available tools
|
||||
- **AND** proceed with initialization using only the specified valid tools
|
||||
|
||||
#### Scenario: Skip tool configuration non-interactively
|
||||
- **WHEN** run with `--tools none`
|
||||
- **THEN** skip AI tool configuration entirely
|
||||
- **AND** only create the OpenSpec directory structure and template files
|
||||
|
||||
#### Scenario: Invalid tool specification
|
||||
- **WHEN** run with `--tools` containing any IDs not present in the AI tool registry
|
||||
- **THEN** exit with code 1 and display available values (`all`, `none`, or the supported tool IDs)
|
||||
|
||||
#### Scenario: Help text lists available tool IDs
|
||||
- **WHEN** displaying CLI help for `openspec init`
|
||||
- **THEN** show the `--tools` option description with the valid values derived from the AI tool registry
|
||||
|
||||
### Requirement: Root instruction stub
|
||||
`openspec init` SHALL always scaffold the root-level `AGENTS.md` hand-off so every teammate finds the primary OpenSpec instructions.
|
||||
|
||||
#### Scenario: Creating root `AGENTS.md`
|
||||
- **GIVEN** the project may or may not already contain an `AGENTS.md` file
|
||||
- **WHEN** initialization completes in fresh or extend mode
|
||||
- **THEN** create or refresh `AGENTS.md` at the repository root using the managed marker block from `TemplateManager.getAgentsStandardTemplate()`
|
||||
- **AND** preserve any existing content outside the managed markers while replacing the stub text inside them
|
||||
- **AND** create the stub regardless of which native AI tools are selected
|
||||
|
||||
## Why
|
||||
|
||||
Manual creation of OpenSpec structure is error-prone and creates adoption friction. A standardized init command ensures:
|
||||
|
||||
@@ -32,18 +32,14 @@ The update command SHALL handle file updates in a predictable and safe manner.
|
||||
- **AND** if a root-level stub exists, update the managed block content so it keeps directing teammates to `@/openspec/AGENTS.md`
|
||||
|
||||
### Requirement: Tool-Agnostic Updates
|
||||
The update command SHALL handle file updates in a predictable and safe manner while respecting team tool choices.
|
||||
The update command SHALL refresh OpenSpec-managed files in a predictable manner while respecting each team's chosen tooling.
|
||||
|
||||
#### Scenario: Updating files
|
||||
|
||||
- **WHEN** updating files
|
||||
- **THEN** completely replace `openspec/AGENTS.md` with the latest template
|
||||
- **AND** update the root-level `AGENTS.md` using the OpenSpec markers only when that file already exists, keeping the stub content that links to `@/openspec/AGENTS.md`
|
||||
- **AND** update only the OpenSpec-managed blocks in **existing** AI tool files using markers
|
||||
- **AND** use the default directory name `openspec`
|
||||
- **AND** be idempotent (repeated runs have no additional effect)
|
||||
- **AND** respect team members' AI tool choices by not creating additional tool files beyond the root `AGENTS.md`
|
||||
- **AND** do not create new root-level stub files when none are present
|
||||
- **AND** create or refresh the root-level `AGENTS.md` stub using the managed marker block, even if the file was previously absent
|
||||
- **AND** update only the OpenSpec-managed sections inside existing AI tool files, leaving user-authored content untouched
|
||||
- **AND** avoid creating new native-tool configuration files (slash commands, CLAUDE.md, etc.) unless they already exist
|
||||
|
||||
### Requirement: Core Files Always Updated
|
||||
The update command SHALL always update the core OpenSpec files and display an ASCII-safe success message.
|
||||
@@ -71,6 +67,31 @@ The update command SHALL refresh existing slash command files for configured too
|
||||
- **THEN** refresh each file using shared templates
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Updating slash commands for Windsurf
|
||||
- **WHEN** `.windsurf/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
|
||||
#### Scenario: Updating slash commands for Kilo Code
|
||||
- **WHEN** `.kilocode/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **THEN** refresh each file using shared templates wrapped in OpenSpec markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
- **AND** skip creating missing files (the update command only refreshes what already exists)
|
||||
|
||||
#### Scenario: Updating slash commands for Codex
|
||||
- **GIVEN** the global Codex prompt directory contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
|
||||
- **WHEN** a user runs `openspec update`
|
||||
- **THEN** refresh each file using the shared slash-command templates (including placeholder guidance)
|
||||
- **AND** preserve any unmanaged content outside the OpenSpec marker block
|
||||
- **AND** skip creation when a Codex prompt file is missing
|
||||
|
||||
#### Scenario: Updating slash commands for GitHub Copilot
|
||||
- **WHEN** `.github/prompts/` contains `openspec-proposal.prompt.md`, `openspec-apply.prompt.md`, and `openspec-archive.prompt.md`
|
||||
- **THEN** refresh each file using shared templates while preserving the YAML frontmatter
|
||||
- **AND** update only the OpenSpec-managed block between markers
|
||||
- **AND** ensure templates include instructions for the relevant workflow stage
|
||||
|
||||
#### Scenario: Missing slash command file
|
||||
- **WHEN** a tool lacks a slash command file
|
||||
- **THEN** do not create a new file during update
|
||||
|
||||
@@ -9,17 +9,25 @@ Validation output SHALL include specific guidance to fix each error, including e
|
||||
#### Scenario: No deltas found in change
|
||||
- **WHEN** validating a change with zero parsed deltas
|
||||
- **THEN** show error "No deltas found" with guidance:
|
||||
- Ensure `openspec/changes/{id}/specs/` exists with `.md` files
|
||||
- Use delta headers: `## ADDED Requirements`, `## MODIFIED Requirements`, `## REMOVED Requirements`, `## RENAMED Requirements`
|
||||
- Each requirement must include at least one `#### Scenario:` block
|
||||
- Try: `openspec change show {id} --json --deltas-only` to inspect what was parsed
|
||||
- Explain that change specs must include `## ADDED Requirements`, `## MODIFIED Requirements`, `## REMOVED Requirements`, or `## RENAMED Requirements`
|
||||
- Remind authors that files must live under `openspec/changes/{id}/specs/<capability>/spec.md`
|
||||
- Include an explicit note: "Spec delta files cannot start with titles before the operation headers"
|
||||
- Suggest running `openspec change show {id} --json --deltas-only` for debugging
|
||||
|
||||
#### Scenario: Missing required sections
|
||||
- **WHEN** a required section is missing
|
||||
- **THEN** the validator SHALL include expected header names and a minimal skeleton:
|
||||
- **THEN** include expected header names and a minimal skeleton:
|
||||
- For Spec: `## Purpose`, `## Requirements`
|
||||
- For Change: `## Why`, `## What Changes`
|
||||
- Show an example snippet of the missing section
|
||||
- Provide an example snippet of the missing section with placeholder prose ready to copy
|
||||
- Mention the quick-reference section in `openspec/AGENTS.md` as the authoritative template
|
||||
|
||||
#### Scenario: Missing requirement descriptive text
|
||||
- **WHEN** a requirement header lacks descriptive text before scenarios
|
||||
- **THEN** emit an error explaining that `### Requirement:` lines must be followed by narrative text before any `#### Scenario:` headers
|
||||
- Show compliant example: "### Requirement: Foo" followed by "The system SHALL ..."
|
||||
- Suggest adding 1-2 sentences describing the normative behavior prior to listing scenarios
|
||||
- Reference the pre-validation checklist in `openspec/AGENTS.md`
|
||||
|
||||
### Requirement: Validator SHALL detect likely misformatted scenarios and warn with a fix
|
||||
The validator SHALL recognize bulleted lines that look like scenarios (e.g., lines beginning with WHEN/THEN/AND) and emit a targeted warning with a conversion example to `#### Scenario:`.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# docs-agent-instructions Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change improve-agent-instruction-usability. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Quick Reference Placement
|
||||
The AI instructions SHALL begin with a quick-reference section that surfaces required file structures, templates, and formatting rules before any narrative guidance.
|
||||
|
||||
#### Scenario: Loading templates at the top
|
||||
- **WHEN** `openspec/AGENTS.md` is regenerated or updated
|
||||
- **THEN** the first substantive section after the title SHALL provide copy-ready headings for `proposal.md`, `tasks.md`, spec deltas, and scenario formatting
|
||||
- **AND** link each template to the corresponding workflow step for deeper reading
|
||||
|
||||
### Requirement: Embedded Templates and Examples
|
||||
`openspec/AGENTS.md` SHALL include complete copy/paste templates and inline examples exactly where agents make corresponding edits.
|
||||
|
||||
#### Scenario: Providing file templates
|
||||
- **WHEN** authors reach the workflow guidance for drafting proposals and deltas
|
||||
- **THEN** provide fenced Markdown templates that match the required structure (`## Why`, `## ADDED Requirements`, `#### Scenario:` etc.)
|
||||
- **AND** accompany each template with a brief example showing correct header usage and scenario bullets
|
||||
|
||||
### Requirement: Pre-validation Checklist
|
||||
`openspec/AGENTS.md` SHALL offer a concise pre-validation checklist that highlights common formatting mistakes before running `openspec validate`.
|
||||
|
||||
#### Scenario: Highlighting common validation failures
|
||||
- **WHEN** a reader reaches the validation guidance
|
||||
- **THEN** present a checklist reminding them to verify requirement headers, scenario formatting, and delta sections
|
||||
- **AND** include reminders about at least `#### Scenario:` usage and descriptive requirement text before scenarios
|
||||
|
||||
### Requirement: Progressive Disclosure of Workflow Guidance
|
||||
The documentation SHALL separate beginner essentials from advanced topics so newcomers can focus on core steps without losing access to advanced workflows.
|
||||
|
||||
#### Scenario: Organizing beginner and advanced sections
|
||||
- **WHEN** reorganizing `openspec/AGENTS.md`
|
||||
- **THEN** keep an introductory section limited to the minimum steps (scaffold, draft, validate, request review)
|
||||
- **AND** move advanced topics (multi-capability changes, archiving details, tooling deep dives) into clearly labeled later sections
|
||||
- **AND** provide anchor links from the quick-reference to those advanced sections
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fission-ai/openspec",
|
||||
"version": "0.9.1",
|
||||
"version": "0.11.0",
|
||||
"description": "AI-native system for spec-driven development",
|
||||
"keywords": [
|
||||
"openspec",
|
||||
|
||||
+9
-2
@@ -4,6 +4,7 @@ import ora from 'ora';
|
||||
import path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
import { InitCommand } from '../core/init.js';
|
||||
import { AI_TOOLS } from '../core/config.js';
|
||||
import { UpdateCommand } from '../core/update.js';
|
||||
import { ListCommand } from '../core/list.js';
|
||||
import { ArchiveCommand } from '../core/archive.js';
|
||||
@@ -33,10 +34,14 @@ program.hook('preAction', (thisCommand) => {
|
||||
}
|
||||
});
|
||||
|
||||
const availableToolIds = AI_TOOLS.filter((tool) => tool.available).map((tool) => tool.value);
|
||||
const toolsOptionDescription = `Configure AI tools non-interactively. Use "all", "none", or a comma-separated list of: ${availableToolIds.join(', ')}`;
|
||||
|
||||
program
|
||||
.command('init [path]')
|
||||
.description('Initialize OpenSpec in your project')
|
||||
.action(async (targetPath = '.') => {
|
||||
.option('--tools <tools>', toolsOptionDescription)
|
||||
.action(async (targetPath = '.', options?: { tools?: string }) => {
|
||||
try {
|
||||
// Validate that the path is a valid directory
|
||||
const resolvedPath = path.resolve(targetPath);
|
||||
@@ -57,7 +62,9 @@ program
|
||||
}
|
||||
}
|
||||
|
||||
const initCommand = new InitCommand();
|
||||
const initCommand = new InitCommand({
|
||||
tools: options?.tools,
|
||||
});
|
||||
await initCommand.execute(targetPath);
|
||||
} catch (error) {
|
||||
console.log(); // Empty line for spacing
|
||||
|
||||
@@ -24,5 +24,6 @@ export const AI_TOOLS: AIToolOption[] = [
|
||||
{ name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf' },
|
||||
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex' },
|
||||
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot' },
|
||||
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer' },
|
||||
{ name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' }
|
||||
];
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { SlashCommandConfigurator } from './base.js';
|
||||
import { SlashCommandId } from '../../templates/index.js';
|
||||
|
||||
const FILE_PATHS: Record<SlashCommandId, string> = {
|
||||
proposal: '.amazonq/prompts/openspec-proposal.md',
|
||||
apply: '.amazonq/prompts/openspec-apply.md',
|
||||
archive: '.amazonq/prompts/openspec-archive.md'
|
||||
};
|
||||
|
||||
const FRONTMATTER: Record<SlashCommandId, string> = {
|
||||
proposal: `---
|
||||
description: Scaffold a new OpenSpec change and validate strictly.
|
||||
---
|
||||
|
||||
The user has requested the following change proposal. Use the openspec instructions to create their change proposal.
|
||||
|
||||
<UserRequest>
|
||||
$ARGUMENTS
|
||||
</UserRequest>`,
|
||||
apply: `---
|
||||
description: Implement an approved OpenSpec change and keep tasks in sync.
|
||||
---
|
||||
|
||||
The user wants to apply the following change. Use the openspec instructions to implement the approved change.
|
||||
|
||||
<ChangeId>
|
||||
$ARGUMENTS
|
||||
</ChangeId>`,
|
||||
archive: `---
|
||||
description: Archive a deployed OpenSpec change and update specs.
|
||||
---
|
||||
|
||||
The user wants to archive the following deployed change. Use the openspec instructions to archive the change and update specs.
|
||||
|
||||
<ChangeId>
|
||||
$ARGUMENTS
|
||||
</ChangeId>`
|
||||
};
|
||||
|
||||
export class AmazonQSlashCommandConfigurator extends SlashCommandConfigurator {
|
||||
readonly toolId = 'amazon-q';
|
||||
readonly isAvailable = true;
|
||||
|
||||
protected getRelativePath(id: SlashCommandId): string {
|
||||
return FILE_PATHS[id];
|
||||
}
|
||||
|
||||
protected getFrontmatter(id: SlashCommandId): string {
|
||||
return FRONTMATTER[id];
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { KiloCodeSlashCommandConfigurator } from './kilocode.js';
|
||||
import { OpenCodeSlashCommandConfigurator } from './opencode.js';
|
||||
import { CodexSlashCommandConfigurator } from './codex.js';
|
||||
import { GitHubCopilotSlashCommandConfigurator } from './github-copilot.js';
|
||||
import { AmazonQSlashCommandConfigurator } from './amazon-q.js';
|
||||
|
||||
export class SlashCommandRegistry {
|
||||
private static configurators: Map<string, SlashCommandConfigurator> = new Map();
|
||||
@@ -18,6 +19,7 @@ export class SlashCommandRegistry {
|
||||
const opencode = new OpenCodeSlashCommandConfigurator();
|
||||
const codex = new CodexSlashCommandConfigurator();
|
||||
const githubCopilot = new GitHubCopilotSlashCommandConfigurator();
|
||||
const amazonQ = new AmazonQSlashCommandConfigurator();
|
||||
|
||||
this.configurators.set(claude.toolId, claude);
|
||||
this.configurators.set(cursor.toolId, cursor);
|
||||
@@ -26,6 +28,7 @@ export class SlashCommandRegistry {
|
||||
this.configurators.set(opencode.toolId, opencode);
|
||||
this.configurators.set(codex.toolId, codex);
|
||||
this.configurators.set(githubCopilot.toolId, githubCopilot);
|
||||
this.configurators.set(amazonQ.toolId, amazonQ);
|
||||
}
|
||||
|
||||
static register(configurator: SlashCommandConfigurator): void {
|
||||
|
||||
+91
-5
@@ -220,6 +220,16 @@ const toolSelectionWizard = createPrompt<string[], ToolWizardConfig>(
|
||||
}
|
||||
|
||||
if (isEnterKey(key)) {
|
||||
const current = config.choices[cursor];
|
||||
if (
|
||||
current &&
|
||||
current.selectable &&
|
||||
!selectedSet.has(current.value)
|
||||
) {
|
||||
const next = new Set(selected);
|
||||
next.add(current.value);
|
||||
updateSelected(next);
|
||||
}
|
||||
setStep('review');
|
||||
setError(null);
|
||||
return;
|
||||
@@ -298,7 +308,7 @@ const toolSelectionWizard = createPrompt<string[], ToolWizardConfig>(
|
||||
lines.push(PALETTE.white(config.baseMessage));
|
||||
lines.push(
|
||||
PALETTE.midGray(
|
||||
'Use ↑/↓ to move · Space to toggle · Enter to review selections.'
|
||||
'Use ↑/↓ to move · Space to toggle · Enter selects highlighted tool and reviews.'
|
||||
)
|
||||
);
|
||||
lines.push('');
|
||||
@@ -359,13 +369,16 @@ const toolSelectionWizard = createPrompt<string[], ToolWizardConfig>(
|
||||
|
||||
type InitCommandOptions = {
|
||||
prompt?: ToolSelectionPrompt;
|
||||
tools?: string;
|
||||
};
|
||||
|
||||
export class InitCommand {
|
||||
private readonly prompt: ToolSelectionPrompt;
|
||||
private readonly toolsArg?: string;
|
||||
|
||||
constructor(options: InitCommandOptions = {}) {
|
||||
this.prompt = options.prompt ?? ((config) => toolSelectionWizard(config));
|
||||
this.toolsArg = options.tools;
|
||||
}
|
||||
|
||||
async execute(targetPath: string): Promise<void> {
|
||||
@@ -460,13 +473,86 @@ export class InitCommand {
|
||||
existingTools: Record<string, boolean>,
|
||||
extendMode: boolean
|
||||
): Promise<OpenSpecConfig> {
|
||||
const selectedTools = await this.promptForAITools(
|
||||
existingTools,
|
||||
extendMode
|
||||
);
|
||||
const selectedTools = await this.getSelectedTools(existingTools, extendMode);
|
||||
return { aiTools: selectedTools };
|
||||
}
|
||||
|
||||
private async getSelectedTools(
|
||||
existingTools: Record<string, boolean>,
|
||||
extendMode: boolean
|
||||
): Promise<string[]> {
|
||||
const nonInteractiveSelection = this.resolveToolsArg();
|
||||
if (nonInteractiveSelection !== null) {
|
||||
return nonInteractiveSelection;
|
||||
}
|
||||
|
||||
// Fall back to interactive mode
|
||||
return this.promptForAITools(existingTools, extendMode);
|
||||
}
|
||||
|
||||
private resolveToolsArg(): string[] | null {
|
||||
if (typeof this.toolsArg === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const raw = this.toolsArg.trim();
|
||||
if (raw.length === 0) {
|
||||
throw new Error(
|
||||
'The --tools option requires a value. Use "all", "none", or a comma-separated list of tool IDs.'
|
||||
);
|
||||
}
|
||||
|
||||
const availableTools = AI_TOOLS.filter((tool) => tool.available);
|
||||
const availableValues = availableTools.map((tool) => tool.value);
|
||||
const availableSet = new Set(availableValues);
|
||||
const availableList = ['all', 'none', ...availableValues].join(', ');
|
||||
|
||||
const lowerRaw = raw.toLowerCase();
|
||||
if (lowerRaw === 'all') {
|
||||
return availableValues;
|
||||
}
|
||||
|
||||
if (lowerRaw === 'none') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tokens = raw
|
||||
.split(',')
|
||||
.map((token) => token.trim())
|
||||
.filter((token) => token.length > 0);
|
||||
|
||||
if (tokens.length === 0) {
|
||||
throw new Error(
|
||||
'The --tools option requires at least one tool ID when not using "all" or "none".'
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedTokens = tokens.map((token) => token.toLowerCase());
|
||||
|
||||
if (normalizedTokens.some((token) => token === 'all' || token === 'none')) {
|
||||
throw new Error('Cannot combine reserved values "all" or "none" with specific tool IDs.');
|
||||
}
|
||||
|
||||
const invalidTokens = tokens.filter(
|
||||
(_token, index) => !availableSet.has(normalizedTokens[index])
|
||||
);
|
||||
|
||||
if (invalidTokens.length > 0) {
|
||||
throw new Error(
|
||||
`Invalid tool(s): ${invalidTokens.join(', ')}. Available values: ${availableList}`
|
||||
);
|
||||
}
|
||||
|
||||
const deduped: string[] = [];
|
||||
for (const token of normalizedTokens) {
|
||||
if (!deduped.includes(token)) {
|
||||
deduped.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
return deduped;
|
||||
}
|
||||
|
||||
private async promptForAITools(
|
||||
existingTools: Record<string, boolean>,
|
||||
extendMode: boolean
|
||||
|
||||
@@ -3,6 +3,16 @@ import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
import { runCLI, cliProjectRoot } from '../helpers/run-cli.js';
|
||||
import { AI_TOOLS } from '../../src/core/config.js';
|
||||
|
||||
async function fileExists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const tempRoots: string[] = [];
|
||||
|
||||
@@ -26,6 +36,20 @@ describe('openspec CLI e2e basics', () => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain('Usage: openspec');
|
||||
expect(result.stderr).toBe('');
|
||||
|
||||
});
|
||||
|
||||
it('shows dynamic tool ids in init help', async () => {
|
||||
const result = await runCLI(['init', '--help']);
|
||||
expect(result.exitCode).toBe(0);
|
||||
|
||||
const expectedTools = AI_TOOLS.filter((tool) => tool.available)
|
||||
.map((tool) => tool.value)
|
||||
.join(', ');
|
||||
const normalizedOutput = result.stdout.replace(/\s+/g, ' ').trim();
|
||||
expect(normalizedOutput).toContain(
|
||||
`Use "all", "none", or a comma-separated list of: ${expectedTools}`
|
||||
);
|
||||
});
|
||||
|
||||
it('reports the package version', async () => {
|
||||
@@ -53,4 +77,76 @@ describe('openspec CLI e2e basics', () => {
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.stderr).toContain("Unknown item 'does-not-exist'");
|
||||
});
|
||||
|
||||
describe('init command non-interactive options', () => {
|
||||
it('initializes with --tools all option', async () => {
|
||||
const projectDir = await prepareFixture('tmp-init');
|
||||
const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
|
||||
await fs.mkdir(emptyProjectDir, { recursive: true });
|
||||
|
||||
const result = await runCLI(['init', '--tools', 'all'], { cwd: emptyProjectDir });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain('Tool summary:');
|
||||
|
||||
// Check that tool configurations were created
|
||||
const claudePath = path.join(emptyProjectDir, 'CLAUDE.md');
|
||||
const cursorProposal = path.join(emptyProjectDir, '.cursor/commands/openspec-proposal.md');
|
||||
expect(await fileExists(claudePath)).toBe(true);
|
||||
expect(await fileExists(cursorProposal)).toBe(true);
|
||||
});
|
||||
|
||||
it('initializes with --tools list option', async () => {
|
||||
const projectDir = await prepareFixture('tmp-init');
|
||||
const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
|
||||
await fs.mkdir(emptyProjectDir, { recursive: true });
|
||||
|
||||
const result = await runCLI(['init', '--tools', 'claude'], { cwd: emptyProjectDir });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain('Tool summary:');
|
||||
|
||||
const claudePath = path.join(emptyProjectDir, 'CLAUDE.md');
|
||||
const cursorProposal = path.join(emptyProjectDir, '.cursor/commands/openspec-proposal.md');
|
||||
expect(await fileExists(claudePath)).toBe(true);
|
||||
expect(await fileExists(cursorProposal)).toBe(false); // Not selected
|
||||
});
|
||||
|
||||
it('initializes with --tools none option', async () => {
|
||||
const projectDir = await prepareFixture('tmp-init');
|
||||
const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
|
||||
await fs.mkdir(emptyProjectDir, { recursive: true });
|
||||
|
||||
const result = await runCLI(['init', '--tools', 'none'], { cwd: emptyProjectDir });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain('Tool summary:');
|
||||
|
||||
const claudePath = path.join(emptyProjectDir, 'CLAUDE.md');
|
||||
const cursorProposal = path.join(emptyProjectDir, '.cursor/commands/openspec-proposal.md');
|
||||
const rootAgentsPath = path.join(emptyProjectDir, 'AGENTS.md');
|
||||
|
||||
expect(await fileExists(rootAgentsPath)).toBe(true);
|
||||
expect(await fileExists(claudePath)).toBe(false);
|
||||
expect(await fileExists(cursorProposal)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns error for invalid tool names', async () => {
|
||||
const projectDir = await prepareFixture('tmp-init');
|
||||
const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
|
||||
await fs.mkdir(emptyProjectDir, { recursive: true });
|
||||
|
||||
const result = await runCLI(['init', '--tools', 'invalid-tool'], { cwd: emptyProjectDir });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.stderr).toContain('Invalid tool(s): invalid-tool');
|
||||
expect(result.stderr).toContain('Available values:');
|
||||
});
|
||||
|
||||
it('returns error when combining reserved keywords with explicit ids', async () => {
|
||||
const projectDir = await prepareFixture('tmp-init');
|
||||
const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
|
||||
await fs.mkdir(emptyProjectDir, { recursive: true });
|
||||
|
||||
const result = await runCLI(['init', '--tools', 'all,claude'], { cwd: emptyProjectDir });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.stderr).toContain('Cannot combine reserved values "all" or "none" with specific tool IDs');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -570,6 +570,146 @@ describe('InitCommand', () => {
|
||||
);
|
||||
expect(githubCopilotChoice.configured).toBe(true);
|
||||
});
|
||||
|
||||
it('should create Amazon Q Developer prompt files with templates', async () => {
|
||||
queueSelections('amazon-q', DONE);
|
||||
|
||||
await initCommand.execute(testDir);
|
||||
|
||||
const proposalPath = path.join(
|
||||
testDir,
|
||||
'.amazonq/prompts/openspec-proposal.md'
|
||||
);
|
||||
const applyPath = path.join(
|
||||
testDir,
|
||||
'.amazonq/prompts/openspec-apply.md'
|
||||
);
|
||||
const archivePath = path.join(
|
||||
testDir,
|
||||
'.amazonq/prompts/openspec-archive.md'
|
||||
);
|
||||
|
||||
expect(await fileExists(proposalPath)).toBe(true);
|
||||
expect(await fileExists(applyPath)).toBe(true);
|
||||
expect(await fileExists(archivePath)).toBe(true);
|
||||
|
||||
const proposalContent = await fs.readFile(proposalPath, 'utf-8');
|
||||
expect(proposalContent).toContain('---');
|
||||
expect(proposalContent).toContain('description: Scaffold a new OpenSpec change and validate strictly.');
|
||||
expect(proposalContent).toContain('$ARGUMENTS');
|
||||
expect(proposalContent).toContain('<!-- OPENSPEC:START -->');
|
||||
expect(proposalContent).toContain('**Guardrails**');
|
||||
|
||||
const applyContent = await fs.readFile(applyPath, 'utf-8');
|
||||
expect(applyContent).toContain('---');
|
||||
expect(applyContent).toContain('description: Implement an approved OpenSpec change and keep tasks in sync.');
|
||||
expect(applyContent).toContain('$ARGUMENTS');
|
||||
expect(applyContent).toContain('<!-- OPENSPEC:START -->');
|
||||
});
|
||||
|
||||
it('should mark Amazon Q Developer as already configured during extend mode', async () => {
|
||||
queueSelections('amazon-q', DONE, 'amazon-q', DONE);
|
||||
await initCommand.execute(testDir);
|
||||
await initCommand.execute(testDir);
|
||||
|
||||
const secondRunArgs = mockPrompt.mock.calls[1][0];
|
||||
const amazonQChoice = secondRunArgs.choices.find(
|
||||
(choice: any) => choice.value === 'amazon-q'
|
||||
);
|
||||
expect(amazonQChoice.configured).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('non-interactive mode', () => {
|
||||
it('should select all available tools with --tools all option', async () => {
|
||||
const nonInteractiveCommand = new InitCommand({ tools: 'all' });
|
||||
|
||||
await nonInteractiveCommand.execute(testDir);
|
||||
|
||||
// Should create configurations for all available tools
|
||||
const claudePath = path.join(testDir, 'CLAUDE.md');
|
||||
const cursorProposal = path.join(
|
||||
testDir,
|
||||
'.cursor/commands/openspec-proposal.md'
|
||||
);
|
||||
const windsurfProposal = path.join(
|
||||
testDir,
|
||||
'.windsurf/workflows/openspec-proposal.md'
|
||||
);
|
||||
|
||||
expect(await fileExists(claudePath)).toBe(true);
|
||||
expect(await fileExists(cursorProposal)).toBe(true);
|
||||
expect(await fileExists(windsurfProposal)).toBe(true);
|
||||
});
|
||||
|
||||
it('should select specific tools with --tools option', async () => {
|
||||
const nonInteractiveCommand = new InitCommand({ tools: 'claude,cursor' });
|
||||
|
||||
await nonInteractiveCommand.execute(testDir);
|
||||
|
||||
const claudePath = path.join(testDir, 'CLAUDE.md');
|
||||
const cursorProposal = path.join(
|
||||
testDir,
|
||||
'.cursor/commands/openspec-proposal.md'
|
||||
);
|
||||
const windsurfProposal = path.join(
|
||||
testDir,
|
||||
'.windsurf/workflows/openspec-proposal.md'
|
||||
);
|
||||
|
||||
expect(await fileExists(claudePath)).toBe(true);
|
||||
expect(await fileExists(cursorProposal)).toBe(true);
|
||||
expect(await fileExists(windsurfProposal)).toBe(false); // Not selected
|
||||
});
|
||||
|
||||
it('should skip tool configuration with --tools none option', async () => {
|
||||
const nonInteractiveCommand = new InitCommand({ tools: 'none' });
|
||||
|
||||
await nonInteractiveCommand.execute(testDir);
|
||||
|
||||
const claudePath = path.join(testDir, 'CLAUDE.md');
|
||||
const cursorProposal = path.join(
|
||||
testDir,
|
||||
'.cursor/commands/openspec-proposal.md'
|
||||
);
|
||||
|
||||
// Should still create AGENTS.md but no tool-specific files
|
||||
const rootAgentsPath = path.join(testDir, 'AGENTS.md');
|
||||
expect(await fileExists(rootAgentsPath)).toBe(true);
|
||||
expect(await fileExists(claudePath)).toBe(false);
|
||||
expect(await fileExists(cursorProposal)).toBe(false);
|
||||
});
|
||||
|
||||
it('should throw error for invalid tool names', async () => {
|
||||
const nonInteractiveCommand = new InitCommand({ tools: 'invalid-tool' });
|
||||
|
||||
await expect(nonInteractiveCommand.execute(testDir)).rejects.toThrow(
|
||||
/Invalid tool\(s\): invalid-tool\. Available values: /
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle comma-separated tool names with spaces', async () => {
|
||||
const nonInteractiveCommand = new InitCommand({ tools: 'claude, cursor' });
|
||||
|
||||
await nonInteractiveCommand.execute(testDir);
|
||||
|
||||
const claudePath = path.join(testDir, 'CLAUDE.md');
|
||||
const cursorProposal = path.join(
|
||||
testDir,
|
||||
'.cursor/commands/openspec-proposal.md'
|
||||
);
|
||||
|
||||
expect(await fileExists(claudePath)).toBe(true);
|
||||
expect(await fileExists(cursorProposal)).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject combining reserved keywords with explicit tool ids', async () => {
|
||||
const nonInteractiveCommand = new InitCommand({ tools: 'all,claude' });
|
||||
|
||||
await expect(nonInteractiveCommand.execute(testDir)).rejects.toThrow(
|
||||
/Cannot combine reserved values "all" or "none" with specific tool IDs/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
|
||||
@@ -377,6 +377,72 @@ Old body
|
||||
await expect(FileSystemUtils.fileExists(ghArchive)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('should refresh existing Amazon Q Developer prompts', async () => {
|
||||
const aqPath = path.join(
|
||||
testDir,
|
||||
'.amazonq/prompts/openspec-apply.md'
|
||||
);
|
||||
await fs.mkdir(path.dirname(aqPath), { recursive: true });
|
||||
const initialContent = `---
|
||||
description: Implement an approved OpenSpec change and keep tasks in sync.
|
||||
---
|
||||
|
||||
The user wants to apply the following change. Use the openspec instructions to implement the approved change.
|
||||
|
||||
<ChangeId>
|
||||
$ARGUMENTS
|
||||
</ChangeId>
|
||||
<!-- OPENSPEC:START -->
|
||||
Old body
|
||||
<!-- OPENSPEC:END -->`;
|
||||
await fs.writeFile(aqPath, initialContent);
|
||||
|
||||
const consoleSpy = vi.spyOn(console, 'log');
|
||||
|
||||
await updateCommand.execute(testDir);
|
||||
|
||||
const updatedContent = await fs.readFile(aqPath, 'utf-8');
|
||||
expect(updatedContent).toContain('**Guardrails**');
|
||||
expect(updatedContent).toContain('<!-- OPENSPEC:START -->');
|
||||
expect(updatedContent).toContain('<!-- OPENSPEC:END -->');
|
||||
expect(updatedContent).not.toContain('Old body');
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('.amazonq/prompts/openspec-apply.md')
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not create missing Amazon Q Developer prompts on update', async () => {
|
||||
const aqApply = path.join(
|
||||
testDir,
|
||||
'.amazonq/prompts/openspec-apply.md'
|
||||
);
|
||||
|
||||
// Only create apply; leave proposal and archive missing
|
||||
await fs.mkdir(path.dirname(aqApply), { recursive: true });
|
||||
await fs.writeFile(
|
||||
aqApply,
|
||||
'---\ndescription: Old\n---\n\nThe user wants to apply the following change.\n\n<ChangeId>\n $ARGUMENTS\n</ChangeId>\n<!-- OPENSPEC:START -->\nOld\n<!-- OPENSPEC:END -->'
|
||||
);
|
||||
|
||||
await updateCommand.execute(testDir);
|
||||
|
||||
const aqProposal = path.join(
|
||||
testDir,
|
||||
'.amazonq/prompts/openspec-proposal.md'
|
||||
);
|
||||
const aqArchive = path.join(
|
||||
testDir,
|
||||
'.amazonq/prompts/openspec-archive.md'
|
||||
);
|
||||
|
||||
// Confirm they weren't created by update
|
||||
await expect(FileSystemUtils.fileExists(aqProposal)).resolves.toBe(false);
|
||||
await expect(FileSystemUtils.fileExists(aqArchive)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('should preserve Windsurf content outside markers during update', async () => {
|
||||
const wsPath = path.join(
|
||||
testDir,
|
||||
|
||||
Reference in New Issue
Block a user