Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions openspec/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ Track these steps as TODOs and complete them one by one.
After deployment, create separate PR to:
- Move `changes/[name]/` → `changes/archive/YYYY-MM-DD-[name]/`
- Update `specs/` if capabilities changed
- Use `openspec archive [change] --skip-specs --yes` for tooling-only changes
- Use `openspec archive <change-id> --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly)
- OpenCode slash command: `/openspec:archive <change-id>` passes the change ID via arguments—fail fast if the ID is missing or invalid
- Run `openspec validate --strict` to confirm the archived change passes checks

## Before Any Task
Expand Down Expand Up @@ -97,7 +98,7 @@ openspec list --specs # List specifications
openspec show [item] # Display change or spec
openspec diff [change] # Show spec differences
openspec validate [item] # Validate changes or specs
openspec archive [change] [--yes|-y] # Archive after deployment (add --yes for non-interactive runs)
openspec archive <change-id> [--yes|-y] # Archive after deployment (add --yes for non-interactive runs)

# Project management
openspec init [path] # Initialize OpenSpec
Expand Down
17 changes: 17 additions & 0 deletions openspec/changes/add-archive-command-arguments/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Add Archive Command Arguments

## Why
The `/openspec:archive` slash command currently lacks argument support, forcing the AI to infer which change to archive from conversation context or by listing all changes. This creates a safety risk where the wrong proposal could be archived if the context is ambiguous or multiple changes exist. Users expect to specify the change ID explicitly, matching the behavior of the CLI command `openspec archive <id>`.

## What Changes
- Add `$ARGUMENTS` placeholder to the OpenCode archive slash command frontmatter (matching existing pattern for proposal command)
- Update archive command template steps to validate the specific change ID argument when provided
- Note: Codex, GitHub Copilot, and Amazon Q already have `$ARGUMENTS` for archive; Claude/Cursor/Windsurf/Kilocode don't support arguments

## Impact
- Affected specs: `cli-update` (slash command generation logic)
- Affected code:
- `src/core/configurators/slash/opencode.ts` (add `$ARGUMENTS` to archive frontmatter)
- `src/core/templates/slash-command-templates.ts` (archive template steps for argument validation)
- Breaking: No - this is additive functionality that makes the command safer
- User-facing: Yes - OpenCode users will be able to pass the change ID as an argument: `/openspec:archive <change-id>`
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# CLI Update Specification Delta

## MODIFIED Requirements

### Requirement: Slash Command Updates
The update command SHALL refresh existing slash command files for configured tools without creating new ones, and ensure the OpenCode archive command accepts change ID arguments.

#### Scenario: Updating slash commands for OpenCode
- **WHEN** `.opencode/command/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
- **THEN** refresh each file using shared templates
- **AND** ensure templates include instructions for the relevant workflow stage
- **AND** ensure the archive command includes `$ARGUMENTS` placeholder in frontmatter for accepting change ID arguments

## ADDED Requirements

### Requirement: Archive Command Argument Support
The archive slash command template SHALL support optional change ID arguments for tools that support `$ARGUMENTS` placeholder.

#### Scenario: Archive command with change ID argument
- **WHEN** a user invokes `/openspec:archive <change-id>` with a change ID
- **THEN** the template SHALL instruct the AI to validate the provided change ID against `openspec list`
- **AND** use the provided change ID for archiving if valid
- **AND** fail fast if the provided change ID doesn't match an archivable change

#### Scenario: Archive command without argument (backward compatibility)
- **WHEN** a user invokes `/openspec:archive` without providing a change ID
- **THEN** the template SHALL instruct the AI to identify the change ID from context or by running `openspec list`
- **AND** proceed with the existing behavior (maintaining backward compatibility)

#### Scenario: OpenCode archive template generation
- **WHEN** generating the OpenCode archive slash command file
- **THEN** include the `$ARGUMENTS` placeholder in the frontmatter
- **AND** wrap it in a clear structure like `<ChangeId>\n $ARGUMENTS\n</ChangeId>` to indicate the expected argument
- **AND** include validation steps in the template body to check if the change ID is valid
21 changes: 21 additions & 0 deletions openspec/changes/add-archive-command-arguments/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Implementation Tasks

## 1. Update OpenCode Configurator
- [x] 1.1 Add `$ARGUMENTS` placeholder to OpenCode archive frontmatter (matching the proposal pattern)
- [x] 1.2 Format it as `<ChangeId>\n $ARGUMENTS\n</ChangeId>` or similar structure for clarity
- [x] 1.3 Ensure `updateExisting` rewrites the archive frontmatter/body so `$ARGUMENTS` persists after `openspec update`

## 2. Update Slash Command Templates
- [x] 2.1 Modify archive steps to validate change ID argument when provided via `$ARGUMENTS`
- [x] 2.2 Keep backward compatibility - allow inferring from context if no argument provided
- [x] 2.3 Add step to validate the change ID exists using `openspec list` before archiving

## 3. Update Documentation
- [x] 3.1 Update AGENTS.md archive examples to show argument usage
- [x] 3.2 Document that OpenCode now supports `/openspec:archive <change-id>`

## 4. Validation and Testing
- [ ] 4.1 Run `openspec update` to regenerate OpenCode slash commands
- [ ] 4.2 Manually test with OpenCode using `/openspec:archive <change-id>`
- [ ] 4.3 Test backward compatibility (archive command without arguments)
- [ ] 4.4 Run `openspec validate --strict` to ensure no issues
42 changes: 41 additions & 1 deletion src/core/configurators/slash/opencode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { SlashCommandConfigurator } from "./base.js";
import { SlashCommandId } from "../../templates/index.js";
import { FileSystemUtils } from "../../../utils/file-system.js";
import { OPENSPEC_MARKERS } from "../../config.js";

const FILE_PATHS: Record<SlashCommandId, string> = {
proposal: ".opencode/command/openspec-proposal.md",
Expand All @@ -24,7 +26,11 @@ description: Implement an approved OpenSpec change and keep tasks in sync.
archive: `---
agent: build
description: Archive a deployed OpenSpec change and update specs.
---`,
---
<ChangeId>
$ARGUMENTS
</ChangeId>
`,
};

export class OpenCodeSlashCommandConfigurator extends SlashCommandConfigurator {
Expand All @@ -38,4 +44,38 @@ export class OpenCodeSlashCommandConfigurator extends SlashCommandConfigurator {
protected getFrontmatter(id: SlashCommandId): string | undefined {
return FRONTMATTER[id];
}

async generateAll(projectPath: string, _openspecDir: string): Promise<string[]> {
const createdOrUpdated = await super.generateAll(projectPath, _openspecDir);
await this.rewriteArchiveFile(projectPath);
return createdOrUpdated;
}

async updateExisting(projectPath: string, _openspecDir: string): Promise<string[]> {
const updated = await super.updateExisting(projectPath, _openspecDir);
const rewroteArchive = await this.rewriteArchiveFile(projectPath);
if (rewroteArchive && !updated.includes(FILE_PATHS.archive)) {
updated.push(FILE_PATHS.archive);
}
return updated;
}

private async rewriteArchiveFile(projectPath: string): Promise<boolean> {
const archivePath = FileSystemUtils.joinPath(projectPath, FILE_PATHS.archive);
if (!await FileSystemUtils.fileExists(archivePath)) {
return false;
}

const body = this.getBody("archive");
const frontmatter = this.getFrontmatter("archive");
const sections: string[] = [];

if (frontmatter) {
sections.push(frontmatter.trim());
}

sections.push(`${OPENSPEC_MARKERS.start}\n${body}\n${OPENSPEC_MARKERS.end}`);
await FileSystemUtils.writeFile(archivePath, sections.join("\n") + "\n");
return true;
}
}
14 changes: 10 additions & 4 deletions src/core/templates/slash-command-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ const applyReferences = `**Reference**
- Use \`openspec show <id> --json --deltas-only\` if you need additional context from the proposal while implementing.`;

const archiveSteps = `**Steps**
1. Identify the requested change ID (via the prompt or \`openspec list\`).
2. Run \`openspec archive <id> --yes\` to let the CLI move the change and apply spec updates without prompts (use \`--skip-specs\` only for tooling-only work).
3. Review the command output to confirm the target specs were updated and the change landed in \`changes/archive/\`.
4. Validate with \`openspec validate --strict\` and inspect with \`openspec show <id>\` if anything looks off.`;
1. Determine the change ID to archive:
- If this prompt already includes a specific change ID (for example inside a \`<ChangeId>\` block populated by slash-command arguments), use that value after trimming whitespace.
- If the conversation references a change loosely (for example by title or summary), run \`openspec list\` to surface likely IDs, share the relevant candidates, and confirm which one the user intends.
- Otherwise, review the conversation, run \`openspec list\`, and ask the user which change to archive; wait for a confirmed change ID before proceeding.
- If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet.
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.`;

const archiveReferences = `**Reference**
- Use \`openspec list\` to confirm change IDs before archiving.
- Inspect refreshed specs with \`openspec list --specs\` and address any validation issues before handing off.`;

export const slashCommandBodies: Record<SlashCommandId, string> = {
Expand Down
Loading