Skip to content

feat(core): discover kilo.json and .kilo config directories - #15018

Merged
marius-kilocode merged 10 commits into
v2from
v2-kilo-config
Oct 19, 2026
Merged

marius-kilocode merged 10 commits into
v2from
v2-kilo-config

Conversation

@marius-kilocode

@marius-kilocode marius-kilocode commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Plan unit U-E1-07 (Kilo config layer), part 1: config file names and .kilo directory discovery.

What Problem This Solves

The v2 base only knows OpenCode conventions. It reads opencode.json and opencode.jsonc, discovers .opencode/ project directories, and walks above the repository root. Kilo users keep their config in kilo.json, kilo.jsonc, .kilo/ and the legacy .kilocode/. On v2 none of that loads, so project config, commands, agents, modes, skills and plugins from Kilo directories are lost.

Why This Change Was Made

The behavior follows v1 Kilo on main (767b958). v1 reads files in three different orders, and this PR keeps all three. In each row a later file wins.

Source Order (lowest to highest) v1 reference
Global config directory kilo.json, kilo.jsonc, opencode.json, opencode.jsonc config.ts:460-481
Project root files, walked up all kilo.* (root to nearest, .json then .jsonc), then all opencode.* the same way, so any opencode.* wins over any kilo.* at any depth config.ts:786, paths.ts:10-21
Inside .kilocode and .kilo kilo.jsonc, kilo.json, opencode.jsonc, opencode.json (ALL_CONFIG_FILES) kilocode/config/config.ts:41, config.ts:837

Other v1 rules this PR applies:

  • .kilocode and .kilo are both read at each level, and .kilo wins. .opencode is not read, as in v1 since ef9b1dcec01 (merged in fix(cli): prefer .kilo config directory #11638).
  • Inside a git or hg repository the upward walk stops at the worktree root (paths.ts:19,31). Outside version control it still reaches the filesystem root. The comparison resolves symlinks, so a /tmp spelling of a /private/tmp repository also stops.
  • Global writes go to the first existing file of kilo.jsonc, kilo.json, opencode.jsonc, opencode.json, else to a new kilo.jsonc (globalConfigFile, config.ts:206-216).
  • kilo mcp add (also used by plugin add and plugin remove) prefers .kilo, then .kilocode, then root files, and creates kilo.json (mcp.ts:396-426).

Commands, agents, modes, skills and plugins need no change of their own. v2 loads them from every config Directory entry, so .kilo/command, .kilo/agent, .kilo/mode, .kilo/skills and .kilo/plugins work when the directories are discovered.

The global config directory is not hardcoded. It comes from Global (global.config), so it follows the app name when the identity work changes it.

Shape of the change:

  • The Kilo rules live in the Kilo-owned module packages/core/src/kilocode/config/discovery.ts.
  • Shared files get small hooks marked kilocode_change: config/discovery.ts, config.ts (directory file order parameter and writer) and cli/.../mcp/add.ts.
  • packages/core/src/plugin/internal.ts is not touched.

Two deliberate differences from upstream v2: the walk stops at the worktree root, and .opencode is not read. Both follow v1.

User Impact

  • kilo.json and kilo.jsonc load from the global config directory, the project and parent directories up to the worktree root, and from .kilo and .kilocode.
  • Markdown commands, agents, modes, skills and plugins in .kilo/ and .kilocode/ are available, with .kilo winning on conflicts.
  • .opencode/ content and config above the repository root no longer load.
  • New global config is written to kilo.jsonc. kilo mcp add creates kilo.json.

Evidence

Typecheck

Package Result
packages/core bun typecheck pass
packages/cli bun typecheck fails only in packages/plugin/src/source.node.ts (registerHooks, 4 errors). Same errors on the unmodified tree

Tests

All runs used temporary XDG_DATA_HOME, XDG_CONFIG_HOME, XDG_STATE_HOME and XDG_CACHE_HOME. A check after the runs found no new files in the real OpenCode directories.

Command Result
bun run test test/kilocode (core, new) 5 pass. All 5 fail with the shared hooks reverted
core focused: test/kilocode test/config test/formatter.test.ts test/plugin/supervisor-reload.test.ts test/plugin/supervisor.test.ts test/session-activation.test.ts test/skill.test.ts test/agent.test.ts test/command.test.ts test/instruction-discovery.test.ts test/preload.test.ts 298 pass, 6 fail
core full bun run test 6552 pass, 17 fail
cli full bun run test 319 pass, 0 fail
server full bun run test 69 pass, 2 fail

Every failure also happens on the unmodified tree. I checked this by running the same files with the changed files restored to the base:

  • 6 in test/plugin/supervisor-reload.test.ts (RPC is unavailable: greeter, one 15 s timeout).
  • 6 Session.shell tests. The baseline lists 7 macOS bash 3.2 shell failures.
  • 5 in test/plugin/module.test.ts.
  • 2 in server test/worktree.test.ts (canonical plugins).
  • One server fs.test.ts timeout passed on rerun (flaky).

The baseline for the shell failures is pinned-v2-baseline.md on kilo-v2-clean-bootstrap. Package code is identical between that base and v2.

Upstream tests changed, with kilocode_change markers. None is skipped or deleted.

  • .opencode fixtures became .kilo: config/config.test.ts, config/reload.test.ts, config/command-subagent.test.ts, formatter.test.ts, plugin/supervisor-reload.test.ts and session-activation.test.ts.
  • config.test.ts "loads global and ancestor configuration across the project boundary" became "... up to the project boundary". The file above the git root (outside) is no longer expected.
  • Two watch-subscription expectations now list the Kilo names.
  • packages/cli/script/service-smoke.ts creates its plugin in .kilo/plugins. The first CI run failed Verify compiled service lifecycle with Compiled service did not discover the created plugin, because the smoke used .opencode/plugins. The compiled build cannot run locally (it requires bun ^1.4.2, this machine has 1.4.0-canary), so CI verifies this step.

Self-test (real CLI, bun dev isolated profile, TUI harness from the v2 migration skill, headless)

The fixture project is a git repo. The TUI was started in the nested folder repo/app. Each config file defines a slash command with the same name and a description that names the file, so the slash list shows which file wins.

Scenario Expected (v1) Result
kilo.json default_agent: plan prompt shows Plan pass
/rootorder in root kilo.json and kilo.jsonc root kilo.jsonc pass
/depth in root kilo.json, root opencode.json, app/kilo.json root opencode.json pass
/dirorder in .kilo/kilo.jsonc and .kilo/kilo.json .kilo/kilo.json pass
/legacy in .kilocode/kilo.json and .kilo/kilo.json .kilo/kilo.json pass
/globalorder in global kilo.json and kilo.jsonc global kilo.jsonc pass
.kilo/command/kilo-hello.md, .kilocode/command/legacy-hello.md both listed pass
.opencode/command/opencode-hello.md not listed pass
kilo.json and .kilo/command above the git root not listed pass
run /kilo-hello template sent, model replies KILOOK pass with kilo/kilo-auto/free
run /kilo-hello with kilo/deepseek/deepseek-v4.1-flash blocked: the Gateway answered You need to sign in to use this model in the dev profile (Gateway and login work, #14998). Config loading was already confirmed by the sent template
kilo mcp add in a new project, then with .kilo/kilo.jsonc present, then --global kilo.json, .kilo/kilo.jsonc, global kilo.json pass

The CLI logs have no errors except the sign-in error above. The headless run cannot take screenshots, so the evidence is the terminal text.

Follow-ups (not in this PR)

  • TUI themes and TUI plugins from .kilo and .kilocode (separate PR, ready locally).
  • Managed enterprise config. v1 reads /Library/Application Support/kilo, %ProgramData%\kilo and /etc/kilo as the highest priority, plus the macOS MDM plist ai.opencode.managed. v2 has no managed config directory, and ManagedPolicy only holds Console statements. Policy ranking also reverses document order, so a managed document placed last would rank lowest for policies. The v2 path is an open question.
  • Key mapping and legacy migrators (U-E3-03), the overlay writer (0600, revision checks), and the v1-era keys (memory_model, swarm, PTC, cleanup).
  • The v1 primary-checkout fallback, which reads .kilo and .kilocode of the main checkout from a git worktree.
  • Whether v2 reads the v1 user-level folders ~/.kilo and ~/.kilocode. Not done here because v2 uses separate kilo2 directories.
  • The leftover .opencode notice from v1.
  • Identity work (U-E1-01): packages/core/script/test.ts and packages/core/test/preload.test.ts hardcode opencode paths and will need changes when the app name changes. KILO_* env names, and the updater, which reads only opencode.* from global config.

Review follow-ups

The Kilo Code Review on this PR raised three suggestions. One is fixed here, two are recorded for the config unit:

  • Fixed: resolveConfigPath takes a global flag again, so mcp add --global and plugin add/remove only probe root files in the global directory, like v1. Without it a server or plugin could be written into .kilo/ or .kilocode/ under the global directory, which the config layer never reads.
  • Follow-up: the legacy global config.json (lowest read priority in v1, last write candidate, written by the v1 TOML migration) is not read or written here. It needs a global-only entry in the read chain and in Config.update; it cannot join the shared names list because project roots must not read it.
  • Follow-up: for nested projects, v1 lets an ancestor .kilo override a nested one, while this PR keeps the nearest .kilo winning (the existing .toReversed()). That matches root-file behavior and v2 conventions; a cross-level test and a decision should pin it.

Read kilo.json and kilo.jsonc next to opencode.json and opencode.jsonc in the Kilo v1 orders. Discover .kilocode and .kilo project directories instead of .opencode, with .kilo winning. Stop the upward walk at the worktree root. Write new global config to kilo.jsonc and let kilo mcp add use the Kilo v1 candidates.
@github-actions github-actions Bot added the area:cli CLI, TUI and core engine label Oct 13, 2026
Kilo does not read .opencode project directories, so the compiled service smoke must create its plugin in .kilo/plugins.
# Conflicts:
#	packages/core/src/config.ts
@marius-kilocode
marius-kilocode marked this pull request as ready for review October 19, 2026 13:14
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

➖ v2 migration progress: 0.9% → 0.9% (0 done, 0 total, 0 dropped; 86/9829 cells)

Placement: 125 new marked lines in shared files (125 seam), 0 new kilo_port annotations, 1 new core-internal import (@opencode/core/location).

Ratchets: 9 core-internal imports (base 8); shared patches by rung 29 seam, 1 patch (base 18 seam, 1 patch).

No change to the progress map.

v2 migration progress map at this commit

Full map · how cells close

// Kilo v1 reads config files in three different orders. For reads, a later file in each list wins.

/** The global config directory and the project root files of one directory. `.jsonc` wins over `.json`. */
export const names = ["kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Legacy global config.json is no longer read or written

Kilo v1 also reads config.json in the global config directory (lowest priority) and its globalConfigFile includes config.json as a write candidate; the v1 TOML migration also writes that file. Here names is shared by the global directory and project-root discovery, so it cannot simply be appended (every project root would then read it). If dropping the legacy file is intentional, noting it in the body would help; otherwise it needs a global-only entry in the read chain and in Config.update's candidate list.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

project: yield* Effect.forEach(
visible.filter((item) => path.basename(item) === ".opencode").toReversed(),
// kilocode_change - Kilo project directories replace .opencode
visible.filter((item) => directoryNames.includes(path.basename(item))).toReversed(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Confirm the cross-level precedence for .kilo directories

Kilo v1 merges config directories nearest-first (ConfigPaths.directories does not reverse the fs.up result), so an ancestor .kilo overrides a nested one. The pre-existing .toReversed() here makes the nearest .kilo win, which matches the root-file behavior and v2 conventions but differs from v1 for nested projects. If that is intentional, a cross-level test and a line in the PR body would pin it down.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

path.join(directory, ".opencode", "opencode.jsonc"),
]
// kilocode_change start - Kilo config names, with .kilo and .kilocode before root files, like Kilo v1
const candidates = [".kilo", ".kilocode", "."].flatMap((parent) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: resolveConfigPath now probes .kilo/.kilocode inside the global config directory

v1's resolveConfigPath(baseDir, global) only checks the root files when global is true. Both mcp add --global and plugin add/plugin remove call this with global.config, so a nested .kilo/kilo.jsonc there would now be selected and written even though the global loader never reads it. Keeping a global flag like v1 would preserve the distinction. The function is exported and has no unit test; a small fs-fixture test would cover both modes.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 1 Issue Found | Recommendation: Merge after optional test addition

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/cli/src/commands/handlers/mcp/add.ts 42 The global branch of resolveConfigPath (used by mcp add --global, plugin add and plugin remove) still has no automated test. The only coverage remains the manual self-test in the PR body. A temp-directory fixture can assert the per-mode candidate lists and fallback (kilo.jsonc/root files for global, kilo.json as fallback).
Files Reviewed (2 incremental files)
  • migration-tracking/progress/README.md - 0 issues
  • migration-tracking/progress/history.svg - 0 issues

Incremental verification

  • The incremental diff (0ac8fe3..0b8a0a2) contains a single commit, chore(migration): render the map at the merge base, which only regenerates the progress README table and history.svg.
  • Both generated files are consistent: the 2026-10-16: 12/9854 point renders at 0.1%, and the polyline and labels match the table. The migration progress map is CI-owned (kilo-v2.yml) and is not re-reported.
  • packages/cli/src/commands/handlers/mcp/add.ts is unchanged in this increment. A targeted check confirms resolveConfigPath has no test in packages/cli/test, so the earlier suggestion is carried forward. No new inline comment was posted because an active comment already covers it.
  • The two previously raised documentation follow-ups (legacy global config.json, .kilo cross-level precedence) remain recorded in the PR body and are not re-reported.
  • No security, performance or memory-leak concerns in the incremental changes: only generated counters and SVG coordinates were added.

Fix these issues in Kilo Cloud

Previous Review Summaries (3 snapshots, latest commit 0ac8fe3)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 0ac8fe3)

Status: 1 Issue Found | Recommendation: Merge after optional test addition

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/cli/src/commands/handlers/mcp/add.ts 42 The new global branch of resolveConfigPath (and the plugin add/plugin remove callers that pass true) still has no automated test; only the manual kilo mcp add --global self-test covers it. A temp-directory fixture can assert the per-mode candidate lists and fallback.
Files Reviewed (7 files)
  • migration-tracking/progress/README.md - 0 issues
  • migration-tracking/progress/history.svg - 0 issues
  • migration-tracking/progress/progress.json - 0 issues
  • packages/cli/src/commands/handlers/mcp/add.ts - 1 issue
  • packages/cli/src/commands/handlers/plugin/add.ts - 0 issues
  • packages/cli/src/commands/handlers/plugin/remove.ts - 0 issues
  • script/upstream/shared-patches.json - 0 issues

Incremental verification

  • The incremental diff (57e709e..0ac8fe3) contains only kilocode_change annotation markers on mcp/add.ts, plugin/add.ts and plugin/remove.ts, plus the generated migration progress map and the shared-patch baseline entries for the two plugin handlers.
  • No functional code changed, so the earlier resolveConfigPath global-mode fix still holds: global mode probes only root files, and both plugin handlers pass global: true.
  • The legacy global config.json omission and the .kilo cross-level precedence deviation are documented in the PR body as follow-ups, so those earlier documentation requests remain satisfied.
  • The two new shared-patches.json entries and the raised limits are CI-owned (check-shared-patches.ts / check-opencode-annotations.ts) and are not re-reported.
  • No security, performance or memory-leak concerns in the incremental changes: only comment text and generated counters were added.

Fix these issues in Kilo Cloud

Previous review (commit 57e709e)

Status: 1 Issue Found | Recommendation: Merge after optional test addition

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/cli/src/commands/handlers/mcp/add.ts 42 The new global branch of resolveConfigPath (and the plugin add/plugin remove callers that now pass true) has no automated test; only the manual kilo mcp add --global self-test covers it. A temp-directory fixture can assert the per-mode candidate lists and fallback.
Files Reviewed (13 files)
  • migration-tracking/progress/README.md - 0 issues
  • migration-tracking/progress/progress.json - 0 issues
  • packages/cli/src/commands/handlers/mcp/add.ts - 1 issue
  • packages/cli/src/commands/handlers/plugin/add.ts - 0 issues
  • packages/cli/src/commands/handlers/plugin/remove.ts - 0 issues
  • packages/core/src/config.ts - 0 issues
  • packages/core/src/config/discovery.ts - 0 issues
  • packages/core/test/config/config.test.ts - 0 issues
  • packages/core/test/config/reload.test.ts - 0 issues
  • packages/core/test/formatter.test.ts - 0 issues
  • packages/core/test/plugin/supervisor-reload.test.ts - 0 issues
  • script/upstream/shared-patches.json - 0 issues
  • packages/cli/script/service-smoke.ts - 0 issues

Incremental verification

  • The resolveConfigPath global write-path bug is fixed: global mode now probes only root files, and plugin add/plugin remove pass global: true.
  • The legacy global config.json omission is now stated in the PR body as an explicit follow-up, so the earlier request to document it is satisfied.
  • The .kilo cross-level precedence deviation is documented in the PR body, and config.test.ts "loads global and ancestor configuration up to the project boundary" already asserts root .kilo before nested .kilo in the entry order, so that finding is resolved.
  • Marker annotations, the shared-patch baseline registration and the regenerated progress map are CI-owned and not re-reported.

No security or memory-leak concerns in the incremental changes: the new branch allocates only path strings and the discovery walk holds no long-lived state. Automated lint/typecheck/tests were not run in this read-only review.

Fix these issues in Kilo Cloud

Previous review (commit b90dbab)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/core/src/kilocode/config/discovery.ts 11 Legacy global config.json is read and written by v1 (lowest read priority, last write candidate) but is absent from names and from Config.update's candidates. It cannot be added to names because that list is shared with project-root discovery, so it needs a global-only entry; otherwise the omission should be stated in the PR body.
packages/core/src/config/discovery.ts 147 v1 ConfigPaths.directories does not reverse the fs.up result, so an ancestor .kilo overrides a nested one. The pre-existing .toReversed() here makes the nearest .kilo win. This is a deliberate-looking deviation but is not stated in the PR body and has no cross-level test.
packages/cli/src/commands/handlers/mcp/add.ts 42 Removing the v1 global flag makes resolveConfigPath(global.config) (used by mcp add --global, plugin add, plugin remove) probe global.config/.kilo and .kilocode. Those paths are never read by the config layer (sources.global only loads files directly in the global directory), so a server/plugin could be written to a file that is silently ignored. Keeping a global mode like v1 avoids this; the exported function has no test.
Files Reviewed (15 files)
  • migration-tracking/progress/README.md - 0 issues
  • migration-tracking/progress/progress.json - 0 issues
  • packages/cli/script/service-smoke.ts - 0 issues
  • packages/cli/src/commands/handlers/mcp/add.ts - 1 issue
  • packages/core/src/config.ts - 0 issues
  • packages/core/src/config/discovery.ts - 1 issue
  • packages/core/src/kilocode/config/discovery.ts - 1 issue
  • packages/core/test/config/command-subagent.test.ts - 0 issues
  • packages/core/test/config/config.test.ts - 0 issues
  • packages/core/test/config/reload.test.ts - 0 issues
  • packages/core/test/formatter.test.ts - 0 issues
  • packages/core/test/kilocode/config/discovery.test.ts - 0 issues
  • packages/core/test/plugin/supervisor-reload.test.ts - 0 issues
  • packages/core/test/session-activation.test.ts - 0 issues
  • script/upstream/shared-patches.json - 0 issues

Incremental changes since b90dbab are marker annotations, the shared-patch baseline registration (limits raised with entries and reasons), and the regenerated progress map; no new code defects. The three findings above were re-verified against current HEAD. No security or memory-leak concerns: the new walk holds no long-lived state and the watcher plan derives from the same sources. Automated lint/typecheck/tests were not run in this read-only review.

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash-1017 · Input: 38.2K · Output: 3.7K · Cached: 226.4K

Review guidance: REVIEW.md from base branch v2

]
export async function resolveConfigPath(directory: string, global = false) {
// kilocode_change start - Kilo config names, with .kilo and .kilocode before root files, like Kilo v1; the global directory only reads root files, like Kilo v1
const candidates = global

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The new global branch has no automated test

The global mode was added to fix the write-path bug, and it now has two distinct candidate lists plus a fallback that differs by mode (kilo.jsonc/root files for global, kilo.json as the fallback). The only coverage is the manual kilo mcp add --global self-test in the PR body; plugin add/plugin remove, which now pass true, are not exercised. resolveConfigPath is exported, pure filesystem I/O, and easy to cover with a temp directory: assert global mode ignores .kilo/ and .kilocode/, non-global prefers them, and the fallback path differs per mode.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:cli CLI, TUI and core engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant