Skip to content

Commit 63b8a3e

Browse files
authored
feat: add argument support to archive slash command (#183)
* feat: add argument support to archive slash command Add $ARGUMENTS placeholder to the /openspec:archive slash command to allow explicit change ID specification, improving safety and matching CLI behavior. Changes: - Add $ARGUMENTS to archive command frontmatter in OpenCode configurator - Update archive command template with argument validation steps - Add rewriteArchiveFile method to handle managed section updates - Update AGENTS.md documentation for archive command usage This change makes it possible for users to run `/openspec:archive <change-id>` instead of relying on context inference, reducing the risk of archiving the wrong change. * refactor: improve archive slash command argument handling instructions Update the archive slash command template to provide clearer guidance on how to handle change IDs from arguments versus conversation context. The new instructions better distinguish between explicit argument-provided IDs and contextual references, and provide more explicit failure modes.
1 parent 4cf7bf8 commit 63b8a3e

File tree

6 files changed

+126
-7
lines changed

6 files changed

+126
-7
lines changed

openspec/AGENTS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ Track these steps as TODOs and complete them one by one.
6060
After deployment, create separate PR to:
6161
- Move `changes/[name]/``changes/archive/YYYY-MM-DD-[name]/`
6262
- Update `specs/` if capabilities changed
63-
- Use `openspec archive [change] --skip-specs --yes` for tooling-only changes
63+
- Use `openspec archive <change-id> --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly)
64+
- OpenCode slash command: `/openspec:archive <change-id>` passes the change ID via arguments—fail fast if the ID is missing or invalid
6465
- Run `openspec validate --strict` to confirm the archived change passes checks
6566

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

102103
# Project management
103104
openspec init [path] # Initialize OpenSpec
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Add Archive Command Arguments
2+
3+
## Why
4+
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>`.
5+
6+
## What Changes
7+
- Add `$ARGUMENTS` placeholder to the OpenCode archive slash command frontmatter (matching existing pattern for proposal command)
8+
- Update archive command template steps to validate the specific change ID argument when provided
9+
- Note: Codex, GitHub Copilot, and Amazon Q already have `$ARGUMENTS` for archive; Claude/Cursor/Windsurf/Kilocode don't support arguments
10+
11+
## Impact
12+
- Affected specs: `cli-update` (slash command generation logic)
13+
- Affected code:
14+
- `src/core/configurators/slash/opencode.ts` (add `$ARGUMENTS` to archive frontmatter)
15+
- `src/core/templates/slash-command-templates.ts` (archive template steps for argument validation)
16+
- Breaking: No - this is additive functionality that makes the command safer
17+
- User-facing: Yes - OpenCode users will be able to pass the change ID as an argument: `/openspec:archive <change-id>`
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# CLI Update Specification Delta
2+
3+
## MODIFIED Requirements
4+
5+
### Requirement: Slash Command Updates
6+
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.
7+
8+
#### Scenario: Updating slash commands for OpenCode
9+
- **WHEN** `.opencode/command/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
10+
- **THEN** refresh each file using shared templates
11+
- **AND** ensure templates include instructions for the relevant workflow stage
12+
- **AND** ensure the archive command includes `$ARGUMENTS` placeholder in frontmatter for accepting change ID arguments
13+
14+
## ADDED Requirements
15+
16+
### Requirement: Archive Command Argument Support
17+
The archive slash command template SHALL support optional change ID arguments for tools that support `$ARGUMENTS` placeholder.
18+
19+
#### Scenario: Archive command with change ID argument
20+
- **WHEN** a user invokes `/openspec:archive <change-id>` with a change ID
21+
- **THEN** the template SHALL instruct the AI to validate the provided change ID against `openspec list`
22+
- **AND** use the provided change ID for archiving if valid
23+
- **AND** fail fast if the provided change ID doesn't match an archivable change
24+
25+
#### Scenario: Archive command without argument (backward compatibility)
26+
- **WHEN** a user invokes `/openspec:archive` without providing a change ID
27+
- **THEN** the template SHALL instruct the AI to identify the change ID from context or by running `openspec list`
28+
- **AND** proceed with the existing behavior (maintaining backward compatibility)
29+
30+
#### Scenario: OpenCode archive template generation
31+
- **WHEN** generating the OpenCode archive slash command file
32+
- **THEN** include the `$ARGUMENTS` placeholder in the frontmatter
33+
- **AND** wrap it in a clear structure like `<ChangeId>\n $ARGUMENTS\n</ChangeId>` to indicate the expected argument
34+
- **AND** include validation steps in the template body to check if the change ID is valid
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Implementation Tasks
2+
3+
## 1. Update OpenCode Configurator
4+
- [x] 1.1 Add `$ARGUMENTS` placeholder to OpenCode archive frontmatter (matching the proposal pattern)
5+
- [x] 1.2 Format it as `<ChangeId>\n $ARGUMENTS\n</ChangeId>` or similar structure for clarity
6+
- [x] 1.3 Ensure `updateExisting` rewrites the archive frontmatter/body so `$ARGUMENTS` persists after `openspec update`
7+
8+
## 2. Update Slash Command Templates
9+
- [x] 2.1 Modify archive steps to validate change ID argument when provided via `$ARGUMENTS`
10+
- [x] 2.2 Keep backward compatibility - allow inferring from context if no argument provided
11+
- [x] 2.3 Add step to validate the change ID exists using `openspec list` before archiving
12+
13+
## 3. Update Documentation
14+
- [x] 3.1 Update AGENTS.md archive examples to show argument usage
15+
- [x] 3.2 Document that OpenCode now supports `/openspec:archive <change-id>`
16+
17+
## 4. Validation and Testing
18+
- [ ] 4.1 Run `openspec update` to regenerate OpenCode slash commands
19+
- [ ] 4.2 Manually test with OpenCode using `/openspec:archive <change-id>`
20+
- [ ] 4.3 Test backward compatibility (archive command without arguments)
21+
- [ ] 4.4 Run `openspec validate --strict` to ensure no issues

src/core/configurators/slash/opencode.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { SlashCommandConfigurator } from "./base.js";
22
import { SlashCommandId } from "../../templates/index.js";
3+
import { FileSystemUtils } from "../../../utils/file-system.js";
4+
import { OPENSPEC_MARKERS } from "../../config.js";
35

46
const FILE_PATHS: Record<SlashCommandId, string> = {
57
proposal: ".opencode/command/openspec-proposal.md",
@@ -24,7 +26,11 @@ description: Implement an approved OpenSpec change and keep tasks in sync.
2426
archive: `---
2527
agent: build
2628
description: Archive a deployed OpenSpec change and update specs.
27-
---`,
29+
---
30+
<ChangeId>
31+
$ARGUMENTS
32+
</ChangeId>
33+
`,
2834
};
2935

3036
export class OpenCodeSlashCommandConfigurator extends SlashCommandConfigurator {
@@ -38,4 +44,38 @@ export class OpenCodeSlashCommandConfigurator extends SlashCommandConfigurator {
3844
protected getFrontmatter(id: SlashCommandId): string | undefined {
3945
return FRONTMATTER[id];
4046
}
47+
48+
async generateAll(projectPath: string, _openspecDir: string): Promise<string[]> {
49+
const createdOrUpdated = await super.generateAll(projectPath, _openspecDir);
50+
await this.rewriteArchiveFile(projectPath);
51+
return createdOrUpdated;
52+
}
53+
54+
async updateExisting(projectPath: string, _openspecDir: string): Promise<string[]> {
55+
const updated = await super.updateExisting(projectPath, _openspecDir);
56+
const rewroteArchive = await this.rewriteArchiveFile(projectPath);
57+
if (rewroteArchive && !updated.includes(FILE_PATHS.archive)) {
58+
updated.push(FILE_PATHS.archive);
59+
}
60+
return updated;
61+
}
62+
63+
private async rewriteArchiveFile(projectPath: string): Promise<boolean> {
64+
const archivePath = FileSystemUtils.joinPath(projectPath, FILE_PATHS.archive);
65+
if (!await FileSystemUtils.fileExists(archivePath)) {
66+
return false;
67+
}
68+
69+
const body = this.getBody("archive");
70+
const frontmatter = this.getFrontmatter("archive");
71+
const sections: string[] = [];
72+
73+
if (frontmatter) {
74+
sections.push(frontmatter.trim());
75+
}
76+
77+
sections.push(`${OPENSPEC_MARKERS.start}\n${body}\n${OPENSPEC_MARKERS.end}`);
78+
await FileSystemUtils.writeFile(archivePath, sections.join("\n") + "\n");
79+
return true;
80+
}
4181
}

src/core/templates/slash-command-templates.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,18 @@ const applyReferences = `**Reference**
3333
- Use \`openspec show <id> --json --deltas-only\` if you need additional context from the proposal while implementing.`;
3434

3535
const archiveSteps = `**Steps**
36-
1. Identify the requested change ID (via the prompt or \`openspec list\`).
37-
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).
38-
3. Review the command output to confirm the target specs were updated and the change landed in \`changes/archive/\`.
39-
4. Validate with \`openspec validate --strict\` and inspect with \`openspec show <id>\` if anything looks off.`;
36+
1. Determine the change ID to archive:
37+
- 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.
38+
- 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.
39+
- Otherwise, review the conversation, run \`openspec list\`, and ask the user which change to archive; wait for a confirmed change ID before proceeding.
40+
- If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet.
41+
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.
42+
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).
43+
4. Review the command output to confirm the target specs were updated and the change landed in \`changes/archive/\`.
44+
5. Validate with \`openspec validate --strict\` and inspect with \`openspec show <id>\` if anything looks off.`;
4045

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

4450
export const slashCommandBodies: Record<SlashCommandId, string> = {

0 commit comments

Comments
 (0)