Compare commits

...
Author SHA1 Message Date
Tabish Bidiwale 11b934d472 feat: Add Kilo Code workflow support
Implements Kilo Code integration with the following features:
- Added Kilo Code as a selectable AI tool in `openspec init`
- Created KiloCodeSlashCommandConfigurator to generate workflow files
- Generates `.kilocode/workflows/openspec-{proposal,apply,archive}.md`
- Added update support to refresh existing Kilo Code workflows
- Updated README with Kilo Code integration details
- Added comprehensive test coverage for init and update commands
- Updated CHANGELOG with new feature

All tasks from the change proposal are complete.
2025-10-01 18:54:30 +10:00
10 changed files with 120 additions and 12 deletions
+6
View File
@@ -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
+3
View File
@@ -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.
+1
View File
@@ -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' }
];
+21
View File
@@ -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;
}
}
+3
View File
@@ -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.`;
+2 -2
View File
@@ -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;
+46
View File
@@ -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', () => {
+28
View File
@@ -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');