Skip to content

feat(kimi-code): add workspace Scope bindings - #2043

Open
jackie-cqz wants to merge 4 commits into
oceanbase:masterfrom
jackie-cqz:fix/kimi-code-workspace-scope
Open

jackie-cqz wants to merge 4 commits into
oceanbase:masterfrom
jackie-cqz:fix/kimi-code-workspace-scope

Conversation

@jackie-cqz

@jackie-cqz jackie-cqz commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Which issue or RFC does this PR close?

Follow-up to #1988 and the Kimi integration tracked in #1963.

Rationale for this change

A Kimi home currently selects one explicit Scope for every project. Workspace mode lets the same installation use each working directory's Server-owned binding, with the Server default as the fallback.

What changes are included in this PR?

  • Add setup kimi-code --workspace-scope and an installed scripts/scope.mjs resolve|bind|unbind --cwd ... command. Existing explicit Scope installations keep their priority, and updates preserve the installed mode.
  • Reuse the existing HTTP binding endpoints and one Scope resolver across the Hook, Scope command and doctor. Binding write receipts remain separate from subsequent resolution; unconfirmed writes are not retried.
  • Supply current-request Scope metadata to native MCP guidance. Runtime history retains the actual resolved Scope, including an unresolved state before resolution succeeds; offline status remains read-only.
  • Update Kimi guidance templates, packaged guidance, capability declarations/matrices and both guides. Extend real-host acceptance to save/search in two workspaces, verify Source capture and current/historical Scope, and exercise explicit override and unbind fallback.

Are there any user-facing changes?

Use powercontext setup kimi-code --source <checkout> --workspace-scope, then bind an existing Scope with the installed Scope script. Open a new Kimi session after changing the installation mode. --workspace-scope and --scope-id are mutually exclusive.

Workspace keys use the kimi-code namespace and a SHA-256 of the native real path of the absolute working directory. Existing directories share bindings across symlink/junction aliases and filesystem-recognized case variants; native resolution precedes lexical normalization, preserving POSIX symlink/parent-component semantics. Paths that cannot be resolved retain lexical normalization. Bind existing, accessible directories again if their resolved identity changes; bindings under other identities are not migrated automatically. Subdirectories are distinct identities. Explicit Scope resolution does not depend on a workspace key, so scope.mjs resolve may omit --cwd in that mode; bind/unbind always require an absolute workspace. doctor kimi-code --cwd ... --json reports current Scope selection without requiring runtime history. Workspace history describes the Scope used at the time of the attempt, rather than re-resolving later binding changes.

The integration uses the existing Server contract. Scope creation and session lifecycle behavior are unchanged.

How was this change tested?

  • node --test integrations/kimi-code/plugins/powercontext/tests/plugin.test.mjs: 22 passed on Windows and Linux, including shared alias/case-variant bindings, parent components after symlinks, explicit Scope injection/capture without an absolute workspace, and explicit Scope-script resolution without --cwd.
  • pytest tests/test_cli_kimi_code.py tests/e2e/test_kimi_code_host.py: 18 passed on Windows (Python 3.11.15) and isolated Linux (Python 3.12.15), using actual Kimi 2.1.1 and Node 24.21.0. The CLI loaded the managed plugin and performed native MCP save/read/search against a real Server/SQLite. Regression scenarios verify shared bindings through Windows junctions, Linux symlinks and Windows case variants, consistent doctor results, alias bind/unbind and a real CLI search launched through an alias. A symlink followed by .. selects the OS-native target without overwriting or clearing the other directory's binding. Both inference services were controlled local fixtures; this was not a live-provider model run.
  • Integration manifest, CLI capability, capability documentation, guidance generation and guidance installation tests: 106 passed.
  • Generated guidance and capability-document checks, scoped Windows ty check, full Linux ty check, Ruff and uv lock --check passed.
  • prek run -a passed on Windows with the type hook covered by the separate full Linux type check. git diff --cached --check passed.

AI usage statement

OpenAI Codex (GPT-6) assisted implementation, documentation, tests and validation.

Copilot AI balanced review requested due to automatic review settings October 17, 2026 10:54

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

Comment thread src/powercontext/cli/kimi_code.py Outdated
return checks
try:
arguments = [node, str(destination / "scripts/doctor.mjs")]
arguments = [node, str(destination / "scripts/doctor.mjs"), "--cwd", str((cwd or Path.cwd()).resolve())]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Preserve workspace identity in doctor

When --cwd contains a symlink, the Scope command hashes the path without following symlinks, but this Path.resolve() follows them, causing doctor to query a different binding and potentially report the Server default instead of the bound Scope. Use the same path normalization as workspaceKey() when forwarding the workspace to the probe.

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.

修复状态(head c79f213a):本条已解除。

cli/kimi_code.py::run_kimi_diagnostics() 转发给 doctor.mjs 的路径由 os.path.abspath(...)(会先做词法 normpath,把 .. 折叠掉)改为 str((cwd or Path.cwd()).absolute()),客户端不再先行规范化;随后 workspaceKey() 的 realpathSync.native(cwd) 承担解析。于是 doctor 与 Hook/Scope 对同一 --cwd 得到同一绑定键,不再出现「doctor 查询另一个绑定、回落到 Server 默认 Scope」。

证据:tests/e2e/test_kimi_code_host.py 新增 alias/../other workspace 场景,断言 run_kimi_diagnostics(cwd=compound) 返回的 scope.checks 里的 scope_id 与 scope resolve 一致(Windows 走词法父目录,POSIX 走符号链接目标)。本机未执行该 e2e(需要真实 Server 与 Node 环境),以该 head 的 tests (3.x, e2e) 全绿为准。

Copilot AI balanced review requested due to automatic review settings October 18, 2026 04:39

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

export function workspaceKey(cwd) {
if (typeof cwd !== 'string' || !isAbsolute(cwd)) throw new Failure('workspace_required')
return { integration: 'kimi-code', kind: 'workspace',
external_id: createHash('sha256').update(resolve(cwd)).digest('hex') }

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.

[P2] 工作区绑定键不做路径规范化,别名/大小写变体会静默回落到 Server 默认 Scope

workspaceKey() 只用 resolve(cwd) 做词法规范化:不解析 junction/symlink,也不归一化盘符大小写。同一目录的两种写法得到不同的 external_id,而 resolve_binding() 在没有任何 key 命中时按 allow_default(契约默认 true)落到 Server 默认 Scope,客户端不会收到错误。

证据(probes/probe_workspace_key.mjs,Node 22.22.2 / Windows,head f1908b8e):

canonical path        -> fe54eb93... | normalized: D:\...\ws-real
same path, lowercase  -> 4af967a1... | normalized: d:\...\ws-real
junction alias        -> 4f53860f... | normalized: D:\...\ws-alias
realpath of alias: D:\...\ws-real
resolve(binding_keys=[alias]) -> scope-default (server default Scope)
resolve(binding_keys=[lower]) -> scope-default (server default Scope)

即:用户按真实路径 bind,宿主会话以别名路径上报 cwd(Windows junction/subst、macOS 上 /tmp 指向 /private/tmp 一类),Hook 的 prepare 与 Source 采集会全部写进默认 Scope 而且不报错——这正是该特性要避免的跨项目记忆混写。两语文档写的是 "SHA-256 of the normalized absolute working directory",容易被读成路径已规范化。

同仓其他宿主取的是规范化身份:integrations/zcode/plugins/powercontext/shared/scope.mjs 用 realpathSync(并叠加 git rev-parse --show-toplevel),integrations/claude-code/plugins/powercontext/scripts/workspace_scope.py 用 Path.resolve(strict=False)。本接入是唯一纯词法实现。

建议:对已存在目录用 realpathSync.native()(失败回退 resolve)后再哈希;或至少在文档中写明"别名路径是独立身份,绑定必须使用宿主上报的原始字符串",并补 alias / 大小写变体的用例。tests/e2e/test_kimi_code_host.py 目前把"junction 与真实路径是不同身份"断言为期望行为,这等于把该行为固定下来,请确认这是有意选择。

与线程 4305454445 不是同一根因:那条指 doctor 转发 --cwd 时用 Path.resolve() 与 workspaceKey() 不一致(head 已改为 os.path.abspath,客户端内部一致);本条指 workspaceKey() 自身的身份语义。

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.

修复状态(head 6a514384):本条根因已解决,不再成立。

workspaceKey() 现在先 resolve(cwd)、再 realpathSync.native(...),解析失败时退回词法规范化后的绝对路径(与文档新增的 fallback 说明一致),integration / kind / external_id 的结构未变。

本机实测(Windows / Node 22.22.2,exact head 副本 source/tree-6a514384):node --test tests/plugin.test.mjs 21/21 通过;新增段落覆盖 junction 别名与同一路径全大写、全小写两种变体,三种写法都解析到同一个 Scope(scope-first),bind/unbind 走别名也命中同一条绑定。

未覆盖范围:macOS/ubuntu 上大小写敏感文件系统的差异(CI 只跑 ubuntu,且那里的别名用例走 dir 符号链接),以及 realpathSync.native 在 UNC / 超长路径上抛错时的词法回落分支。

}

export async function resolveScope(settings, signal, input = {}) {
const keys = input.cwd === undefined && settings.scopeId ? [] : [workspaceKey(input.cwd)]

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.

[P2] 显式 Scope 下仍强制派生工作区键,非绝对 cwd 会让 Hook 从"注入上下文"退化为"整体失败"

resolveScope() 现在只要 input.cwd !== undefined 就调用 workspaceKey(input.cwd)。settings.scopeId 已配置时这个 key 在服务端不会被用到(resolve_binding() 在 explicit_scope_id 处直接返回),却把 isAbsolute 校验变成了 Hook 的硬前置条件。

Base/Head 对照(probes/hook-cwd/probe_hook_cwd.mjs,显式 Scope + cwd: "relative/workspace",Node 22.22.2):

base cwd="relative/workspace" exit=0 injected=true
head cwd="relative/workspace" exit=0 injected=false   stderr: {"event":"hook","outcome":"workspace_required"}
base cwd="D:\absolute\workspace" exit=0 injected=true
head cwd="D:\absolute\workspace" exit=0 injected=true

master 上同样的输入照常注入上下文并采集 Source;head 直接以 workspace_required 结束,prepare 与 capture 都没有执行。

触发条件是宿主上报非绝对 cwd。我不能证明 Kimi 会这样上报,因此按 P2 提出、不作阻断;但这是相对 master 的功能收窄,而显式 Scope 模式并不需要这个键。

建议:const keys = settings.scopeId ? [] : [workspaceKey(input.cwd)],并补一条"显式 Scope + 相对 cwd 仍注入"的用例。

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.

修复状态(head 6a514384):本条根因已解决,不再成立。

resolveScope() 改为 const keys = settings.scopeId ? [] : [workspaceKey(input.cwd)],显式 Scope 时不再派生工作区键。新增用例 explicit Scope supplies context and captures without an absolute workspace 用 cwd: 'relative/workspace'、null、undefined 各调一次 Hook,断言仍注入上下文且捕获落到 scope-test;原断言 binding_keys[0] 的用例改成 assert.deepEqual(f.requests[0].body.binding_keys, [])。本机 21/21 通过,doctor 也走同一条 resolveScope,已不再把绝对 cwd 当硬前置。

同类残留只剩 Scope 脚本一路(已另发评论),不影响 Hook 与 doctor。

Copilot AI balanced review requested due to automatic review settings October 18, 2026 12:09

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

if (args.positionals.length !== 1 || !['resolve', 'bind', 'unbind'].includes(action) ||
(action === 'bind' ? !isScopeId(target) : target !== undefined)) throw new Failure('invalid_arguments')
const input = { cwd: args.values.cwd }
const key = workspaceKey(input.cwd)

@AlexStocks AlexStocks Oct 18, 2026 •

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.

[P2] scope.mjs resolve 在显式 Scope 下仍强制要求绝对 --cwd

第 32 行的 workspaceKey(input.cwd) 无条件执行,且位于 loadSettings() / resolveScope() 之前。因此当安装为显式 Scope(scope_id 已配置)时,执行文档给出的 scope.mjs resolve(不带 --cwd,或传相对路径)仍会以 workspace_required 失败。这与本 head 在 shared/runtime.mjs 中改成的 const keys = settings.scopeId ? [] : [workspaceKey(input.cwd)] 不一致,也与同一提交新增的文档句「显式 Scope 解析不要求工作区键」(docs/en|zh/docs/integrations/kimi-code.md)以及 PR 正文的 "Explicit Scope resolution does not depend on a workspace key" 不一致。doctor 因为默认取 process.cwd() 不受影响,只有 Scope 脚本这一路仍把绝对 cwd 当作硬前置条件,而文档要求用 Scope 脚本的只读结果来确认当前工作区的 Scope。

证据(exact head 6a514384,探针 oceanbase-powercontext-pr-2043/probes/probe_scope_cwd.mjs;server_url 指向不可达端口以排除网络因素):

  • scope.mjs resolve(无 --cwd)→ {"status":"failed","code":"workspace_required"}
  • scope.mjs resolve --cwd relative/workspace → 同样 workspace_required
  • scope.mjs resolve --cwd <absolute> → server_unavailable,说明唯一阻塞就是 cwd 校验
  • doctor(无 --cwd)→ 直接进入网络阶段,未报 workspace_required

该分支没有测试覆盖:tests/plugin.test.mjs 的夹具 scope(action, scopeId, cwd = workspace) 始终追加 --cwd。

建议只在 bind / unbind(以及没有显式 Scope 时)计算绑定键,例如先 loadSettings() 再 const key = settings.scopeId && action === 'resolve' ? undefined : workspaceKey(input.cwd),并补一条「显式 Scope + 无 --cwd 的 resolve 成功」用例。

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.

修复状态(head c79f213a):本条已解除。

scripts/scope.mjs 现在把 workspaceKey(input.cwd) 移入 if (action !== 'resolve') 分支,resolve 不再无条件派生工作区键:显式 Scope 下不带 --cwd 或传相对路径都能解析,bind/unbind 仍要求绝对路径。

证据:

  • 新增用例 Scope script resolves explicit Scope without a workspace but binding operations still require one 覆盖 cwd=null 与 relative/workspace,断言 workspace 模式下 requests 为空、bind/unbind 返回 workspace_required。
  • 本机 node --test tests/plugin.test.mjs → 22/22 通过。
  • 单行变异(把 const key = workspaceKey(input.cwd) 移回 loadSettings() 之前)后该用例恰好转红(1 failed / 21 passed),说明回归测试确实锁住了本次修复。


export function workspaceKey(cwd) {
if (typeof cwd !== 'string' || !isAbsolute(cwd)) throw new Failure('workspace_required')
let directory = resolve(cwd)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Resolve symlinks before collapsing parent components

When --cwd contains a symlink followed by .., resolve(cwd) collapses the parent component before native resolution, so bind/unbind targets the lexical parent instead of the actual directory and can overwrite or clear another workspace's binding. Resolve the original absolute path first, preserving it through the doctor caller, and normalize lexically only if real-path resolution fails.

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.

平台范围补充,避免用 Windows 结果否定该问题:该差异只在 POSIX 成立。

  • WSL Ubuntu(真实 symlink):link → real/nested,输入 /tmp/pc2043probe/link/../other;先 normpath 再 realpath 得 /tmp/pc2043probe/other,直接 realpath 输入得 /tmp/pc2043probe/real/other ⇒ 与线程描述一致(探针 oceanbase-powercontext-pr-2043/probes/probe_symlink_dotdot.sh)。
  • Windows(junction):同样形状下 resolve() + realpathSync.native 与 realpathSync.native(原始输入) 结果相同——Win32 在接触 reparse point 之前就按词法折叠 ..(探针 probes/probe_symlink_dotdot.mjs),因此本机 Windows 复现不出该缺陷。
  • 触发条件收窄:需要 cwd 自身带「符号链接分量 + ..」;宿主传入的规范绝对路径通常不含 ..,主要来自用户手写 scope.mjs --cwd / doctor --cwd。
  • 覆盖情况:kimi-code-acceptance.yml 的 controlled-cli 矩阵为 [ubuntu-latest, windows-latest](无 macOS),现有夹具用的是不带 .. 的符号链接别名,故该分支无测试覆盖。

建议按该线程方向:先对原始绝对路径做 realpath,失败再退回词法规范化,并补一条含 .. 的别名用例。

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.

修复状态(head c79f213a):本条已解除,且与上一轮补充的平台范围一致。

shared/runtime.mjs::workspaceKey() 改为 realpathSync.native(cwd) 直接作用于原始输入,失败时才退回 resolve(cwd),不再先做词法 resolve() 折叠 ..。POSIX 下 link/../other 按符号链接目标解析;Windows 仍按原生语义落在词法父目录,与上一轮补充的探针结论一致。

文档与测试同步:

  • docs/{en,zh}/docs/integrations/kimi-code.md 增加「POSIX 先跟随符号链接、再解释后续 .. 分量;Windows 遵循原生路径语义」。
  • tests/plugin.test.mjs 新增 alias-parent + alias/../workspace 场景,按平台分别断言 scope-first(POSIX,经链接)与 scope-parent(Windows,词法父目录),并验证 bind/unbind 只影响对应身份。
  • tests/e2e/test_kimi_code_host.py 增加同名场景。

本机 Windows:node --test tests/plugin.test.mjs 22/22 通过;POSIX 分支由该 head 的 controlled-cli (ubuntu-latest) 覆盖(success)。

Copilot AI balanced review requested due to automatic review settings October 19, 2026 02:42

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants