mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
fix(cli): stop loading .opencode config directories
This commit is contained in:
@@ -2,4 +2,4 @@
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Prefer project `.kilo` configuration over legacy `.kilocode` and `.opencode` directories when entries conflict.
|
||||
Stop loading `.opencode` config directories and use `.kilo` instead, while retaining `.kilocode` as a legacy fallback.
|
||||
|
||||
@@ -58,7 +58,7 @@ Config files live in the same locations as the rest of your CLI configuration
|
||||
Drop TypeScript or JavaScript files into a `plugin/` or `plugins/` folder inside any config directory:
|
||||
|
||||
- Global: `~/.config/kilo/plugin/`
|
||||
- Project: `.kilo/plugin/`, `.kilocode/plugin/`, or `.opencode/plugin/`
|
||||
- Project: `.kilo/plugin/` or legacy `.kilocode/plugin/`
|
||||
|
||||
Every `.ts` or `.js` file in those directories is auto-registered at startup — no need to list them in the config file.
|
||||
|
||||
@@ -86,7 +86,7 @@ kilo plugin my-plugin --global
|
||||
kilo plugin my-plugin --force
|
||||
```
|
||||
|
||||
The command resolves the package, reads its `package.json` for plugin entrypoints, and writes the entry into the appropriate config file (currently `.opencode/opencode.jsonc` / `.opencode/tui.jsonc` for local installs, or `~/.config/kilo/opencode.jsonc` / `~/.config/kilo/tui.jsonc` for `--global`) while preserving JSONC comments.
|
||||
The command resolves the package, reads its `package.json` for plugin entrypoints, and writes the entry into the appropriate config file (`.kilo/opencode.jsonc` / `.kilo/tui.jsonc` for local installs, or `~/.config/kilo/opencode.jsonc` / `~/.config/kilo/tui.jsonc` for `--global`) while preserving JSONC comments.
|
||||
|
||||
### How plugins are installed
|
||||
|
||||
@@ -446,7 +446,7 @@ If a custom tool uses the same name as a built-in tool, **the custom tool wins**
|
||||
|
||||
### Alternative: standalone tool files
|
||||
|
||||
For tools that don't need the full plugin context, drop them in a `tool/` or `tools/` folder inside any config directory — for example `.kilo/tool/database.ts` or `~/.config/kilo/tool/database.ts`. The filename becomes the tool name, and each file exports a `tool()` definition directly. The layout is identical to the [OpenCode custom tools guide](https://opencode.ai/docs/custom-tools); substitute `.kilo/` (or `.kilocode/` / `.opencode/`) for `.opencode/`.
|
||||
For tools that don't need the full plugin context, drop them in a `tool/` or `tools/` folder inside any config directory — for example `.kilo/tool/database.ts` or `~/.config/kilo/tool/database.ts`. The filename becomes the tool name, and each file exports a `tool()` definition directly. The layout is identical to the [OpenCode custom tools guide](https://opencode.ai/docs/custom-tools); use `.kilo/`, or legacy `.kilocode/`, instead of `.opencode/`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -335,8 +335,8 @@ The Kilo CLI is a fork of [OpenCode](https://opencode.ai) and supports the same
|
||||
|
||||
| Scope | Path |
|
||||
|---|---|
|
||||
| **Global** | `~/.config/kilo/opencode.json` or `opencode.jsonc` (Windows: config dir may vary; same filenames) |
|
||||
| **Project** | `./opencode.json` or `./.opencode/` in project root |
|
||||
| **Global** | `~/.config/kilo/kilo.json[c]` or legacy `opencode.json[c]` (Windows config dir may vary) |
|
||||
| **Project** | `./kilo.json[c]`, legacy `./opencode.json[c]`, or config inside `./.kilo/` (legacy `./.kilocode/` is also read) |
|
||||
|
||||
Project-level configuration takes precedence over global settings.
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ Later sources override earlier values during instance config load:
|
||||
| 11 | macOS managed preferences |
|
||||
| 12 | Runtime flag-derived permission, tool, compaction, and plugin behavior |
|
||||
|
||||
Global config files load from `${Global.Path.config}`. Project updates prefer existing config files found in ancestor `.kilo`, `.kilocode`, or `.opencode` directories, then existing project root config files, then create `.kilo/kilo.json`. Global indexing settings can carry provider and storage defaults, but global `indexing.enabled` is stripped so project enablement remains local in effective instance config.
|
||||
Global config files load from `${Global.Path.config}`. Project updates prefer existing config files found in ancestor `.kilo` or legacy `.kilocode` directories, then existing project root config files, then create `.kilo/kilo.json`. Global indexing settings can carry provider and storage defaults, but global `indexing.enabled` is stripped so project enablement remains local in effective instance config.
|
||||
|
||||
Signed-in organization modes become normal agent configuration during load. They override migrated legacy modes and remain overridable by later config sources in table.
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ Create `.md` files in any of these directories:
|
||||
```
|
||||
.kilo/agents/my-agent.md
|
||||
.kilo/agent/my-agent.md
|
||||
.opencode/agents/my-agent.md
|
||||
.kilocode/agents/my-agent.md
|
||||
```
|
||||
|
||||
For global agents, place files in your global config directory:
|
||||
@@ -191,7 +191,7 @@ Agent configurations merge from lowest to highest priority:
|
||||
1. Built-in (native) agent defaults
|
||||
2. Global config (`~/.config/kilo/kilo.jsonc`)
|
||||
3. Project config (`kilo.jsonc` at project root)
|
||||
4. `.kilo/` / `.opencode/` directory configs and agent `.md` files
|
||||
4. `.kilo/` / legacy `.kilocode/` directory configs and agent `.md` files
|
||||
5. Environment variable overrides (`KILO_CONFIG_CONTENT`)
|
||||
|
||||
When the same agent name appears at multiple levels, properties are merged (not replaced wholesale), so you can override just a model or temperature without redefining the entire agent.
|
||||
@@ -312,7 +312,7 @@ Create `.md` files in any of these directories:
|
||||
```
|
||||
.kilo/agents/my-agent.md
|
||||
.kilo/agent/my-agent.md
|
||||
.opencode/agents/my-agent.md
|
||||
.kilocode/agents/my-agent.md
|
||||
```
|
||||
|
||||
For global agents, place files in your global config directory:
|
||||
@@ -427,7 +427,7 @@ Agent configurations merge from lowest to highest priority:
|
||||
1. Built-in (native) agent defaults
|
||||
2. Global config (`~/.config/kilo/kilo.jsonc`)
|
||||
3. Project config (`kilo.jsonc` at project root)
|
||||
4. `.kilo/` / `.opencode/` directory configs and agent `.md` files
|
||||
4. `.kilo/` / legacy `.kilocode/` directory configs and agent `.md` files
|
||||
5. Environment variable overrides (`KILO_CONFIG_CONTENT`)
|
||||
|
||||
When the same agent name appears at multiple levels, properties are merged (not replaced wholesale), so you can override just a model or temperature without redefining the entire agent.
|
||||
@@ -579,7 +579,7 @@ When importing modes, you can change the slug in the exported YAML file before i
|
||||
|
||||
Custom agents are defined as Markdown files with optional YAML frontmatter. You can place them in:
|
||||
|
||||
- **Project agents:** `.kilo/agents/*.md` (or `.opencode/agents/*.md`)
|
||||
- **Project agents:** `.kilo/agents/*.md` (or legacy `.kilocode/agents/*.md`)
|
||||
- **Global agents:** `~/.config/kilo/agents/*.md`
|
||||
|
||||
### Agent File Format
|
||||
@@ -1256,7 +1256,7 @@ customModes:
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **Agent not appearing:** Ensure the `.md` file is in a recognized directory (`.kilo/agents/`, `.kilo/agent/`, `.opencode/agents/`). Check that the `mode` property is `primary` or `all` if you expect it in the agent picker.
|
||||
- **Agent not appearing:** Ensure the `.md` file is in a recognized directory (`.kilo/agents/`, `.kilo/agent/`, `.kilocode/agents/`). Check that the `mode` property is `primary` or `all` if you expect it in the agent picker.
|
||||
- **Permission errors:** Permission rules are evaluated last-match-wins. If an agent can't use a tool you expect, check that an `allow` rule appears after any `deny` rules for that permission.
|
||||
- **YAML frontmatter parse errors:** Ensure the frontmatter block starts and ends with `---` on its own line. Validate that YAML keys match expected property names (e.g., `top_p` not `topP`).
|
||||
- **Agent overrides not working:** Config merges from global to project level. If a global config sets a property, your project config can override it, but both must use the same agent name.
|
||||
@@ -1273,7 +1273,7 @@ customModes:
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **Agent not appearing:** Ensure the `.md` file is in a recognized directory (`.kilo/agents/`, `.kilo/agent/`, `.opencode/agents/`). Check that the `mode` property is `primary` or `all` if you expect it in the agent picker.
|
||||
- **Agent not appearing:** Ensure the `.md` file is in a recognized directory (`.kilo/agents/`, `.kilo/agent/`, `.kilocode/agents/`). Check that the `mode` property is `primary` or `all` if you expect it in the agent picker.
|
||||
- **Permission errors:** Permission rules are evaluated last-match-wins. If an agent can't use a tool you expect, check that an `allow` rule appears after any `deny` rules for that permission.
|
||||
- **YAML frontmatter parse errors:** Ensure the frontmatter block starts and ends with `---` on its own line. Validate that YAML keys match expected property names (e.g., `top_p` not `topP`).
|
||||
- **Agent overrides not working:** Config merges from global to project level. If a global config sets a property, your project config can override it, but both must use the same agent name.
|
||||
|
||||
@@ -195,8 +195,8 @@ npm plugins can declare a version compatibility range in `package.json` using th
|
||||
- Alias: `kilo plug <module>`.
|
||||
- `-g` / `--global` writes into the global config dir.
|
||||
- Local installs resolve target dir inside `patchPluginConfig`.
|
||||
- For local scope, path is `<worktree>/.opencode` only when VCS is git and `worktree !== "/"`; otherwise `<directory>/.opencode`.
|
||||
- Root-worktree fallback (`worktree === "/"` uses `<directory>/.opencode`) is covered by regression tests.
|
||||
- For local scope, path is `<worktree>/.kilo` only when VCS is git and `worktree !== "/"`; otherwise `<directory>/.kilo`.
|
||||
- Root-worktree fallback (`worktree === "/"` uses `<directory>/.kilo`) is covered by regression tests.
|
||||
- `patchPluginConfig` applies all detected targets (`server` and/or `tui`) in one call.
|
||||
- `patchPluginConfig` returns structured result unions (`ok`, `code`, fields by error kind) instead of custom thrown errors.
|
||||
- `patchPluginConfig` serializes per-target config writes with `Flock.acquire(...)`.
|
||||
@@ -213,7 +213,7 @@ npm plugins can declare a version compatibility range in `package.json` using th
|
||||
- There is no uninstall, list, or update CLI command for external plugins.
|
||||
- Local file plugins are configured directly in `tui.json`.
|
||||
|
||||
When `plugin` entries exist in a writable `.opencode` dir or `KILO_CONFIG_DIR`, Kilo installs `@kilocode/plugin` into that dir and writes:
|
||||
When `plugin` entries exist in a writable `.kilo` or legacy `.kilocode` directory, or `KILO_CONFIG_DIR`, Kilo installs `@kilocode/plugin` into that directory and writes:
|
||||
|
||||
- `package.json`
|
||||
- `bun.lock`
|
||||
@@ -413,7 +413,7 @@ Theme install behavior:
|
||||
- If the theme name already exists, install is skipped unless plugin metadata state is `updated`.
|
||||
- On `updated`, host skips rewrite when tracked `mtime`/`size` is unchanged.
|
||||
- When a theme already exists and state is not `updated`, host can still persist theme metadata when destination already exists.
|
||||
- Local plugins persist installed themes under the local `.opencode/themes` area near the plugin config source.
|
||||
- Local plugins persist installed themes under `.kilo/themes`, or under `themes` beside a config source in `.kilo` or legacy `.kilocode`.
|
||||
- Global plugins persist installed themes under the global `themes` dir.
|
||||
- Invalid or unreadable theme files are ignored.
|
||||
|
||||
@@ -538,7 +538,4 @@ The plugin manager is exposed as a command with title `Plugins` and value `plugi
|
||||
|
||||
## Current in-repo examples
|
||||
|
||||
- Local smoke plugin: `.opencode/plugins/tui-smoke.tsx`
|
||||
- Local vim plugin: `.opencode/plugins/tui-vim.tsx`
|
||||
- Local smoke config: `.opencode/tui.json`
|
||||
- Local smoke theme: `.opencode/plugins/smoke-theme.json`
|
||||
The upstream `.opencode` smoke fixtures remain in the repository for OpenCode compatibility, but Kilo does not auto-load them. Kilo-local examples must live under `.kilo` or legacy `.kilocode`.
|
||||
|
||||
@@ -397,25 +397,26 @@ export const McpLogoutCommand = effectCmd({
|
||||
})
|
||||
|
||||
async function resolveConfigPath(baseDir: string, global = false) {
|
||||
// kilocode_change start - prefer kilo.json/.kilo over opencode.json/.opencode
|
||||
// Check for existing config files (prefer .jsonc over .json, check .kilo/ and .opencode/ subdirectory too)
|
||||
const candidates = [
|
||||
path.join(baseDir, "kilo.json"),
|
||||
// kilocode_change start - prefer supported Kilo config directories over root files
|
||||
const roots = [
|
||||
path.join(baseDir, "kilo.jsonc"),
|
||||
path.join(baseDir, "opencode.json"),
|
||||
path.join(baseDir, "kilo.json"),
|
||||
path.join(baseDir, "opencode.jsonc"),
|
||||
path.join(baseDir, "opencode.json"),
|
||||
]
|
||||
|
||||
if (!global) {
|
||||
candidates.push(
|
||||
path.join(baseDir, ".kilo", "kilo.json"),
|
||||
path.join(baseDir, ".kilo", "kilo.jsonc"),
|
||||
path.join(baseDir, ".kilo", "opencode.json"),
|
||||
path.join(baseDir, ".kilo", "opencode.jsonc"),
|
||||
path.join(baseDir, ".opencode", "opencode.json"),
|
||||
path.join(baseDir, ".opencode", "opencode.jsonc"),
|
||||
)
|
||||
}
|
||||
const candidates = global
|
||||
? roots
|
||||
: [
|
||||
path.join(baseDir, ".kilo", "kilo.jsonc"),
|
||||
path.join(baseDir, ".kilo", "kilo.json"),
|
||||
path.join(baseDir, ".kilo", "opencode.jsonc"),
|
||||
path.join(baseDir, ".kilo", "opencode.json"),
|
||||
path.join(baseDir, ".kilocode", "kilo.jsonc"),
|
||||
path.join(baseDir, ".kilocode", "kilo.json"),
|
||||
path.join(baseDir, ".kilocode", "opencode.jsonc"),
|
||||
path.join(baseDir, ".kilocode", "opencode.json"),
|
||||
...roots,
|
||||
]
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (await Filesystem.exists(candidate)) {
|
||||
|
||||
@@ -191,8 +191,10 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
|
||||
acc.plugin_origins = plugins
|
||||
})
|
||||
|
||||
// Every config dir we may read from: global config dir, any `.opencode`
|
||||
// kilocode_change start - discover canonical and legacy Kilo config directories
|
||||
// Every config dir we may read from: global config, .kilo and legacy .kilocode
|
||||
// folders between cwd and home, and KILO_CONFIG_DIR.
|
||||
// kilocode_change end
|
||||
const directories = yield* ConfigPaths.directories(ctx.directory)
|
||||
yield* Effect.promise(() => migrateTuiConfig({ directories, cwd: ctx.directory }))
|
||||
|
||||
@@ -220,18 +222,16 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
|
||||
yield* mergeFile(acc, file)
|
||||
}
|
||||
|
||||
// 4. `.opencode` directories (and KILO_CONFIG_DIR) discovered while
|
||||
// walking up the tree. Also returned below so callers can install plugin
|
||||
// dependencies from each location.
|
||||
// kilocode_change start - also load tui.json from .kilo/.kilocode
|
||||
// kilocode_change start - load tui.json from supported Kilo config directories
|
||||
// 4. `.kilo` and legacy `.kilocode` directories (and KILO_CONFIG_DIR)
|
||||
// discovered while walking up the tree. Also returned below so callers can
|
||||
// install plugin dependencies from each location.
|
||||
const dirs = unique(directories).filter(
|
||||
(dir) =>
|
||||
dir.endsWith(".kilo") || dir.endsWith(".kilocode") || dir.endsWith(".opencode") || dir === Flag.KILO_CONFIG_DIR,
|
||||
(dir) => dir.endsWith(".kilo") || dir.endsWith(".kilocode") || dir === Flag.KILO_CONFIG_DIR,
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
for (const dir of dirs) {
|
||||
// if (!dir.endsWith(".opencode") && dir !== Flag.KILO_CONFIG_DIR) continue // kilocode_change
|
||||
for (const file of ConfigPaths.fileInDirectory(dir, "tui")) {
|
||||
yield* mergeFile(acc, file)
|
||||
}
|
||||
|
||||
@@ -508,7 +508,7 @@ async function getCustomThemes() {
|
||||
Global.Path.config,
|
||||
...(await Array.fromAsync(
|
||||
Filesystem.up({
|
||||
targets: [".kilo", ".opencode"], // kilocode_change
|
||||
targets: [".kilocode", ".kilo"], // kilocode_change
|
||||
start: process.cwd(),
|
||||
}),
|
||||
)),
|
||||
|
||||
@@ -135,10 +135,12 @@ export function Tips(props: { api: TuiPluginApi; connected?: boolean }) {
|
||||
}
|
||||
const tip = createMemo(() => {
|
||||
if (props.connected === false) return NO_MODELS_TIP
|
||||
const tips = KILO_TIPS.flatMap((item) => { // kilocode_change
|
||||
// kilocode_change start
|
||||
const tips = KILO_TIPS.flatMap((item) => {
|
||||
const value = typeof item === "string" ? item : item(shortcuts)
|
||||
return value ? [value] : []
|
||||
})
|
||||
// kilocode_change end
|
||||
return tips[Math.floor(tipOffset * tips.length)] ?? NO_MODELS_TIP
|
||||
}, NO_MODELS_TIP)
|
||||
// Solid can expose a memo's initial value while a pure computation is pending.
|
||||
@@ -219,10 +221,10 @@ const TIPS: Tip[] = [
|
||||
"Override any keybind in {highlight}tui.json{/highlight} via the {highlight}keybinds{/highlight} section",
|
||||
"Set any keybind to {highlight}none{/highlight} to disable it completely",
|
||||
"Configure local or remote MCP servers in the {highlight}mcp{/highlight} config section",
|
||||
"Add {highlight}.md{/highlight} files to {highlight}.opencode/commands/{/highlight} to define reusable custom prompts",
|
||||
"Add {highlight}.md{/highlight} files to {highlight}.kilo/commands/{/highlight} to define reusable custom prompts", // kilocode_change
|
||||
"Use {highlight}$ARGUMENTS{/highlight}, {highlight}$1{/highlight}, {highlight}$2{/highlight} in custom commands for dynamic input",
|
||||
"Use backticks in commands to inject shell output (e.g., {highlight}`git status`{/highlight})",
|
||||
"Add {highlight}.md{/highlight} files to {highlight}.opencode/agents/{/highlight} for specialized AI personas",
|
||||
"Add {highlight}.md{/highlight} files to {highlight}.kilo/agents/{/highlight} for specialized AI personas", // kilocode_change
|
||||
"Configure per-agent permissions for {highlight}edit{/highlight}, {highlight}bash{/highlight}, and {highlight}webfetch{/highlight} tools",
|
||||
'Use patterns like {highlight}"git *": "allow"{/highlight} for granular bash permissions',
|
||||
'Set {highlight}"rm -rf *": "deny"{/highlight} to block destructive commands',
|
||||
@@ -231,9 +233,9 @@ const TIPS: Tip[] = [
|
||||
'Set {highlight}"formatter": false{/highlight} in config to disable formatters enabled by another config layer',
|
||||
"Define custom formatter commands with file extensions in config",
|
||||
'Set {highlight}"lsp": true{/highlight} in config to enable built-in LSP servers for code analysis',
|
||||
"Create {highlight}.ts{/highlight} files in {highlight}.opencode/tools/{/highlight} to define new LLM tools",
|
||||
"Create {highlight}.ts{/highlight} files in {highlight}.kilo/tools/{/highlight} to define new LLM tools", // kilocode_change
|
||||
"Tool definitions can invoke scripts written in Python, Go, etc",
|
||||
"Add {highlight}.ts{/highlight} files to {highlight}.opencode/plugins/{/highlight} for event hooks",
|
||||
"Add {highlight}.ts{/highlight} files to {highlight}.kilo/plugins/{/highlight} for event hooks", // kilocode_change
|
||||
"Use plugins to send OS notifications when sessions complete",
|
||||
"Create a plugin to prevent OpenCode from reading sensitive files",
|
||||
"Use {highlight}opencode run{/highlight} for non-interactive scripting",
|
||||
@@ -250,7 +252,7 @@ const TIPS: Tip[] = [
|
||||
"Comment {highlight}/opencode fix this{/highlight} on issues to auto-create PRs",
|
||||
"Comment {highlight}/oc{/highlight} on PR code lines for targeted code reviews",
|
||||
'Use {highlight}"theme": "system"{/highlight} to match your terminal\'s colors',
|
||||
"Create JSON theme files in {highlight}.opencode/themes/{/highlight} directory",
|
||||
"Create JSON theme files in {highlight}.kilo/themes/{/highlight} directory", // kilocode_change
|
||||
"Themes support dark/light variants for both modes",
|
||||
"Use numeric xterm color codes 0-255 in custom theme JSON",
|
||||
"Use {highlight}{env:VAR_NAME}{/highlight} syntax to reference environment variables in config",
|
||||
|
||||
@@ -254,10 +254,13 @@ function createThemeInstaller(
|
||||
const src = Filesystem.resolveFilePath(root, file)
|
||||
const name = path.basename(src, path.extname(src))
|
||||
const source_dir = path.dirname(meta.source)
|
||||
// kilocode_change start - install local themes into supported Kilo config directories
|
||||
const base = path.basename(source_dir)
|
||||
const local_dir =
|
||||
path.basename(source_dir) === ".opencode"
|
||||
base === ".kilo" || base === ".kilocode"
|
||||
? path.join(source_dir, "themes")
|
||||
: path.join(source_dir, ".opencode", "themes")
|
||||
: path.join(source_dir, ".kilo", "themes")
|
||||
// kilocode_change end
|
||||
const dest_dir = meta.scope === "local" ? local_dir : path.join(Global.Path.config, "themes")
|
||||
const dest = path.join(dest_dir, `${name}.json`)
|
||||
const stat = await Filesystem.statAsync(src)
|
||||
@@ -843,7 +846,7 @@ function defaultPluginOrigin(state: RuntimeState, spec: string): ConfigPlugin.Or
|
||||
return {
|
||||
spec,
|
||||
scope: "local",
|
||||
source: state.api.state.path.config || path.join(state.directory, ".opencode", "tui.json"),
|
||||
source: state.api.state.path.config || path.join(state.directory, ".kilo", "tui.json"), // kilocode_change
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -865,7 +865,7 @@ export const layer = Layer.effect(
|
||||
const directories = yield* ConfigPaths.directories(ctx.directory, ctx.worktree)
|
||||
const primary = Flag.KILO_DISABLE_PROJECT_CONFIG
|
||||
? []
|
||||
: yield* primaryPaths(ctx.directory, ctx.worktree, [".opencode", ".kilocode", ".kilo"])
|
||||
: yield* primaryPaths(ctx.directory, ctx.worktree, [".kilocode", ".kilo"])
|
||||
// Load primary fallbacks before active-worktree config, then track them as local.
|
||||
directories.splice(1, 0, ...primary)
|
||||
const primarySet = new Set(primary)
|
||||
@@ -934,7 +934,7 @@ export const layer = Layer.effect(
|
||||
result.agent = mergeDeep(result.agent ?? {}, yield* Effect.promise(() => ConfigAgent.load(dir, warnings)))
|
||||
result.agent = mergeDeep(result.agent ?? {}, yield* Effect.promise(() => ConfigAgent.loadMode(dir, warnings)))
|
||||
// kilocode_change end
|
||||
// Auto-discovered plugins under `.opencode/plugin(s)` are already local files, so ConfigPlugin.load
|
||||
// kilocode_change - Auto-discovered plugins under config directories are already local files, so ConfigPlugin.load
|
||||
// returns normalized Specs and we only need to attach origin metadata here.
|
||||
const list = yield* Effect.promise(() => ConfigPlugin.load(dir))
|
||||
yield* mergePluginOrigins(dir, list, scope) // kilocode_change
|
||||
|
||||
@@ -26,13 +26,13 @@ export const directories = Effect.fn("ConfigPaths.directories")(function* (direc
|
||||
Global.Path.config,
|
||||
...(!Flag.KILO_DISABLE_PROJECT_CONFIG
|
||||
? yield* afs.up({
|
||||
targets: [".opencode", ".kilocode", ".kilo"], // kilocode_change
|
||||
targets: [".kilocode", ".kilo"], // kilocode_change
|
||||
start: directory,
|
||||
stop: worktree,
|
||||
})
|
||||
: []),
|
||||
...(yield* afs.up({
|
||||
targets: [".opencode", ".kilocode", ".kilo"], // kilocode_change
|
||||
targets: [".kilocode", ".kilo"], // kilocode_change
|
||||
start: Global.Path.home,
|
||||
stop: Global.Path.home,
|
||||
})),
|
||||
|
||||
@@ -441,7 +441,7 @@ export const layer: Layer.Layer<Service, never, AppFileSystem.Service | ChildPro
|
||||
|
||||
const root: Node = { name: "", children: new Map() }
|
||||
for (const file of list) {
|
||||
if (file.includes(".kilo") || file.includes(".opencode")) continue // kilocode_change
|
||||
if (file.includes(".kilo") || file.includes(".kilocode")) continue // kilocode_change
|
||||
const parts = file.split(path.sep)
|
||||
if (parts.length < 2) continue
|
||||
let node = root
|
||||
|
||||
@@ -43,12 +43,9 @@ export namespace KilocodeConfig {
|
||||
/** All config file names in precedence order (kilo + opencode). */
|
||||
export const ALL_CONFIG_FILES = ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"] as const
|
||||
|
||||
/** Directory suffixes that Kilo recognizes in addition to .opencode. */
|
||||
/** Config directory suffixes in update-target preference order. */
|
||||
export const KILO_DIR_SUFFIXES = [".kilo", ".kilocode"] as const
|
||||
|
||||
/** All config directory suffixes Kilo can update, including upstream .opencode. */
|
||||
export const ALL_CONFIG_DIR_SUFFIXES = [".kilo", ".kilocode", ".opencode"] as const
|
||||
|
||||
/** Path patterns for resolving kilo agent names from file paths. */
|
||||
export const AGENT_PATTERNS = ["/.kilo/agent/", "/.kilo/agents/", "/.kilocode/agent/", "/.kilocode/agents/"] as const
|
||||
|
||||
@@ -73,7 +70,7 @@ export namespace KilocodeConfig {
|
||||
worktree?: string
|
||||
}) {
|
||||
const dirs = yield* input.fs
|
||||
.up({ targets: [...ALL_CONFIG_DIR_SUFFIXES], start: input.directory, stop: input.worktree })
|
||||
.up({ targets: [...KILO_DIR_SUFFIXES], start: input.directory, stop: input.worktree })
|
||||
.pipe(Effect.orDie)
|
||||
const roots = yield* input.fs
|
||||
.up({ targets: [...ALL_CONFIG_FILES], start: input.directory, stop: input.worktree })
|
||||
@@ -437,6 +434,6 @@ export namespace KilocodeConfig {
|
||||
|
||||
/** Check whether a directory path should be treated as a config directory (for loading config files). */
|
||||
export function isConfigDir(dir: string, flagDir?: string): boolean {
|
||||
return dir.endsWith(".kilo") || dir.endsWith(".kilocode") || dir.endsWith(".opencode") || dir === flagDir
|
||||
return dir.endsWith(".kilo") || dir.endsWith(".kilocode") || dir === flagDir
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export namespace KilocodeConfigOverlay {
|
||||
}
|
||||
|
||||
const files = ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"] as const
|
||||
const dirs = [".opencode", ".kilocode", ".kilo"] as const
|
||||
const dirs = [".kilocode", ".kilo"] as const
|
||||
|
||||
const fieldPaths = [
|
||||
["model"],
|
||||
@@ -182,12 +182,7 @@ export namespace KilocodeConfigOverlay {
|
||||
}
|
||||
|
||||
function globalDirs() {
|
||||
return [
|
||||
Global.Path.config,
|
||||
path.join(Global.Path.home, ".opencode"),
|
||||
path.join(Global.Path.home, ".kilocode"),
|
||||
path.join(Global.Path.home, ".kilo"),
|
||||
]
|
||||
return [Global.Path.config, path.join(Global.Path.home, ".kilocode"), path.join(Global.Path.home, ".kilo")]
|
||||
}
|
||||
|
||||
async function withAgents(input: Config.Info, dirs: string[]): Promise<Config.Info> {
|
||||
|
||||
@@ -56,7 +56,7 @@ export namespace KilocodeConfigSources {
|
||||
|
||||
type Pending = Omit<Source, "order">
|
||||
|
||||
const roots = [".opencode", ".kilocode", ".kilo"] as const
|
||||
const roots = [".kilocode", ".kilo"] as const
|
||||
const global = ["config.json", "kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc"] as const
|
||||
|
||||
export async function list(input: Input): Promise<Result> {
|
||||
|
||||
@@ -192,7 +192,7 @@ Kilocode skills are automatically discovered and made available in Opencode. Thi
|
||||
|
||||
## How It Works
|
||||
|
||||
Opencode scans additional directories for skills alongside its native `.opencode/skill/` locations. The `KilocodePaths.skillDirectories()` function provides these paths.
|
||||
Kilo scans canonical `.kilo/skill/` and `.kilo/skills/` directories alongside legacy `.kilocode/` equivalents. The `KilocodePaths.skillDirectories()` function provides these paths.
|
||||
|
||||
## Source Locations
|
||||
|
||||
@@ -249,16 +249,7 @@ Detailed instructions for the agent...
|
||||
|
||||
## Priority / Override Behavior
|
||||
|
||||
When the same skill name exists in multiple locations, **last one wins**:
|
||||
|
||||
1. `.claude/skills/` (lowest priority)
|
||||
2. `.kilocode/skills/` (walk-up)
|
||||
3. `~/.kilocode/skills/`
|
||||
4. VSCode extension storage
|
||||
5. `.opencode/skill/` (walk-up)
|
||||
6. `~/.opencode/skill/` (highest priority)
|
||||
|
||||
This means Opencode native skills take precedence over Kilocode skills with the same name.
|
||||
When the same skill name exists in multiple locations, **last one wins**. Canonical `.kilo/` skills take precedence over legacy `.kilocode/` skills at the same project level. Explicit external skill paths and compatibility directories are loaded according to the configured scan order.
|
||||
|
||||
## Mode-Specific Skills
|
||||
|
||||
@@ -286,8 +277,8 @@ Skills can be symlinked from a shared location:
|
||||
|
||||
```
|
||||
.agents/skills/shared-skill/ # Actual skill
|
||||
.kilocode/skills/shared-skill -> ... # Symlink
|
||||
.opencode/skill/shared-skill -> ... # Symlink
|
||||
.kilocode/skills/shared-skill -> ... # Legacy symlink
|
||||
.kilo/skill/shared-skill -> ... # Canonical symlink
|
||||
```
|
||||
|
||||
The scanner follows symlinks, so a skill installed once can be available to both Kilo VSCode and Opencode CLI.
|
||||
|
||||
@@ -70,7 +70,7 @@ The migrator reads rules from these locations:
|
||||
|
||||
- `AGENTS.md` in project root
|
||||
- `CLAUDE.md` in project root
|
||||
- `~/.opencode/AGENTS.md` (global)
|
||||
- `~/.config/kilo/AGENTS.md` (global)
|
||||
|
||||
## Not Migrated
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import { KilocodePaths } from "@/kilocode/paths"
|
||||
export namespace ConfigProtection {
|
||||
/**
|
||||
* Config directory prefixes (relative paths, forward-slash normalized).
|
||||
* Matches .kilo/, .kilocode/, .opencode/ at any depth within the project.
|
||||
* Matches .kilo/ and legacy .kilocode/ at any depth within the project.
|
||||
*/
|
||||
const CONFIG_DIRS = [".kilo/", ".kilocode/", ".opencode/"]
|
||||
const CONFIG_DIRS = [".kilo/", ".kilocode/"]
|
||||
|
||||
/**
|
||||
* Subdirectories under CONFIG_DIRS that are NOT config files (e.g. plan files).
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
All config lives in `kilo.json` (or `kilo.jsonc`). Precedence low-to-high: remote well-known, global (`~/.config/kilo/kilo.json`), env `KILO_CONFIG`, project `./kilo.json`, `.kilo/kilo.json`, `KILO_CONFIG_CONTENT`, managed (see Config File Locations). Deep-merged; later wins.
|
||||
|
||||
This also covers where Kilo looks for config files, commands, agents, and skills across project, global, and legacy paths such as `.kilo/`, `.kilocode/`, `.opencode/`, and `~/.config/kilo/`, plus Agent Manager setup/run scripts in the VS Code extension.
|
||||
This also covers where Kilo looks for config files, commands, agents, and skills across project, global, and legacy paths such as `.kilo/`, `.kilocode/`, and `~/.config/kilo/`, plus Agent Manager setup/run scripts in the VS Code extension.
|
||||
|
||||
## Commands (`.kilo/command/*.md`)
|
||||
|
||||
Markdown files with YAML frontmatter. The filename (minus `.md`) becomes the command name invoked via `/name`. Commands can live in `.kilo/`, `.kilocode/`, `.opencode/`, and global config roots, with both `command/` and `commands/` directory names supported. See Config File Locations for the full search order.
|
||||
Markdown files with YAML frontmatter. The filename (minus `.md`) becomes the command name invoked via `/name`. Commands can live in `.kilo/`, legacy `.kilocode/`, and global config roots, with both `command/` and `commands/` directory names supported. See Config File Locations for the full search order.
|
||||
|
||||
```yaml
|
||||
---
|
||||
@@ -29,9 +29,8 @@ When asked where `/name` lives, do not search only the repo root. Search these r
|
||||
1. `~/.config/kilo/`
|
||||
2. `~/.kilo/`
|
||||
3. `~/.kilocode/`
|
||||
4. `~/.opencode/`
|
||||
5. The `KILO_CONFIG_DIR` directory (if the env var is set)
|
||||
6. project `.kilo/`, `.kilocode/`, and `.opencode/` directories from the current working directory up to the worktree root
|
||||
4. The `KILO_CONFIG_DIR` directory (if the env var is set)
|
||||
5. project `.kilo/` and `.kilocode/` directories from the current working directory up to the worktree root
|
||||
|
||||
Use exact patterns first:
|
||||
|
||||
@@ -42,7 +41,7 @@ If found, return the full path. If not found in those roots, explain that the co
|
||||
|
||||
## Agents (`.kilo/agent/*.md`)
|
||||
|
||||
Also loaded from `.kilocode/` and `.opencode/` directories (legacy), and plural `agents/` variants.
|
||||
Also loaded from legacy `.kilocode/` directories and plural `agents/` variants.
|
||||
|
||||
```yaml
|
||||
---
|
||||
@@ -257,7 +256,7 @@ Additional skill directories and remote URLs:
|
||||
}
|
||||
```
|
||||
|
||||
Skills are markdown files at `skills/<name>/SKILL.md` (or `skill/<name>/SKILL.md`) with `name` and `description` in frontmatter. Discovered inside `.kilo/`, `.kilocode/`, and `.opencode/` directories.
|
||||
Skills are markdown files at `skills/<name>/SKILL.md` (or `skill/<name>/SKILL.md`) with `name` and `description` in frontmatter. Discovered inside `.kilo/` and legacy `.kilocode/` directories.
|
||||
|
||||
## Other Top-Level Fields
|
||||
|
||||
@@ -340,14 +339,14 @@ Notification settings are managed through `kilo console` under **Settings > CLI
|
||||
| Global | `~/.config/kilo/kilo.json`, `~/.config/kilo/kilo.jsonc`, `~/.config/kilo/opencode.json` (legacy), `~/.config/kilo/opencode.jsonc` (legacy), `~/.config/kilo/config.json` (legacy) |
|
||||
| Managed | Linux: `/etc/kilo/`, macOS: `/Library/Application Support/kilo/`, Windows: `%ProgramData%\kilo\` — loads `kilo.json`, `kilo.jsonc`, `opencode.json`, `opencode.jsonc` (enterprise, highest priority) |
|
||||
|
||||
Each config directory (`.kilo/`, `.kilocode/`, `.opencode/`) can also contain `kilo.json`, `kilo.jsonc`, `opencode.json`, or `opencode.jsonc`.
|
||||
Each config directory (`.kilo/` and legacy `.kilocode/`) can also contain `kilo.json`, `kilo.jsonc`, `opencode.json`, or `opencode.jsonc`.
|
||||
|
||||
### Config directories
|
||||
|
||||
Three directory names are scanned: `.kilo` (modern), `.kilocode` (legacy), `.opencode` (legacy). All three are checked at each level:
|
||||
Two directory names are scanned: `.kilo` (canonical) and `.kilocode` (legacy fallback). Both are checked at each level, and `.kilo` wins when both define the same entry. `.opencode` directories are not loaded.
|
||||
|
||||
- **Project**: walks up from CWD to the git worktree root, checking for all three at each directory level
|
||||
- **Home**: `~/.kilo/`, `~/.kilocode/`, `~/.opencode/`
|
||||
- **Project**: walks up from CWD to the git worktree root, checking both directories at each level
|
||||
- **Home**: `~/.kilo/` and `~/.kilocode/`
|
||||
- **XDG global**: `~/.config/kilo/` (always loaded, lowest file-based precedence)
|
||||
|
||||
### Commands, agents, modes, plugins
|
||||
@@ -361,7 +360,7 @@ Glob patterns run inside every discovered config directory (including legacy):
|
||||
| Mode | `{mode,modes}/*.md` |
|
||||
| Plugin | `{plugin,plugins}/*.{ts,js}` |
|
||||
|
||||
Example: `~/.config/kilo/command/*.md` (modern global), `~/.kilocode/command/*.md` (legacy global), `.opencode/commands/*.md` (legacy project) all load commands.
|
||||
Example: `~/.config/kilo/command/*.md` (global), `~/.kilocode/command/*.md` (legacy home), and `.kilo/commands/*.md` (project) all load commands.
|
||||
|
||||
### Skills and instructions
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export namespace KilocodeTuiConfig {
|
||||
export type Editable = Omit<Patch, "keybinds"> & { keybinds?: Record<string, string> }
|
||||
|
||||
const files = ["tui.jsonc", "tui.json"] as const
|
||||
const dirs = [".kilo", ".kilocode", ".opencode"] as const
|
||||
const dirs = [".kilo", ".kilocode"] as const
|
||||
|
||||
export async function get(input: { directory: string }) {
|
||||
const cfg = await Effect.runPromise(
|
||||
|
||||
@@ -334,7 +334,7 @@ function patchDir(input: PatchInput) {
|
||||
if (input.global) return input.config ?? Global.Path.config
|
||||
const git = input.vcs === "git" && input.worktree !== "/"
|
||||
const root = git ? input.worktree : input.directory
|
||||
return path.join(root, ".opencode")
|
||||
return path.join(root, ".kilo") // kilocode_change
|
||||
}
|
||||
|
||||
function patchName(kind: Kind): "opencode" | "tui" {
|
||||
|
||||
@@ -594,7 +594,7 @@ it.instance(
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const skillDir = path.join(test.directory, ".opencode", "skill", "perm-skill")
|
||||
const skillDir = path.join(test.directory, ".kilo", "skill", "perm-skill") // kilocode_change
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(
|
||||
path.join(skillDir, "SKILL.md"),
|
||||
|
||||
@@ -168,9 +168,9 @@ async function load(): Promise<Data> {
|
||||
const invalidThemePath = path.join(dir, invalidThemeFile)
|
||||
const globalThemePath = path.join(dir, globalThemeFile)
|
||||
const preloadedThemePath = path.join(dir, preloadedThemeFile)
|
||||
const localDest = path.join(dir, ".opencode", "themes", localThemeFile)
|
||||
const localDest = path.join(dir, ".kilo", "themes", localThemeFile) // kilocode_change
|
||||
const globalDest = path.join(Global.Path.config, "themes", globalThemeFile)
|
||||
const preloadedDest = path.join(dir, ".opencode", "themes", preloadedThemeFile)
|
||||
const preloadedDest = path.join(dir, ".kilo", "themes", preloadedThemeFile) // kilocode_change
|
||||
const fnMarker = path.join(dir, "function-called.txt")
|
||||
const localMarker = path.join(dir, "local-called.json")
|
||||
const invalidMarker = path.join(dir, "invalid-called.json")
|
||||
@@ -549,7 +549,7 @@ export default {
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
const leaked_global_to_local = await fs
|
||||
.stat(path.join(tmp.path, ".opencode", "themes", tmp.extra.globalThemeFile))
|
||||
.stat(path.join(tmp.path, ".kilo", "themes", tmp.extra.globalThemeFile)) // kilocode_change
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
|
||||
@@ -1231,7 +1231,7 @@ test("updates installed theme when plugin metadata changes", async () => {
|
||||
const spec = pathToFileURL(pluginPath).href
|
||||
const themeFile = "theme-update.json"
|
||||
const themePath = path.join(dir, themeFile)
|
||||
const dest = path.join(dir, ".opencode", "themes", themeFile)
|
||||
const dest = path.join(dir, ".kilo", "themes", themeFile) // kilocode_change
|
||||
const themeName = themeFile.replace(/\.json$/, "")
|
||||
const configPath = path.join(dir, "tui.json")
|
||||
|
||||
|
||||
@@ -817,7 +817,7 @@ it.instance("agent markdown permission config preserves user key order", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "agent", "ordered.md"),
|
||||
path.join(test.directory, ".kilo", "agent", "ordered.md"), // kilocode_change
|
||||
`---
|
||||
permission:
|
||||
bash: allow
|
||||
|
||||
@@ -87,7 +87,7 @@ it.instance("keeps server and tui plugin merge semantics aligned", () =>
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* AppFileSystem.Service
|
||||
const test = yield* TestInstance
|
||||
const local = path.join(test.directory, ".opencode")
|
||||
const local = path.join(test.directory, ".kilo") // kilocode_change
|
||||
yield* fs.makeDirectory(local, { recursive: true })
|
||||
|
||||
yield* fs.writeJson(path.join(Global.Path.config, "kilo.json"), {
|
||||
@@ -129,7 +129,7 @@ it.instance("loads tui config with the same precedence order as server config pa
|
||||
yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), { theme: "global" })
|
||||
yield* fs.writeJson(path.join(test.directory, "tui.json"), { theme: "project" })
|
||||
yield* fs.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "tui.json"),
|
||||
path.join(test.directory, ".kilo", "tui.json"), // kilocode_change
|
||||
JSON.stringify({ theme: "local", diff_style: "stacked" }, null, 2),
|
||||
)
|
||||
|
||||
@@ -854,7 +854,7 @@ it.instance("silently skips malformed tui.json - load failures degrade to {}", (
|
||||
const fs = yield* AppFileSystem.Service
|
||||
const test = yield* TestInstance
|
||||
yield* fs.writeFileString(path.join(test.directory, "tui.json"), '{ "theme": "broken",')
|
||||
yield* fs.writeWithDirs(path.join(test.directory, ".opencode", "tui.json"), JSON.stringify({ theme: "fallback" }))
|
||||
yield* fs.writeWithDirs(path.join(test.directory, ".kilo", "tui.json"), JSON.stringify({ theme: "fallback" })) // kilocode_change
|
||||
|
||||
const config = yield* getTuiConfig(test.directory)
|
||||
expect(config.theme).toBe("fallback")
|
||||
@@ -868,7 +868,7 @@ it.instance("silently skips non-ENOENT read failures (e.g. tui.json is a directo
|
||||
const fs = yield* AppFileSystem.Service
|
||||
const test = yield* TestInstance
|
||||
yield* fs.makeDirectory(path.join(test.directory, "tui.json"), { recursive: true })
|
||||
yield* fs.writeWithDirs(path.join(test.directory, ".opencode", "tui.json"), JSON.stringify({ theme: "fallback" }))
|
||||
yield* fs.writeWithDirs(path.join(test.directory, ".kilo", "tui.json"), JSON.stringify({ theme: "fallback" })) // kilocode_change
|
||||
|
||||
const config = yield* getTuiConfig(test.directory)
|
||||
expect(config.theme).toBe("fallback")
|
||||
|
||||
@@ -414,7 +414,7 @@ describe("agent config", () => {
|
||||
})
|
||||
|
||||
describe("project config directory precedence", () => {
|
||||
test("prefers .kilo, then .kilocode, then .opencode", async () => {
|
||||
test("prefers .kilo over legacy .kilocode and ignores .opencode", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const entries = [
|
||||
{
|
||||
@@ -462,6 +462,7 @@ describe("project config directory precedence", () => {
|
||||
`---\ndescription: ${item.source} agent\nmode: subagent\n---\n${item.source} agent prompt`,
|
||||
)
|
||||
}
|
||||
await Filesystem.write(path.join(dir, "plugin", `${item.source}.ts`), "export default {}")
|
||||
}
|
||||
|
||||
await provideTestInstance({
|
||||
@@ -471,7 +472,7 @@ describe("project config directory precedence", () => {
|
||||
|
||||
expect(config.username).toBe("kilo")
|
||||
expect(config.model).toBe("test/kilocode")
|
||||
expect(config.small_model).toBe("test/opencode")
|
||||
expect(config.small_model).toBeUndefined()
|
||||
|
||||
expect(config.command?.shared).toMatchObject({
|
||||
description: "kilo command",
|
||||
@@ -481,10 +482,7 @@ describe("project config directory precedence", () => {
|
||||
description: "kilocode command",
|
||||
template: "kilocode command template",
|
||||
})
|
||||
expect(config.command?.["opencode-only"]).toMatchObject({
|
||||
description: "opencode command",
|
||||
template: "opencode command template",
|
||||
})
|
||||
expect(config.command?.["opencode-only"]).toBeUndefined()
|
||||
|
||||
expect(config.agent?.shared).toMatchObject({
|
||||
description: "kilo agent",
|
||||
@@ -494,10 +492,12 @@ describe("project config directory precedence", () => {
|
||||
description: "kilocode agent",
|
||||
prompt: "kilocode agent prompt",
|
||||
})
|
||||
expect(config.agent?.["opencode-only"]).toMatchObject({
|
||||
description: "opencode agent",
|
||||
prompt: "opencode agent prompt",
|
||||
})
|
||||
expect(config.agent?.["opencode-only"]).toBeUndefined()
|
||||
|
||||
const plugins = JSON.stringify(config.plugin)
|
||||
expect(plugins).toContain("kilocode.ts")
|
||||
expect(plugins).toContain("kilo.ts")
|
||||
expect(plugins).not.toContain("opencode.ts")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -540,7 +540,7 @@ describe("linked worktree config", () => {
|
||||
await Bun.write(path.join(directory, "placeholder"), "")
|
||||
await Bun.write(
|
||||
path.join(primary.path, "packages", ".opencode", "kilo.jsonc"),
|
||||
JSON.stringify({ snapshot: true, autoupdate: false, share: "auto" }),
|
||||
JSON.stringify({ snapshot: true, autoupdate: false, share: "auto", default_agent: "opencode-only" }),
|
||||
)
|
||||
await Bun.write(
|
||||
path.join(primary.path, "packages", ".kilocode", "kilo.jsonc"),
|
||||
@@ -554,6 +554,7 @@ describe("linked worktree config", () => {
|
||||
expect(config.snapshot).toBe(false)
|
||||
expect(config.autoupdate).toBe("notify")
|
||||
expect(config.share).toBe("manual")
|
||||
expect(config.default_agent).toBeUndefined()
|
||||
} finally {
|
||||
await $`git worktree remove --force ${worktree}`.cwd(primary.path).quiet().nothrow()
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ async function mark(dir: string) {
|
||||
}
|
||||
|
||||
export function preparePluginDependencies(dir: string) {
|
||||
return Promise.all([mark(path.join(dir, ".opencode")), mark(Global.Path.config)])
|
||||
return Promise.all([mark(path.join(dir, ".kilo")), mark(Global.Path.config)])
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ describe("config overlay routes", () => {
|
||||
expect(Object.hasOwn(patched, "prototype")).toBe(false)
|
||||
})
|
||||
|
||||
test("prefers .kilo, then .kilocode, then .opencode in project overlays", async () => {
|
||||
test("prefers .kilo over legacy .kilocode and ignores .opencode in project overlays", async () => {
|
||||
await using project = await tmpdir()
|
||||
const entries = [
|
||||
{
|
||||
@@ -120,6 +120,10 @@ describe("config overlay routes", () => {
|
||||
`---\ndescription: ${item.source} agent\nmode: subagent\n---\n${item.source} agent prompt`,
|
||||
)
|
||||
}
|
||||
await Filesystem.write(
|
||||
path.join(project.path, ".opencode", "agent", "opencode-only.md"),
|
||||
"---\ndescription: opencode-only agent\nmode: subagent\n---\nopencode-only agent prompt",
|
||||
)
|
||||
|
||||
const body = await KilocodeConfigOverlay.resolve({
|
||||
directory: project.path,
|
||||
@@ -131,11 +135,12 @@ describe("config overlay routes", () => {
|
||||
|
||||
expect(body.project.username).toBe("kilo")
|
||||
expect(body.project.model).toBe("test/kilocode")
|
||||
expect(body.project.small_model).toBe("test/opencode")
|
||||
expect(body.project.small_model).toBeUndefined()
|
||||
expect(body.project.agent?.shared).toMatchObject({
|
||||
description: "kilo agent",
|
||||
prompt: "kilo agent prompt",
|
||||
})
|
||||
expect(body.project.agent?.["opencode-only"]).toBeUndefined()
|
||||
expect(body.targets.project).toBe(path.join(project.path, ".kilo", "kilo.json"))
|
||||
})
|
||||
|
||||
|
||||
@@ -112,9 +112,9 @@ describe("config source routes", () => {
|
||||
const inline = body.sources.find((source) => source.source === "KILO_CONFIG_CONTENT")
|
||||
|
||||
expect(order(body, envFile)).toBeLessThan(order(body, projectFile))
|
||||
expect(order(body, projectFile)).toBeLessThan(order(body, opencodeFile))
|
||||
expect(order(body, opencodeFile)).toBeLessThan(order(body, kilocodeFile))
|
||||
expect(order(body, projectFile)).toBeLessThan(order(body, kilocodeFile))
|
||||
expect(order(body, kilocodeFile)).toBeLessThan(order(body, configFile))
|
||||
expect(body.sources.some((source) => source.path === opencodeFile)).toBe(false)
|
||||
expect(order(body, configFile)).toBeLessThan(order(body, extraFile))
|
||||
expect(inline?.order).toBeGreaterThan(order(body, extraFile))
|
||||
expect(inline?.order).toBeLessThan(order(body, managedFile))
|
||||
|
||||
@@ -245,10 +245,10 @@ export const kiloScenarios: Scenario[] = [
|
||||
Effect.gen(function* () {
|
||||
const location = yield* file(
|
||||
ctx,
|
||||
".opencode/skill/httpapi-remove/SKILL.md",
|
||||
".kilo/skill/httpapi-remove/SKILL.md",
|
||||
"---\nname: httpapi-remove\ndescription: HTTP API removal fixture.\n---\n# HTTP API remove\n",
|
||||
)
|
||||
const sentinel = yield* file(ctx, ".opencode/skill/httpapi-remove/KEEP.txt", "synthetic sentinel\n")
|
||||
const sentinel = yield* file(ctx, ".kilo/skill/httpapi-remove/KEEP.txt", "synthetic sentinel\n")
|
||||
return { location, sentinel }
|
||||
}),
|
||||
)
|
||||
@@ -273,9 +273,7 @@ export const kiloScenarios: Scenario[] = [
|
||||
http.protected
|
||||
.post("/kilocode/agent/remove", "kilocode.removeAgent")
|
||||
.mutating()
|
||||
.seeded((ctx) =>
|
||||
file(ctx, ".opencode/agent/httpapi-remove.md", "---\ndescription: HTTP API remove\n---\nRemove me.\n"),
|
||||
)
|
||||
.seeded((ctx) => file(ctx, ".kilo/agent/httpapi-remove.md", "---\ndescription: HTTP API remove\n---\nRemove me.\n"))
|
||||
.at((ctx) => ({ path: "/kilocode/agent/remove", headers: ctx.headers(), body: { name: "httpapi-remove" } }))
|
||||
.jsonEffect(200, (body, ctx) =>
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -43,6 +43,25 @@ describe("TUI config routes", () => {
|
||||
expect(body.plugin_origins).toBeUndefined()
|
||||
})
|
||||
|
||||
test("loads legacy .kilocode TUI config and ignores .opencode", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await fs.mkdir(path.join(dir, ".opencode"), { recursive: true })
|
||||
await fs.mkdir(path.join(dir, ".kilocode"), { recursive: true })
|
||||
await Bun.write(path.join(dir, ".opencode", "tui.json"), JSON.stringify({ theme: "dracula" }))
|
||||
await Bun.write(path.join(dir, ".kilocode", "tui.json"), JSON.stringify({ theme: "nord" }))
|
||||
},
|
||||
})
|
||||
|
||||
const response = await Server.Default().app.request("/tui/config", {
|
||||
headers: { "x-kilo-directory": tmp.path },
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
const body = (await response.json()) as { theme?: string }
|
||||
expect(body.theme).toBe("nord")
|
||||
})
|
||||
|
||||
test("lists valid TUI keybinds", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ describe("plugin.auth-override", () => {
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* TestInstance
|
||||
const fs = yield* AppFileSystem.Service
|
||||
const pluginDir = path.join(tmp.directory, ".opencode", "plugin")
|
||||
const pluginDir = path.join(tmp.directory, ".kilo", "plugin") // kilocode_change
|
||||
|
||||
yield* fs.writeWithDirs(
|
||||
path.join(pluginDir, "custom-copilot-auth.ts"),
|
||||
|
||||
@@ -82,7 +82,7 @@ describe("plugin.install.concurrent", () => {
|
||||
expect(out.map((x) => x.code)).toEqual(Array.from({ length: all.length }, () => 0))
|
||||
expect(out.map((x) => x.stderr.toString()).filter(Boolean)).toEqual([])
|
||||
|
||||
const cfg = await read(path.join(tmp.path, ".opencode", "opencode.jsonc"))
|
||||
const cfg = await read(path.join(tmp.path, ".kilo", "opencode.jsonc")) // kilocode_change
|
||||
expectPlugins(cfg.plugin, all)
|
||||
}, 25_000)
|
||||
|
||||
@@ -105,8 +105,10 @@ describe("plugin.install.concurrent", () => {
|
||||
expect(out.map((x) => x.code)).toEqual(Array.from({ length: all.length }, () => 0))
|
||||
expect(out.map((x) => x.stderr.toString()).filter(Boolean)).toEqual([])
|
||||
|
||||
const server = await read(path.join(tmp.path, ".opencode", "opencode.jsonc"))
|
||||
const tui = await read(path.join(tmp.path, ".opencode", "tui.jsonc"))
|
||||
// kilocode_change start
|
||||
const server = await read(path.join(tmp.path, ".kilo", "opencode.jsonc"))
|
||||
const tui = await read(path.join(tmp.path, ".kilo", "tui.jsonc"))
|
||||
// kilocode_change end
|
||||
expectPlugins(server.plugin, all)
|
||||
expectPlugins(tui.plugin, all)
|
||||
}, 25_000)
|
||||
@@ -114,7 +116,7 @@ describe("plugin.install.concurrent", () => {
|
||||
test("preserves updates when existing config uses .json", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const target = await plugin(tmp.path, ["server"])
|
||||
const cfg = path.join(tmp.path, ".opencode", "opencode.json")
|
||||
const cfg = path.join(tmp.path, ".kilo", "opencode.json") // kilocode_change
|
||||
await fs.mkdir(path.dirname(cfg), { recursive: true })
|
||||
await Bun.write(cfg, JSON.stringify({ plugin: ["seed@1.0.0"] }, null, 2))
|
||||
|
||||
@@ -135,6 +137,6 @@ describe("plugin.install.concurrent", () => {
|
||||
|
||||
const json = await read(cfg)
|
||||
expectPlugins(json.plugin, ["seed@1.0.0", ...next])
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "opencode.jsonc"))).toBe(false) // kilocode_change
|
||||
}, 25_000)
|
||||
})
|
||||
|
||||
@@ -122,8 +122,10 @@ describe("plugin.install.task", () => {
|
||||
const ok = await run(ctx(tmp.path))
|
||||
expect(ok).toBe(true)
|
||||
|
||||
const server = await read(path.join(tmp.path, ".opencode", "opencode.jsonc"))
|
||||
const tui = await read(path.join(tmp.path, ".opencode", "tui.jsonc"))
|
||||
// kilocode_change start
|
||||
const server = await read(path.join(tmp.path, ".kilo", "opencode.jsonc"))
|
||||
const tui = await read(path.join(tmp.path, ".kilo", "tui.jsonc"))
|
||||
// kilocode_change end
|
||||
expect(server.plugin).toEqual(["acme@1.2.3"])
|
||||
expect(tui.plugin).toEqual(["acme@1.2.3"])
|
||||
})
|
||||
@@ -144,8 +146,10 @@ describe("plugin.install.task", () => {
|
||||
const ok = await run(ctx(tmp.path))
|
||||
expect(ok).toBe(true)
|
||||
|
||||
const server = await read(path.join(tmp.path, ".opencode", "opencode.jsonc"))
|
||||
const tui = await read(path.join(tmp.path, ".opencode", "tui.jsonc"))
|
||||
// kilocode_change start
|
||||
const server = await read(path.join(tmp.path, ".kilo", "opencode.jsonc"))
|
||||
const tui = await read(path.join(tmp.path, ".kilo", "tui.jsonc"))
|
||||
// kilocode_change end
|
||||
expect(server.plugin).toEqual([["acme@1.2.3", { custom: true, other: false }]])
|
||||
expect(tui.plugin).toEqual([["acme@1.2.3", { compact: true }]])
|
||||
})
|
||||
@@ -153,7 +157,7 @@ describe("plugin.install.task", () => {
|
||||
test("preserves JSONC comments when adding plugins to server and tui config", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const target = await plugin(tmp.path, ["server", "tui"])
|
||||
const cfg = path.join(tmp.path, ".opencode")
|
||||
const cfg = path.join(tmp.path, ".kilo") // kilocode_change
|
||||
const server = path.join(cfg, "opencode.jsonc")
|
||||
const tui = path.join(cfg, "tui.jsonc")
|
||||
await fs.mkdir(cfg, { recursive: true })
|
||||
@@ -212,7 +216,7 @@ describe("plugin.install.task", () => {
|
||||
test("preserves JSONC comments when force replacing plugin version", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const target = await plugin(tmp.path, ["server"])
|
||||
const cfg = path.join(tmp.path, ".opencode", "opencode.jsonc")
|
||||
const cfg = path.join(tmp.path, ".kilo", "opencode.jsonc") // kilocode_change
|
||||
await fs.mkdir(path.dirname(cfg), { recursive: true })
|
||||
await Bun.write(
|
||||
cfg,
|
||||
@@ -257,14 +261,14 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctx(tmp.path))
|
||||
expect(ok).toBe(true)
|
||||
const server = await read(path.join(tmp.path, ".opencode", "opencode.jsonc"))
|
||||
const server = await read(path.join(tmp.path, ".kilo", "opencode.jsonc")) // kilocode_change
|
||||
expect(server.plugin).toEqual(["acme@1.2.3"])
|
||||
})
|
||||
|
||||
test("does not change configured package version without force", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const target = await plugin(tmp.path, ["server"])
|
||||
const cfg = path.join(tmp.path, ".opencode", "opencode.json")
|
||||
const cfg = path.join(tmp.path, ".kilo", "opencode.json") // kilocode_change
|
||||
await fs.mkdir(path.dirname(cfg), { recursive: true })
|
||||
await Bun.write(cfg, JSON.stringify({ plugin: ["acme@1.0.0"] }, null, 2))
|
||||
|
||||
@@ -284,7 +288,7 @@ describe("plugin.install.task", () => {
|
||||
test("does not change scoped package version without force", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const target = await plugin(tmp.path, ["server"])
|
||||
const cfg = path.join(tmp.path, ".opencode", "opencode.json")
|
||||
const cfg = path.join(tmp.path, ".kilo", "opencode.json") // kilocode_change
|
||||
await fs.mkdir(path.dirname(cfg), { recursive: true })
|
||||
await Bun.write(cfg, JSON.stringify({ plugin: ["@scope/acme@1.0.0"] }, null, 2))
|
||||
|
||||
@@ -304,7 +308,7 @@ describe("plugin.install.task", () => {
|
||||
test("keeps file plugin entries and still adds npm plugin", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const target = await plugin(tmp.path, ["server"])
|
||||
const cfg = path.join(tmp.path, ".opencode", "opencode.json")
|
||||
const cfg = path.join(tmp.path, ".kilo", "opencode.json") // kilocode_change
|
||||
await fs.mkdir(path.dirname(cfg), { recursive: true })
|
||||
await Bun.write(cfg, JSON.stringify({ plugin: ["file:///tmp/acme.ts"] }, null, 2))
|
||||
|
||||
@@ -324,7 +328,7 @@ describe("plugin.install.task", () => {
|
||||
test("force replaces configured package version and keeps tuple options", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const target = await plugin(tmp.path, ["server"])
|
||||
const cfg = path.join(tmp.path, ".opencode", "opencode.json")
|
||||
const cfg = path.join(tmp.path, ".kilo", "opencode.json") // kilocode_change
|
||||
await fs.mkdir(path.dirname(cfg), { recursive: true })
|
||||
await Bun.write(
|
||||
cfg,
|
||||
@@ -367,7 +371,7 @@ describe("plugin.install.task", () => {
|
||||
expect(ok).toBe(true)
|
||||
|
||||
expect(await Filesystem.exists(path.join(global, "opencode.jsonc"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "opencode.jsonc"))).toBe(false) // kilocode_change
|
||||
})
|
||||
|
||||
test("writes local scope under directory when vcs is not git", async () => {
|
||||
@@ -386,8 +390,10 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctxDir(directory, worktree))
|
||||
expect(ok).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(directory, ".opencode", "opencode.jsonc"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(worktree, ".opencode", "opencode.jsonc"))).toBe(false)
|
||||
// kilocode_change start
|
||||
expect(await Filesystem.exists(path.join(directory, ".kilo", "opencode.jsonc"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(worktree, ".kilo", "opencode.jsonc"))).toBe(false)
|
||||
// kilocode_change end
|
||||
})
|
||||
|
||||
test("writes local scope under directory when worktree is root slash", async () => {
|
||||
@@ -404,7 +410,7 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctxRoot(directory))
|
||||
expect(ok).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(directory, ".opencode", "opencode.jsonc"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(directory, ".kilo", "opencode.jsonc"))).toBe(true) // kilocode_change
|
||||
})
|
||||
|
||||
test("writes tui local scope under directory when worktree is root slash", async () => {
|
||||
@@ -421,7 +427,7 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctxRoot(directory))
|
||||
expect(ok).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(directory, ".opencode", "tui.jsonc"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(directory, ".kilo", "tui.jsonc"))).toBe(true) // kilocode_change
|
||||
})
|
||||
|
||||
test("writes only tui config for tui-only plugins", async () => {
|
||||
@@ -436,8 +442,10 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctx(tmp.path))
|
||||
expect(ok).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "tui.jsonc"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false)
|
||||
// kilocode_change start
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "tui.jsonc"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "opencode.jsonc"))).toBe(false)
|
||||
// kilocode_change end
|
||||
})
|
||||
|
||||
test("writes tui config for oc-themes-only packages", async () => {
|
||||
@@ -454,10 +462,12 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctx(tmp.path))
|
||||
expect(ok).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "tui.jsonc"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false)
|
||||
// kilocode_change start
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "tui.jsonc"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "opencode.jsonc"))).toBe(false)
|
||||
// kilocode_change end
|
||||
|
||||
const tui = await read(path.join(tmp.path, ".opencode", "tui.jsonc"))
|
||||
const tui = await read(path.join(tmp.path, ".kilo", "tui.jsonc")) // kilocode_change
|
||||
expect(tui.plugin).toEqual(["acme@1.2.3"])
|
||||
})
|
||||
|
||||
@@ -473,15 +483,19 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctx(tmp.path))
|
||||
expect(ok).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "tui.jsonc"))).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false)
|
||||
// kilocode_change start
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "tui.jsonc"))).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "opencode.jsonc"))).toBe(false)
|
||||
// kilocode_change end
|
||||
})
|
||||
|
||||
test("force replaces version in both server and tui configs", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const target = await plugin(tmp.path, ["server", "tui"])
|
||||
const server = path.join(tmp.path, ".opencode", "opencode.json")
|
||||
const tui = path.join(tmp.path, ".opencode", "tui.json")
|
||||
// kilocode_change start
|
||||
const server = path.join(tmp.path, ".kilo", "opencode.json")
|
||||
const tui = path.join(tmp.path, ".kilo", "tui.json")
|
||||
// kilocode_change end
|
||||
await fs.mkdir(path.dirname(server), { recursive: true })
|
||||
await Bun.write(server, JSON.stringify({ plugin: ["acme@1.0.0", "other@1.0.0"] }, null, 2))
|
||||
await Bun.write(tui, JSON.stringify({ plugin: [["acme@1.0.0", { mode: "safe" }], "other@1.0.0"] }, null, 2))
|
||||
@@ -505,7 +519,7 @@ describe("plugin.install.task", () => {
|
||||
test("returns false and keeps config unchanged for invalid JSONC", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const target = await plugin(tmp.path, ["server"])
|
||||
const cfg = path.join(tmp.path, ".opencode", "opencode.jsonc")
|
||||
const cfg = path.join(tmp.path, ".kilo", "opencode.jsonc") // kilocode_change
|
||||
await fs.mkdir(path.dirname(cfg), { recursive: true })
|
||||
const bad = '{"plugin": ["acme@1.0.0",}'
|
||||
await Bun.write(cfg, bad)
|
||||
@@ -534,8 +548,10 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctx(tmp.path))
|
||||
expect(ok).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "tui.jsonc"))).toBe(false)
|
||||
// kilocode_change start
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "opencode.jsonc"))).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "tui.jsonc"))).toBe(false)
|
||||
// kilocode_change end
|
||||
})
|
||||
|
||||
test("returns false when manifest cannot be read", async () => {
|
||||
@@ -551,7 +567,7 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctx(tmp.path))
|
||||
expect(ok).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "opencode.jsonc"))).toBe(false) // kilocode_change
|
||||
})
|
||||
|
||||
test("returns false when install fails", async () => {
|
||||
@@ -565,6 +581,6 @@ describe("plugin.install.task", () => {
|
||||
|
||||
const ok = await run(ctx(tmp.path))
|
||||
expect(ok).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false)
|
||||
expect(await Filesystem.exists(path.join(tmp.path, ".kilo", "opencode.jsonc"))).toBe(false) // kilocode_change
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1640,7 +1640,7 @@ const provideMultiInstance = <A, E, R>(eff: Effect.Effect<A, E, R>) =>
|
||||
it.effect("plugin config providers persist after instance dispose", () =>
|
||||
Effect.gen(function* () {
|
||||
const dir = yield* tmpdirScoped()
|
||||
const configDir = path.join(dir, ".opencode")
|
||||
const configDir = path.join(dir, ".kilo") // kilocode_change
|
||||
const root = path.join(configDir, "plugin")
|
||||
yield* Effect.promise(() => mkdir(root, { recursive: true }))
|
||||
yield* Effect.promise(() => markPluginDependenciesReady(configDir))
|
||||
@@ -1697,7 +1697,7 @@ it.instance(
|
||||
"plugin config enabled and disabled providers are honored",
|
||||
Effect.gen(function* () {
|
||||
const instance = yield* TestInstance
|
||||
const configDir = path.join(instance.directory, ".opencode")
|
||||
const configDir = path.join(instance.directory, ".kilo") // kilocode_change
|
||||
const root = path.join(configDir, "plugin")
|
||||
yield* Effect.promise(() => mkdir(root, { recursive: true }))
|
||||
yield* Effect.promise(() => markPluginDependenciesReady(configDir))
|
||||
|
||||
@@ -122,7 +122,7 @@ function writeProviderAuthPlugin(dir: string) {
|
||||
yield* Effect.promise(() => preparePluginDependencies(dir)) // kilocode_change
|
||||
|
||||
yield* fs.writeWithDirs(
|
||||
path.join(dir, ".opencode", "plugin", "provider-oauth-parity.ts"),
|
||||
path.join(dir, ".kilo", "plugin", "provider-oauth-parity.ts"), // kilocode_change
|
||||
[
|
||||
"export default {",
|
||||
' id: "test.provider-oauth-parity",',
|
||||
@@ -157,7 +157,7 @@ function writeProviderAuthValidationPlugin(dir: string) {
|
||||
yield* Effect.promise(() => preparePluginDependencies(dir)) // kilocode_change
|
||||
|
||||
yield* fs.writeWithDirs(
|
||||
path.join(dir, ".opencode", "plugin", "provider-oauth-validation.ts"),
|
||||
path.join(dir, ".kilo", "plugin", "provider-oauth-validation.ts"), // kilocode_change
|
||||
[
|
||||
"export default {",
|
||||
' id: "test.provider-oauth-validation",',
|
||||
@@ -199,7 +199,7 @@ function writeFunctionOptionsPlugin(dir: string) {
|
||||
yield* Effect.promise(() => preparePluginDependencies(dir)) // kilocode_change
|
||||
|
||||
yield* fs.writeWithDirs(
|
||||
path.join(dir, ".opencode", "plugin", "provider-function-options.ts"),
|
||||
path.join(dir, ".kilo", "plugin", "provider-function-options.ts"), // kilocode_change
|
||||
[
|
||||
"export default {",
|
||||
' id: "test.provider-function-options",',
|
||||
@@ -231,7 +231,7 @@ function writeProviderModelsMutationPlugin(dir: string) {
|
||||
yield* Effect.promise(() => preparePluginDependencies(dir)) // kilocode_change
|
||||
|
||||
yield* fs.writeWithDirs(
|
||||
path.join(dir, ".opencode", "plugin", "provider-models-mutation.ts"),
|
||||
path.join(dir, ".kilo", "plugin", "provider-models-mutation.ts"), // kilocode_change
|
||||
[
|
||||
"export default {",
|
||||
' id: "test.provider-models-mutation",',
|
||||
|
||||
@@ -294,7 +294,7 @@ function writeStandardFiles(dir: string) {
|
||||
function writeProjectSkill(dir: string) {
|
||||
return AppFileSystem.Service.use((fs) =>
|
||||
fs.writeWithDirs(
|
||||
path.join(dir, ".opencode", "skills", "project-rest-skill", "SKILL.md"),
|
||||
path.join(dir, ".kilo", "skills", "project-rest-skill", "SKILL.md"), // kilocode_change
|
||||
`---
|
||||
name: project-rest-skill
|
||||
description: A project skill visible to REST API prompts.
|
||||
|
||||
@@ -194,10 +194,12 @@ Just some content without YAML frontmatter.
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
Effect.gen(function* () {
|
||||
// kilocode_change start - load .kilo skills without falling back to .opencode
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(
|
||||
path.join(dir, ".opencode", "skill", "manual-skill", "SKILL.md"),
|
||||
`---
|
||||
Promise.all([
|
||||
Bun.write(
|
||||
path.join(dir, ".kilo", "skill", "manual-skill", "SKILL.md"),
|
||||
`---
|
||||
name: manual-skill
|
||||
---
|
||||
|
||||
@@ -205,8 +207,18 @@ name: manual-skill
|
||||
|
||||
Instructions here.
|
||||
`,
|
||||
),
|
||||
),
|
||||
Bun.write(
|
||||
path.join(dir, ".opencode", "skill", "ignored-skill", "SKILL.md"),
|
||||
`---
|
||||
name: ignored-skill
|
||||
description: This skill must not load.
|
||||
---
|
||||
`,
|
||||
),
|
||||
]),
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
const skill = yield* Skill.Service
|
||||
const list = discovered(yield* skill.all()) // kilocode_change
|
||||
@@ -485,16 +497,18 @@ description: A skill in the .agents/skills directory.
|
||||
# Agent Skill
|
||||
`,
|
||||
),
|
||||
// kilocode_change start
|
||||
Bun.write(
|
||||
path.join(dir, ".opencode", "skill", "opencode-skill", "SKILL.md"),
|
||||
path.join(dir, ".kilo", "skill", "opencode-skill", "SKILL.md"),
|
||||
`---
|
||||
name: opencode-skill
|
||||
description: A skill in the .opencode/skill directory.
|
||||
description: A skill in the .kilo/skill directory.
|
||||
---
|
||||
|
||||
# OpenCode Skill
|
||||
`,
|
||||
),
|
||||
// kilocode_change end
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -532,26 +546,28 @@ description: A skill in the .agents/skills directory.
|
||||
# Agent Skill
|
||||
`,
|
||||
),
|
||||
// kilocode_change start
|
||||
Bun.write(
|
||||
path.join(dir, ".opencode", "skill", "agent-skill", "SKILL.md"),
|
||||
path.join(dir, ".kilo", "skill", "agent-skill", "SKILL.md"),
|
||||
`---
|
||||
name: opencode-skill
|
||||
description: A skill in the .opencode/skill directory.
|
||||
description: A skill in the .kilo/skill directory.
|
||||
---
|
||||
|
||||
# OpenCode Skill
|
||||
`,
|
||||
),
|
||||
Bun.write(
|
||||
path.join(dir, ".opencode", "skills", "agent-skill", "SKILL.md"),
|
||||
path.join(dir, ".kilo", "skills", "agent-skill", "SKILL.md"),
|
||||
`---
|
||||
name: opencode-skill
|
||||
description: A skill in the .opencode/skills directory.
|
||||
description: A skill in the .kilo/skills directory.
|
||||
---
|
||||
|
||||
# OpenCode Skill
|
||||
`,
|
||||
),
|
||||
// kilocode_change end
|
||||
]),
|
||||
)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import { run as runSandbox, type Profile } from "@kilocode/sandbox" // kilocode_
|
||||
|
||||
const node = CrossSpawnSpawner.defaultLayer
|
||||
const configLayer = TestConfig.layer({
|
||||
directories: () => InstanceState.directory.pipe(Effect.map((dir) => [path.join(dir, ".opencode")])),
|
||||
directories: () => InstanceState.directory.pipe(Effect.map((dir) => [path.join(dir, ".kilo")])), // kilocode_change
|
||||
})
|
||||
|
||||
type RegistryLayerOptions = {
|
||||
@@ -206,10 +206,10 @@ describe("tool.registry", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("loads tools from .opencode/tool (singular)", () =>
|
||||
it.instance("loads tools from .kilo/tool (singular)" /* kilocode_change */, () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const opencode = path.join(test.directory, ".opencode")
|
||||
const opencode = path.join(test.directory, ".kilo") // kilocode_change
|
||||
const tool = path.join(opencode, "tool")
|
||||
yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
@@ -233,10 +233,10 @@ describe("tool.registry", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("ignores non-tool exports in .opencode/tool files", () =>
|
||||
it.instance("ignores non-tool exports in .kilo/tool files" /* kilocode_change */, () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const tool = path.join(test.directory, ".opencode", "tool")
|
||||
const tool = path.join(test.directory, ".kilo", "tool") // kilocode_change
|
||||
yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(
|
||||
@@ -269,7 +269,7 @@ describe("tool.registry", () => {
|
||||
it.instance("tolerates a custom tool exporting null/undefined args (no-args fallback)", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const tool = path.join(test.directory, ".opencode", "tool")
|
||||
const tool = path.join(test.directory, ".kilo", "tool") // kilocode_change
|
||||
yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(
|
||||
@@ -311,10 +311,10 @@ describe("tool.registry", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("loads tools from .opencode/tools (plural)", () =>
|
||||
it.instance("loads tools from .kilo/tools (plural)" /* kilocode_change */, () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const opencode = path.join(test.directory, ".opencode")
|
||||
const opencode = path.join(test.directory, ".kilo") // kilocode_change
|
||||
const tools = path.join(opencode, "tools")
|
||||
yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
@@ -341,7 +341,7 @@ describe("tool.registry", () => {
|
||||
it.instance("loads Zod-schema custom tools with JSON Schema and validation", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const customTools = path.join(test.directory, ".opencode", "tools")
|
||||
const customTools = path.join(test.directory, ".kilo", "tools") // kilocode_change
|
||||
const pluginTool = pathToFileURL(path.resolve(import.meta.dir, "../../../plugin/src/tool.ts")).href
|
||||
yield* Effect.promise(() => fs.mkdir(customTools, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
@@ -394,7 +394,7 @@ describe("tool.registry", () => {
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const opencode = path.join(test.directory, ".opencode")
|
||||
const opencode = path.join(test.directory, ".kilo") // kilocode_change
|
||||
const customTools = path.join(opencode, "tools")
|
||||
const plugin = path.join(opencode, "node_modules", "@kilocode", "plugin") // kilocode_change
|
||||
yield* Effect.promise(() => fs.mkdir(path.join(plugin, "dist"), { recursive: true }))
|
||||
@@ -459,7 +459,7 @@ describe("tool.registry", () => {
|
||||
it.instance("preserves attachments from structured custom tool results", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const customTools = path.join(test.directory, ".opencode", "tools")
|
||||
const customTools = path.join(test.directory, ".kilo", "tools") // kilocode_change
|
||||
const pluginTool = pathToFileURL(path.resolve(import.meta.dir, "../../../plugin/src/tool.ts")).href
|
||||
yield* Effect.promise(() => fs.mkdir(customTools, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
@@ -504,7 +504,7 @@ describe("tool.registry", () => {
|
||||
it.instance("loads legacy JSON-schema-shaped custom tools with wire schema", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const tools = path.join(test.directory, ".opencode", "tools")
|
||||
const tools = path.join(test.directory, ".kilo", "tools") // kilocode_change
|
||||
yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(
|
||||
@@ -536,7 +536,7 @@ describe("tool.registry", () => {
|
||||
it.instance("loads tools with external dependencies without crashing", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const opencode = path.join(test.directory, ".opencode")
|
||||
const opencode = path.join(test.directory, ".kilo") // kilocode_change
|
||||
const tools = path.join(opencode, "tools")
|
||||
yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
|
||||
@@ -36,7 +36,7 @@ describe("tool.skill", () => {
|
||||
unix("execute returns skill content block with files", () =>
|
||||
provideTmpdirInstance((dir) =>
|
||||
Effect.gen(function* () {
|
||||
const skill = path.join(dir, ".opencode", "skill", "tool-skill")
|
||||
const skill = path.join(dir, ".kilo", "skill", "tool-skill") // kilocode_change
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(
|
||||
path.join(skill, "SKILL.md"),
|
||||
|
||||
Reference in New Issue
Block a user