mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-03 22:13:19 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11b934d472 |
@@ -1,5 +1,11 @@
|
||||
# @fission-ai/openspec
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add Kilo Code workflow support so `openspec init` scaffolds `.kilocode/workflows/openspec-*.md` and `openspec update` refreshes them in place.
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -84,6 +84,9 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe
|
||||
| **Claude Code** | `/openspec:proposal`, `/openspec:apply`, `/openspec:archive` |
|
||||
| **Cursor** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` |
|
||||
| **OpenCode** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` |
|
||||
| **Kilo Code** | `/openspec-proposal.md`, `/openspec-apply.md`, `/openspec-archive.md` (`.kilocode/workflows/`) |
|
||||
|
||||
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`.
|
||||
|
||||
#### AGENTS.md Compatible
|
||||
These tools automatically read workflow instructions from `openspec/AGENTS.md`. Ask them to follow the OpenSpec workflow if they need a reminder. Learn more about the [AGENTS.md convention](https://agents.md/).
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
## 1. CLI wiring
|
||||
- [ ] 1.1 Add Kilo Code to the selectable AI tools in `openspec init`, including "already configured" detection and success summaries.
|
||||
- [ ] 1.2 Register a `KiloCodeSlashCommandConfigurator` alongside other slash-command tools.
|
||||
- [x] 1.1 Add Kilo Code to the selectable AI tools in `openspec init`, including "already configured" detection and success summaries.
|
||||
- [x] 1.2 Register a `KiloCodeSlashCommandConfigurator` alongside other slash-command tools.
|
||||
|
||||
## 2. Workflow generation
|
||||
- [ ] 2.1 Implement the configurator so it creates `.kilocode/workflows/` (if needed) and writes `openspec-{proposal,apply,archive}.md` with OpenSpec markers.
|
||||
- [ ] 2.2 Reuse the shared slash-command bodies without front matter; verify resulting files stay Markdown-only with no extra metadata.
|
||||
- [x] 2.1 Implement the configurator so it creates `.kilocode/workflows/` (if needed) and writes `openspec-{proposal,apply,archive}.md` with OpenSpec markers.
|
||||
- [x] 2.2 Reuse the shared slash-command bodies without front matter; verify resulting files stay Markdown-only with no extra metadata.
|
||||
|
||||
## 3. Update support
|
||||
- [ ] 3.1 Ensure `openspec update` refreshes existing Kilo Code workflows while skipping ones that are absent.
|
||||
- [ ] 3.2 Add regression coverage confirming marker content is replaced (not duplicated) during updates.
|
||||
- [x] 3.1 Ensure `openspec update` refreshes existing Kilo Code workflows while skipping ones that are absent.
|
||||
- [x] 3.2 Add regression coverage confirming marker content is replaced (not duplicated) during updates.
|
||||
|
||||
## 4. Documentation
|
||||
- [ ] 4.1 Update README / docs to note Kilo Code workflow support and path (`.kilocode/workflows/`).
|
||||
- [ ] 4.2 Mention the integration in CHANGELOG or release notes if applicable.
|
||||
- [x] 4.1 Update README / docs to note Kilo Code workflow support and path (`.kilocode/workflows/`).
|
||||
- [x] 4.2 Mention the integration in CHANGELOG or release notes if applicable.
|
||||
|
||||
@@ -20,5 +20,6 @@ export const AI_TOOLS: AIToolOption[] = [
|
||||
{ name: 'Claude Code (✅ OpenSpec custom slash commands available)', value: 'claude', available: true, successLabel: 'Claude Code' },
|
||||
{ name: 'Cursor (✅ OpenSpec custom slash commands available)', value: 'cursor', available: true, successLabel: 'Cursor' },
|
||||
{ name: 'OpenCode (✅ OpenSpec custom slash commands available)', value: 'opencode', available: true, successLabel: 'OpenCode' },
|
||||
{ name: 'Kilo Code (✅ OpenSpec workflows available)', value: 'kilocode', available: true, successLabel: 'Kilo Code' },
|
||||
{ name: 'AGENTS.md (works with Codex, Amp, Copilot, …)', value: 'agents', available: true, successLabel: 'your AGENTS.md-compatible assistant' }
|
||||
];
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { SlashCommandConfigurator } from "./base.js";
|
||||
import { SlashCommandId } from "../../templates/index.js";
|
||||
|
||||
const FILE_PATHS: Record<SlashCommandId, string> = {
|
||||
proposal: ".kilocode/workflows/openspec-proposal.md",
|
||||
apply: ".kilocode/workflows/openspec-apply.md",
|
||||
archive: ".kilocode/workflows/openspec-archive.md"
|
||||
};
|
||||
|
||||
export class KiloCodeSlashCommandConfigurator extends SlashCommandConfigurator {
|
||||
readonly toolId = "kilocode";
|
||||
readonly isAvailable = true;
|
||||
|
||||
protected getRelativePath(id: SlashCommandId): string {
|
||||
return FILE_PATHS[id];
|
||||
}
|
||||
|
||||
protected getFrontmatter(_id: SlashCommandId): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SlashCommandConfigurator } from './base.js';
|
||||
import { ClaudeSlashCommandConfigurator } from './claude.js';
|
||||
import { CursorSlashCommandConfigurator } from './cursor.js';
|
||||
import { KiloCodeSlashCommandConfigurator } from './kilocode.js';
|
||||
import { OpenCodeSlashCommandConfigurator } from './opencode.js';
|
||||
|
||||
export class SlashCommandRegistry {
|
||||
@@ -9,10 +10,12 @@ export class SlashCommandRegistry {
|
||||
static {
|
||||
const claude = new ClaudeSlashCommandConfigurator();
|
||||
const cursor = new CursorSlashCommandConfigurator();
|
||||
const kilocode = new KiloCodeSlashCommandConfigurator();
|
||||
const opencode = new OpenCodeSlashCommandConfigurator();
|
||||
|
||||
this.configurators.set(claude.toolId, claude);
|
||||
this.configurators.set(cursor.toolId, cursor);
|
||||
this.configurators.set(kilocode.toolId, kilocode);
|
||||
this.configurators.set(opencode.toolId, opencode);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ export type SlashCommandId = 'proposal' | 'apply' | 'archive';
|
||||
const baseGuardrails = `**Guardrails**
|
||||
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
|
||||
- Keep changes tightly scoped to the requested outcome.
|
||||
- Refer to \`openspec/AGENTS.md\` if you need additional OpenSpec conventions or clarifications.`;
|
||||
- Refer to \`openspec/AGENTS.md\` (located inside the \`openspec/\` directory—run \`ls openspec\` or \`openspec update\` if you don't see it) if you need additional OpenSpec conventions or clarifications.`;
|
||||
|
||||
const proposalGuardrails = `${baseGuardrails}\n- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files.`;
|
||||
|
||||
@@ -12,7 +12,7 @@ const proposalSteps = `**Steps**
|
||||
2. Choose a unique verb-led \`change-id\` and scaffold \`proposal.md\`, \`tasks.md\`, and \`design.md\` (when needed) under \`openspec/changes/<id>/\`.
|
||||
3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing.
|
||||
4. Capture architectural reasoning in \`design.md\` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs.
|
||||
5. Draft spec deltas in \`changes/<id>/specs/\` using \`## ADDED|MODIFIED|REMOVED Requirements\` with at least one \`#### Scenario:\` per requirement and cross-reference related capabilities when relevant.
|
||||
5. Draft spec deltas in \`changes/<id>/specs/<capability>/spec.md\` (one folder per capability) using \`## ADDED|MODIFIED|REMOVED Requirements\` with at least one \`#### Scenario:\` per requirement and cross-reference related capabilities when relevant.
|
||||
6. Draft \`tasks.md\` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work.
|
||||
7. Validate with \`openspec validate <id> --strict\` and resolve every issue before sharing the proposal.`;
|
||||
|
||||
|
||||
@@ -38,11 +38,11 @@ export const VALIDATION_MESSAGES = {
|
||||
|
||||
// Guidance snippets (appended to primary messages for remediation)
|
||||
GUIDE_NO_DELTAS:
|
||||
'No deltas found. Ensure your change has a specs/ directory with .md files using delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one "#### Scenario:" block. Tip: run "openspec change show <change-id> --json --deltas-only" to inspect parsed deltas.',
|
||||
'No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one "#### Scenario:" block. Tip: run "openspec change show <change-id> --json --deltas-only" to inspect parsed deltas.',
|
||||
GUIDE_MISSING_SPEC_SECTIONS:
|
||||
'Missing required sections. Expected headers: "## Purpose" and "## Requirements". Example:\n## Purpose\n[brief purpose]\n\n## Requirements\n### Requirement: Clear requirement statement\nUsers SHALL ...\n\n#### Scenario: Descriptive name\n- **WHEN** ...\n- **THEN** ...',
|
||||
GUIDE_MISSING_CHANGE_SECTIONS:
|
||||
'Missing required sections. Expected headers: "## Why" and "## What Changes". Ensure deltas are documented in specs/ using delta headers.',
|
||||
GUIDE_SCENARIO_FORMAT:
|
||||
'Scenarios must use level-4 headers. Convert bullet lists into:\n#### Scenario: Short name\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...',
|
||||
} as const;
|
||||
} as const;
|
||||
|
||||
@@ -265,6 +265,42 @@ describe('InitCommand', () => {
|
||||
expect(archiveContent).toContain('openspec list --specs');
|
||||
});
|
||||
|
||||
it('should create Kilo Code workflows with templates', async () => {
|
||||
queueSelections('kilocode', DONE);
|
||||
|
||||
await initCommand.execute(testDir);
|
||||
|
||||
const proposalPath = path.join(
|
||||
testDir,
|
||||
'.kilocode/workflows/openspec-proposal.md'
|
||||
);
|
||||
const applyPath = path.join(
|
||||
testDir,
|
||||
'.kilocode/workflows/openspec-apply.md'
|
||||
);
|
||||
const archivePath = path.join(
|
||||
testDir,
|
||||
'.kilocode/workflows/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('<!-- OPENSPEC:START -->');
|
||||
expect(proposalContent).toContain('**Guardrails**');
|
||||
expect(proposalContent).not.toContain('---\n');
|
||||
|
||||
const applyContent = await fs.readFile(applyPath, 'utf-8');
|
||||
expect(applyContent).toContain('Work through tasks sequentially');
|
||||
expect(applyContent).not.toContain('---\n');
|
||||
|
||||
const archiveContent = await fs.readFile(archivePath, 'utf-8');
|
||||
expect(archiveContent).toContain('openspec list --specs');
|
||||
expect(archiveContent).not.toContain('---\n');
|
||||
});
|
||||
|
||||
it('should add new tool when OpenSpec already exists', async () => {
|
||||
queueSelections('claude', DONE, 'cursor', DONE);
|
||||
await initCommand.execute(testDir);
|
||||
@@ -353,6 +389,16 @@ describe('InitCommand', () => {
|
||||
);
|
||||
expect(claudeChoice.configured).toBe(true);
|
||||
});
|
||||
|
||||
it('should preselect Kilo Code when workflows already exist', async () => {
|
||||
queueSelections('kilocode', DONE, 'kilocode', DONE);
|
||||
await initCommand.execute(testDir);
|
||||
await initCommand.execute(testDir);
|
||||
|
||||
const secondRunArgs = mockPrompt.mock.calls[1][0];
|
||||
const preselected = secondRunArgs.initialSelected ?? [];
|
||||
expect(preselected).toContain('kilocode');
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
|
||||
@@ -192,6 +192,34 @@ Old body
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should refresh existing Kilo Code workflows', async () => {
|
||||
const kilocodePath = path.join(
|
||||
testDir,
|
||||
'.kilocode/workflows/openspec-apply.md'
|
||||
);
|
||||
await fs.mkdir(path.dirname(kilocodePath), { recursive: true });
|
||||
const initialContent = `<!-- OPENSPEC:START -->
|
||||
Old body
|
||||
<!-- OPENSPEC:END -->`;
|
||||
await fs.writeFile(kilocodePath, initialContent);
|
||||
|
||||
const consoleSpy = vi.spyOn(console, 'log');
|
||||
|
||||
await updateCommand.execute(testDir);
|
||||
|
||||
const updated = await fs.readFile(kilocodePath, 'utf-8');
|
||||
expect(updated).toContain('Work through tasks sequentially');
|
||||
expect(updated).not.toContain('Old body');
|
||||
expect(updated.startsWith('<!-- OPENSPEC:START -->')).toBe(true);
|
||||
|
||||
const [logMessage] = consoleSpy.mock.calls[0];
|
||||
expect(logMessage).toContain(
|
||||
'Updated slash commands: .kilocode/workflows/openspec-apply.md'
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle no AI tool files present', async () => {
|
||||
// Execute update command with no AI tool files
|
||||
const consoleSpy = vi.spyOn(console, 'log');
|
||||
|
||||
Reference in New Issue
Block a user