Skip to content
Draft
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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
# 9-cell matrix: 4 marketplace (claude/cursor/copilot/codex) + 5 flat
# (+opencode, which is flat-only). Mirrors the CLI golden snapshot matrix.
# 10-cell matrix: 4 marketplace (claude/cursor/copilot/codex) + 6 flat
# (+opencode and kilo, which are flat-only). Mirrors the CLI golden snapshot matrix.
matrix:
include:
- { tool: claude, mode: marketplace, flag: "" }
Expand All @@ -158,6 +158,7 @@ jobs:
- { tool: copilot, mode: flat, flag: "--flat" }
- { tool: codex, mode: flat, flag: "--flat" }
- { tool: opencode, mode: flat, flag: "--flat" }
- { tool: kilo, mode: flat, flag: "--flat" }
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Why not just write your own commands? → [FAQ](docs/FAQ.md#-why-aidd-instead-of

## ✅ Prerequisites

- **An AI coding tool** — Claude Code (native), or Cursor / Copilot / Codex / OpenCode (see [Compatibility](#-compatibility)).
- **An AI coding tool** — Claude Code (native), or Cursor / Copilot / Codex / OpenCode / Kilo Code (see [Compatibility](#-compatibility)).
- **[Node](https://nodejs.org)** on your `PATH` — for the plugin that ships hooks ([what they do](docs/ARCHITECTURE.md#-bundled-hooks)).

## 🔌 Compatibility
Expand All @@ -49,6 +49,7 @@ Why not just write your own commands? → [FAQ](docs/FAQ.md#-why-aidd-instead-of
| **GitHub Copilot** | ✅ Supported | Marketplace · Flat |
| **Codex** | ✅ Supported | Marketplace · Flat |
| **OpenCode** | ✅ Supported | Flat |
| **Kilo Code** | ✅ Supported | Flat |
| **Gemini · Mistral** | 🚧 In progress | — |

<sub>**Marketplace** = installed and updated through your tool's plugin manager. **Flat** = files copied directly into your project, no plugin manager involved. Install steps per tool → [Other tools](#other-tools).</sub>
Expand Down Expand Up @@ -176,6 +177,16 @@ codex plugin add aidd-context@aidd-framework # per plugin

</details>

<details>
<summary><strong>Kilo Code</strong> — Flat only</summary>

1. Unzip the `kilo-flat` archive, then copy its contents into your project root → `.kilo/` and `kilo.json`.
2. Start a new Kilo session, or run `/reload` in an existing session.

[Docs](https://kilo.ai/docs/customize/skills)

</details>

## 🚀 Quick start

Three ways in — pick one:
Expand Down
13 changes: 9 additions & 4 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The **AIDD CLI** (`@ai-driven-dev/cli`) installs AI tool runtime configs, IDE integrations, and plugins from the [AIDD marketplace](https://github.com/ai-driven-dev/framework) across AI coding assistants. Runtime configs are bundled in the CLI binary; memory and context files are provided by the `aidd-context` plugin, not the binary. Plugins are fetched from the marketplace on demand. Every installed file is hash-tracked in a manifest for drift detection.

**Supported tools:** Claude Code · Cursor · GitHub Copilot · OpenCode · Codex · VS Code (IDE integration)
**Supported tools:** Claude Code · Cursor · GitHub Copilot · OpenCode · Kilo Code · Codex · VS Code (IDE integration)

---

Expand Down Expand Up @@ -405,6 +405,7 @@ Marketplace registration and plugin enable state are written to per-tool setting
| GitHub Copilot | `.github/copilot/settings.json` |
| Codex | `.codex/config.json` |
| OpenCode | `opencode.json` (project root) |
| Kilo Code | `kilo.json` (project root) |

> **GitHub Copilot — workspace recommendations only.** Per [VS Code docs](https://code.visualstudio.com/docs/copilot/customization/agent-plugins), `.github/copilot/settings.json` registers marketplaces as **team recommendations**, not auto-activated. On first chat in the workspace VS Code shows a notification — the user must accept it (or filter Extensions by `@agentPlugins @recommended` and enable manually) before plugins load. To skip the per-project click, add the marketplace to the user-level setting `chat.plugins.marketplaces` (application-scoped, not writable from workspace). See [End-to-end: distribute a framework to Copilot](#end-to-end-distribute-a-framework-to-copilot-marketplace) for the full flow.

Expand All @@ -423,7 +424,7 @@ aidd framework build \
| Flag | Required | Description |
|---|---|---|
| `--source` | yes | Path to a framework root with `plugins/<name>/.claude-plugin/plugin.json` entries |
| `--target` | yes | `claude`, `cursor`, `copilot`, `codex`, or `opencode` |
| `--target` | yes | `claude`, `cursor`, `copilot`, `codex`, `opencode`, or `kilo` |
| `--out` | yes | Output directory. Marketplace mode: dist root (auto-wiped + recreated). Flat mode: the project root to materialize into |
| `--flat` | no | Materialize directly into a project workspace, bypassing the marketplace layer |
| `--force` | no | Overwrite existing files at canonical paths. **Flat mode only** (rejected without `--flat`) |
Expand All @@ -435,7 +436,7 @@ aidd framework build \

#### Per-tool / per-mode matrix

`opencode` is **flat-only** (no native marketplace). The other four support both modes.
`opencode` and `kilo` are **flat-only** (no native marketplace). The other four support both modes.

| Target | Marketplace layout (`<out>/`) | Plugin-root token | Flat layout (`<project>/`) |
|---|---|---|---|
Expand All @@ -444,6 +445,7 @@ aidd framework build \
| `copilot` | `.plugin/marketplace.json` · `plugins/<n>/.plugin/plugin.json` (OpenPlugin spec) · `agents/*.md` | `${PLUGIN_ROOT}` | `.github/` (+ `.vscode/`) |
| `codex` | `.claude-plugin/marketplace.json` · `plugins/<n>/.codex-plugin/plugin.json` · `codex-agents/*.toml` | `${PLUGIN_ROOT}` | `.codex/` |
| `opencode` | — (flat-only) | — | `.opencode/` (+ `opencode.json` for MCP) |
| `kilo` | — (flat-only) | — | `.kilo/` (+ `kilo.json` for MCP) |

Copilot uses the [OpenPlugin spec](https://github.com/vercel/open-plugin-spec) (`.plugin/plugin.json`, `${PLUGIN_ROOT}`) — the only layout where Copilot's editor + CLI resolve the plugin-root token at runtime. Codex requires the manifest `skills` field as a **string** (`"./skills"`), and project subagents (`.codex/agents/*.toml`) load only when the project is **trusted**.

Expand Down Expand Up @@ -476,7 +478,7 @@ To skip the per-project notification, add the dist path to the user-level `chat.

The CLI cannot write this setting programmatically (VS Code enforces application scope on it).

#### Flat materialization (e.g. opencode)
#### Flat materialization (e.g. OpenCode or Kilo Code)

```bash
# Materialize the framework straight into a project workspace
Expand All @@ -487,13 +489,16 @@ aidd framework build --source ./framework --target opencode --out ./my-project -

Flat mode writes directly under the project's tool directory — no `aidd marketplace add` / `aidd plugin install` step. opencode hooks are skipped (its runtime is JS modules, not declarative `hooks.json`).

For Kilo Code, replace `opencode` with `kilo`; the build writes `.kilo/` and `kilo.json`.

#### Build every target for a release

```bash
for t in claude cursor copilot codex; do
aidd framework build --source ./framework --target "$t" --out "./dist/aidd-framework-$t"
done
aidd framework build --source ./framework --target opencode --out ./dist/aidd-framework-opencode-flat --flat
aidd framework build --source ./framework --target kilo --out ./dist/aidd-framework-kilo-flat --flat
```

### Manifest schema upgrades
Expand Down
4 changes: 4 additions & 0 deletions cli/assets/configs/kilo/kilo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://app.kilo.ai/config.json",
"instructions": [".kilo/rules/**/*.md"]
}
1 change: 1 addition & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"github-copilot",
"codex",
"opencode",
"kilo-code",
"developer-tools",
"ai-coding-assistant"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/src/application/commands/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function assertAiToolId(toolId: string): asserts toolId is AiToolId {
export function registerAiCommand(program: Command): void {
const ai = program
.command("ai")
.description("Manage AI tools (claude, cursor, copilot, codex, opencode)");
.description("Manage AI tools (claude, cursor, copilot, codex, opencode, kilo)");

ai.action(async () => {
if (!process.stdout.isTTY) {
Expand Down
5 changes: 4 additions & 1 deletion cli/src/application/commands/framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export function registerFrameworkCommand(program: Command): void {
"Build a Claude-format framework into a target-native plugin marketplace tree or project workspace"
)
.requiredOption("--source <path>", "Path to the source framework directory")
.requiredOption("--target <target>", "Build target (claude, cursor, copilot, codex, opencode)")
.requiredOption(
"--target <target>",
"Build target (claude, cursor, copilot, codex, opencode, kilo)"
)
.requiredOption("--out <dir>", "Output directory (marketplace dist or project root)")
.option("--flat", "Materialize directly into project workspace, bypass marketplace")
.option("--force", "Overwrite existing files at canonical paths (flat mode only)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -818,3 +818,78 @@ export function buildOpencodeFlatContract(): ToolBuildContract {
},
};
}

// ── Kilo flat contract ────────────────────────────────────────────────────────

function kiloFlatAgentPath(plugin: string, rel: string): string {
return genericFlatAgentPath(".kilo/agents/", plugin, rel.replace(/^agents\//, ""), ".md");
}

function kiloFlatSkillPath(plugin: string, rel: string): string {
return genericFlatSkillPath(".kilo/skills/", plugin, rel.replace(/^skills\//, ""));
}

function kiloFlatResolveTarget(plugin: string, rel: string): string {
if (rel.startsWith("agents/")) return kiloFlatAgentPath(plugin, rel);
if (rel.startsWith("skills/")) return kiloFlatSkillPath(plugin, rel);
return rel;
}

function transformKiloFlatAgent(content: string, plugin: string, outName: string): string {
const { frontmatter, body } = parseFrontmatter(content);
const flatRelPath = kiloFlatAgentPath(plugin, `agents/${outName}`);
const rewrittenBody = rewriteRelativeLinks(body, {
currentFilePluginRelative: flatRelPath,
resolveTargetPath: (rel) => kiloFlatResolveTarget(plugin, rel),
});
const prefixedName = `${plugin}-${outName.replace(/\.md$/, "")}`;
return serializeFrontmatter(
{ ...frontmatter, name: prefixedName, mode: "subagent" },
rewrittenBody
);
}

async function resolveKiloJsonPath(outDir: string, fs: FsType): Promise<string> {
const jsoncExists = await fs.fileExists(`${outDir}/kilo.jsonc`);
if (jsoncExists) return `${outDir}/kilo.jsonc`;
return `${outDir}/kilo.json`;
}

export function buildKiloFlatContract(): ToolBuildContract {
return {
manifestDir: null,
marketplaceRelative: null,
manifestFileRelative: null,
synthesizeManifest: null,
manifestSchemaName: null,
artifacts: {
skills: {
supported: true,
source: { kind: "fullTree", srcDir: "skills" },
path: kiloFlatSkillPath,
rewriteSkillName: true,
},
agents: {
supported: true,
source: { kind: "filteredTree", srcDir: "agents", inputExt: ".md" },
path: kiloFlatAgentPath,
transform: transformKiloFlatAgent,
},
mcp: { supported: false },
hooks: { supported: false },
rules: { supported: false },
commands: { supported: false },
},
buildMarketplaceCatalog: null,
buildMarketplaceEntry: null,
emitConfigArtifact: async (builtPlugins, outDir, sourceDir, fs, _validator, assetProvider) => {
const configPath = await resolveKiloJsonPath(outDir, fs);
const existing = (await fs.fileExists(configPath)) ? await fs.readFile(configPath) : null;
const incoming = await collectOpencodeMcp(builtPlugins, sourceDir, fs);
const baseAsset = assetProvider.loadConfigAsset("kilo", "kilo.json");
const base = typeof baseAsset === "string" ? baseAsset : JSON.stringify(baseAsset);
await fs.writeFile(configPath, buildOpencodeFlatConfig(base, existing, incoming));
return 1;
},
};
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { join } from "node:path";
import { SettingsCapability } from "../../../domain/capabilities/settings-capability.js";
import { KiloDualConfigError } from "../../../domain/errors.js";
import { InstallationFile } from "../../../domain/models/file.js";
import type { Manifest } from "../../../domain/models/manifest.js";
import { extractMergeEntries, type MergeFileEntry } from "../../../domain/models/merge.js";
Expand Down Expand Up @@ -77,7 +78,13 @@ export class InstallRuntimeConfigUseCase {
const toolConfig = getToolConfig(options.toolId);
if (!isAiTool(toolConfig) || !toolConfig.configOutputPaths) return [];
const files: InstallationFile[] = [];
for (const [fileName, outputPath] of Object.entries(toolConfig.configOutputPaths)) {
for (const [fileName, configuredOutputPath] of Object.entries(toolConfig.configOutputPaths)) {
const outputPath = await this.resolveConfigOutputPath(
options.toolId,
fileName,
configuredOutputPath,
options.projectRoot
);
const asset = this.assets.loadConfigAsset(options.toolId, fileName);
const content = typeof asset === "string" ? asset : JSON.stringify(asset, null, 2);
if (await this.isUserOwned(outputPath, options)) continue;
Expand All @@ -88,6 +95,21 @@ export class InstallRuntimeConfigUseCase {
return files;
}

private async resolveConfigOutputPath(
toolId: AiToolId,
fileName: string,
configuredOutputPath: string,
projectRoot: string
): Promise<string> {
if (toolId !== "kilo" || fileName !== "kilo.json") return configuredOutputPath;
const jsonPath = join(projectRoot, "kilo.json");
const jsoncPath = join(projectRoot, "kilo.jsonc");
const jsonExists = await this.fs.fileExists(jsonPath);
const jsoncExists = await this.fs.fileExists(jsoncPath);
if (jsonExists && jsoncExists) throw new KiloDualConfigError();
return jsoncExists ? "kilo.jsonc" : configuredOutputPath;
}

private buildStaticSettingsFiles(options: InstallRuntimeConfigOptions): InstallationFile[] {
const toolConfig = getToolConfig(options.toolId);
if (!isAiTool(toolConfig)) return [];
Expand Down
Loading