ultra-builder-pro-cli 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { spawnSync } = require('node:child_process');
9
+
10
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
11
+ const ADAPTER = path.join(REPO_ROOT, 'hooks', 'adapters', 'kimi.py');
12
+
13
+ function run(feature, payload, args = []) {
14
+ return spawnSync('python3', [ADAPTER, feature, ...args], {
15
+ cwd: REPO_ROOT,
16
+ input: JSON.stringify(payload),
17
+ encoding: 'utf8',
18
+ });
19
+ }
20
+
21
+ test('Kimi hook adapter rejects non-workflow policy hooks without blocking the host', () => {
22
+ const result = run('block_dangerous_commands.py', {
23
+ session_id: 'session-1',
24
+ cwd: REPO_ROOT,
25
+ hook_event_name: 'PreToolUse',
26
+ tool_name: 'Shell',
27
+ tool_input: { command: 'git reset --hard' },
28
+ });
29
+ assert.equal(result.status, 0, result.stderr);
30
+ assert.match(JSON.parse(result.stdout).message, /refused unknown feature/);
31
+ });
32
+
33
+ test('Kimi hook adapter translates active edit context to the native message field', () => {
34
+ const project = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-kimi-hook-'));
35
+ try {
36
+ fs.mkdirSync(path.join(project, '.ultra'));
37
+ fs.writeFileSync(path.join(project, '.ultra', 'workflow-state.json'), JSON.stringify({
38
+ command: 'ultra-dev', task_id: 'task-7', step: '4.5', status: 'review_pending',
39
+ }));
40
+ const result = run('active_task_context.py', {
41
+ session_id: 'session-2',
42
+ cwd: project,
43
+ hook_event_name: 'PreToolUse',
44
+ tool_name: 'Edit',
45
+ tool_input: { file_path: 'src/a.py' },
46
+ });
47
+ assert.equal(result.status, 0, result.stderr);
48
+ const output = JSON.parse(result.stdout);
49
+ assert.match(output.message, /task-7/);
50
+ assert.equal(output.hookSpecificOutput, undefined);
51
+ } finally {
52
+ fs.rmSync(project, { recursive: true, force: true });
53
+ }
54
+ });
55
+
56
+ test('Kimi hook adapter emits the native deny contract for projection writes', () => {
57
+ const project = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-kimi-hook-deny-'));
58
+ try {
59
+ fs.mkdirSync(path.join(project, '.ultra'));
60
+ fs.writeFileSync(path.join(project, '.ultra', 'workflow-state.json'), JSON.stringify({
61
+ command: 'ultra-plan', task_id: 'plan', step: 'persist', status: 'active',
62
+ }));
63
+ const result = run('active_task_context.py', {
64
+ session_id: 'session-deny',
65
+ cwd: project,
66
+ hook_event_name: 'PreToolUse',
67
+ tool_name: 'Write',
68
+ tool_input: { file_path: '.ultra/tasks/tasks.json' },
69
+ });
70
+ assert.equal(result.status, 0, result.stderr);
71
+ const output = JSON.parse(result.stdout);
72
+ assert.equal(output.hookSpecificOutput.permissionDecision, 'deny');
73
+ assert.match(output.hookSpecificOutput.permissionDecisionReason, /\.ultra\/state\.db/);
74
+ } finally {
75
+ fs.rmSync(project, { recursive: true, force: true });
76
+ }
77
+ });
78
+
79
+ test('Kimi subagent fields are normalized for the existing lifecycle tracker', () => {
80
+ const script = [
81
+ 'import importlib.util, json, pathlib',
82
+ `p = pathlib.Path(${JSON.stringify(ADAPTER)})`,
83
+ 's = importlib.util.spec_from_file_location("kimi_hook_adapter", p)',
84
+ 'm = importlib.util.module_from_spec(s)',
85
+ 's.loader.exec_module(m)',
86
+ 'payload = {"hook_event_name":"SubagentStart","agent_name":"review-code","session_id":"s"}',
87
+ 'print(json.dumps(m.normalize_input(payload)))',
88
+ ].join('; ');
89
+ const result = spawnSync('python3', ['-c', script], { cwd: REPO_ROOT, encoding: 'utf8' });
90
+ assert.equal(result.status, 0, result.stderr);
91
+ assert.deepEqual(JSON.parse(result.stdout), {
92
+ hook_event_name: 'SubagentStart',
93
+ agent_name: 'review-code',
94
+ session_id: 's',
95
+ agent_id: 'review-code',
96
+ agent_type: 'review-code',
97
+ });
98
+ });
99
+
100
+ test('Kimi stop blocks and recovery context use the exact structured-output schema', () => {
101
+ const script = [
102
+ 'import importlib.util, json, pathlib',
103
+ `p = pathlib.Path(${JSON.stringify(ADAPTER)})`,
104
+ 's = importlib.util.spec_from_file_location("kimi_hook_adapter", p)',
105
+ 'm = importlib.util.module_from_spec(s)',
106
+ 's.loader.exec_module(m)',
107
+ 'values = [m.adapt_output({"decision":"block","reason":"finish gates"}, "Stop"), m.adapt_output({"additionalContext":"restored"}, "PostCompact")]',
108
+ 'print(json.dumps(values))',
109
+ ].join('; ');
110
+ const result = spawnSync('python3', ['-c', script], { cwd: REPO_ROOT, encoding: 'utf8' });
111
+ assert.equal(result.status, 0, result.stderr);
112
+ assert.deepEqual(JSON.parse(result.stdout), [
113
+ {
114
+ hookSpecificOutput: {
115
+ message: 'finish gates',
116
+ permissionDecision: 'deny',
117
+ permissionDecisionReason: 'finish gates',
118
+ },
119
+ },
120
+ { message: 'restored' },
121
+ ]);
122
+ });
@@ -0,0 +1,255 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+
9
+ const kimi = require('../kimi.js');
10
+ const {
11
+ CORE_PUBLIC_SKILLS,
12
+ WORKFLOW_HOOK_FILES,
13
+ skillsForRuntime,
14
+ } = require('../_shared/runtime-assets.cjs');
15
+
16
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
17
+ const PLUGIN_ID = 'ultra-builder-pro';
18
+
19
+ function mkTarget() {
20
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-kimi-'));
21
+ }
22
+
23
+ function layout(home) {
24
+ return {
25
+ pluginRoot: path.join(home, 'plugins', 'managed', PLUGIN_ID),
26
+ registryFile: path.join(home, 'plugins', 'installed.json'),
27
+ };
28
+ }
29
+
30
+ function readJson(file) {
31
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
32
+ }
33
+
34
+ test('install builds and registers one Kimi-native plugin without changing config.toml', () => {
35
+ const home = mkTarget();
36
+ const { pluginRoot, registryFile } = layout(home);
37
+ const configFile = path.join(home, 'config.toml');
38
+ const userConfig = '[[hooks]]\nevent = "Stop"\ncommand = "/user/hook.sh"\n';
39
+ try {
40
+ fs.writeFileSync(configFile, userConfig);
41
+ const report = kimi.install({ configDir: home, repoRoot: REPO_ROOT });
42
+
43
+ assert.equal(report.target, pluginRoot);
44
+ assert.equal(fs.readFileSync(configFile, 'utf8'), userConfig);
45
+ assert.ok(fs.existsSync(path.join(pluginRoot, '.ubp-managed')));
46
+ assert.ok(fs.existsSync(path.join(pluginRoot, 'provenance.json')));
47
+
48
+ const registry = readJson(registryFile);
49
+ assert.equal(registry.version, 1);
50
+ assert.equal(registry.plugins.length, 1);
51
+ assert.deepEqual(registry.plugins[0], {
52
+ id: PLUGIN_ID,
53
+ root: pluginRoot,
54
+ source: 'local-path',
55
+ enabled: true,
56
+ installedAt: registry.plugins[0].installedAt,
57
+ updatedAt: registry.plugins[0].updatedAt,
58
+ originalSource: REPO_ROOT,
59
+ });
60
+ } finally {
61
+ fs.rmSync(home, { recursive: true, force: true });
62
+ }
63
+ });
64
+
65
+ test('Kimi manifest exposes native skills, commands, hooks, session bootstrap, and MCP', () => {
66
+ const home = mkTarget();
67
+ const { pluginRoot } = layout(home);
68
+ try {
69
+ kimi.install({ configDir: home, repoRoot: REPO_ROOT });
70
+ const manifest = readJson(path.join(pluginRoot, 'kimi.plugin.json'));
71
+
72
+ assert.equal(manifest.name, PLUGIN_ID);
73
+ assert.deepEqual(manifest.skills, ['./skills']);
74
+ assert.deepEqual(manifest.commands, ['./commands']);
75
+ assert.deepEqual(manifest.sessionStart, { skill: 'using-ultra-builder-pro' });
76
+ assert.equal(manifest.agents, undefined, 'Kimi 0.26/0.27 has no custom-agent manifest field');
77
+ assert.deepEqual(manifest.mcpServers[PLUGIN_ID], {
78
+ transport: 'stdio',
79
+ command: process.platform === 'win32' ? 'node.exe' : 'env',
80
+ args: process.platform === 'win32'
81
+ ? ['./runtime/launch.cjs']
82
+ : ['node', './runtime/launch.cjs'],
83
+ enabled: true,
84
+ });
85
+
86
+ const hookText = JSON.stringify(manifest.hooks);
87
+ assert.match(hookText, /hooks\/adapters\/kimi\.py/);
88
+ for (const hook of WORKFLOW_HOOK_FILES) assert.match(hookText, new RegExp(hook.replace('.', '\\.')));
89
+ assert.doesNotMatch(hookText, /memory|recall|journal|prompt[_ -]?capture|block_dangerous|post_edit_guard/i);
90
+
91
+ const commands = fs.readdirSync(path.join(pluginRoot, 'commands')).sort();
92
+ assert.deepEqual(commands, CORE_PUBLIC_SKILLS.map((name) => `${name}.md`).sort());
93
+ const commandText = fs.readFileSync(path.join(pluginRoot, 'commands', 'ultra-init.md'), 'utf8');
94
+ assert.match(commandText, /Use the registered `ultra-init` skill/);
95
+ assert.match(commandText, /\$ARGUMENTS/);
96
+ } finally {
97
+ fs.rmSync(home, { recursive: true, force: true });
98
+ }
99
+ });
100
+
101
+ test('Kimi assets are allowlisted and adapted to native tools and paths', () => {
102
+ const home = mkTarget();
103
+ const { pluginRoot } = layout(home);
104
+ try {
105
+ kimi.install({ configDir: home, repoRoot: REPO_ROOT });
106
+
107
+ const skills = fs.readdirSync(path.join(pluginRoot, 'skills'), { withFileTypes: true })
108
+ .filter((entry) => entry.isDirectory()
109
+ && fs.existsSync(path.join(pluginRoot, 'skills', entry.name, 'SKILL.md')))
110
+ .map((entry) => entry.name)
111
+ .sort();
112
+ assert.deepEqual(skills, [...skillsForRuntime('kimi'), 'using-ultra-builder-pro'].sort());
113
+
114
+ const learn = fs.readFileSync(path.join(pluginRoot, 'skills', 'learn', 'SKILL.md'), 'utf8');
115
+ const review = fs.readFileSync(path.join(pluginRoot, 'skills', 'ultra-review', 'SKILL.md'), 'utf8');
116
+ const init = fs.readFileSync(path.join(pluginRoot, 'skills', 'ultra-init', 'SKILL.md'), 'utf8');
117
+ const bootstrap = fs.readFileSync(
118
+ path.join(pluginRoot, 'skills', 'using-ultra-builder-pro', 'SKILL.md'),
119
+ 'utf8',
120
+ );
121
+ assert.match(learn, /~\/.kimi-code\/skills\/learned-<pattern-slug>-unverified\/SKILL\.md/);
122
+ assert.match(review, /Kimi `AgentSwarm`/);
123
+ assert.match(review, /5 specialized reviewers/);
124
+ assert.doesNotMatch(review, /6 specialized|ALL 6/);
125
+ assert.match(review, /\$KIMI_PLUGIN_ROOT\/agents\/review-code\.md/);
126
+ assert.match(review, /`prompt_template` containing\s+`\{\{item\}\}`/);
127
+ assert.match(review, /selected reviewer names as the `items` array/);
128
+ assert.match(review, /Launch the coordinator with one foreground Kimi `Agent` call/);
129
+ assert.doesNotMatch(review, /background mode|Set Kimi `AgentSwarm` parallel execution|Launch review-coordinator with Kimi `AgentSwarm`/);
130
+ assert.match(init, /\| Kimi \| `\/ultra-builder-pro:ultra-init/);
131
+ assert.doesNotMatch(init, /codex exec "run \/ultra-init/);
132
+ assert.match(bootstrap, /\.ultra\/state\.db/);
133
+ assert.match(bootstrap, /external providers/);
134
+
135
+ const allPromptAssets = [];
136
+ for (const root of ['commands', 'skills', 'agents']) {
137
+ const pending = [path.join(pluginRoot, root)];
138
+ while (pending.length) {
139
+ const current = pending.pop();
140
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
141
+ const file = path.join(current, entry.name);
142
+ if (entry.isDirectory()) pending.push(file);
143
+ else if (entry.isFile() && /\.(?:md|json)$/.test(entry.name)) allPromptAssets.push(file);
144
+ }
145
+ }
146
+ }
147
+ const foreign = /~\/.claude|~\/.codex|~\/.config\/opencode|CLAUDE\.md|TaskCreate|TaskUpdate|TaskList|AskUserQuestion|run_in_background:\s*true|\$CLAUDE_PLUGIN_ROOT|Kimi `Shell`/;
148
+ for (const file of allPromptAssets) {
149
+ const text = fs.readFileSync(file, 'utf8');
150
+ assert.doesNotMatch(text, foreign, file);
151
+ assert.doesNotMatch(text, /(^|[\s`("'])\/ultra-(?!builder-pro:)[a-z]/m, file);
152
+ }
153
+ } finally {
154
+ fs.rmSync(home, { recursive: true, force: true });
155
+ }
156
+ });
157
+
158
+ test('reinstall preserves unrelated records and existing Kimi capability choices', () => {
159
+ const home = mkTarget();
160
+ const { registryFile } = layout(home);
161
+ try {
162
+ fs.mkdirSync(path.dirname(registryFile), { recursive: true });
163
+ fs.writeFileSync(registryFile, JSON.stringify({
164
+ version: 1,
165
+ plugins: [{
166
+ id: 'mine', root: '/tmp/mine', source: 'local-path', enabled: true,
167
+ installedAt: '2026-01-01T00:00:00.000Z', originalSource: '/tmp/mine',
168
+ }],
169
+ }, null, 2));
170
+
171
+ kimi.install({ configDir: home, repoRoot: REPO_ROOT });
172
+ const first = readJson(registryFile);
173
+ const ultra = first.plugins.find((entry) => entry.id === PLUGIN_ID);
174
+ ultra.enabled = false;
175
+ ultra.capabilities = { mcpServers: { [PLUGIN_ID]: { enabled: false } } };
176
+ fs.writeFileSync(registryFile, JSON.stringify(first, null, 2));
177
+
178
+ kimi.install({ configDir: home, repoRoot: REPO_ROOT });
179
+ const second = readJson(registryFile);
180
+ assert.deepEqual(second.plugins.map((entry) => entry.id).sort(), ['mine', PLUGIN_ID]);
181
+ const refreshed = second.plugins.find((entry) => entry.id === PLUGIN_ID);
182
+ assert.equal(refreshed.installedAt, ultra.installedAt);
183
+ assert.equal(refreshed.enabled, false);
184
+ assert.deepEqual(refreshed.capabilities, ultra.capabilities);
185
+ } finally {
186
+ fs.rmSync(home, { recursive: true, force: true });
187
+ }
188
+ });
189
+
190
+ test('uninstall removes only the owned Kimi plugin record and managed root', () => {
191
+ const home = mkTarget();
192
+ const { pluginRoot, registryFile } = layout(home);
193
+ try {
194
+ kimi.install({ configDir: home, repoRoot: REPO_ROOT });
195
+ const registry = readJson(registryFile);
196
+ registry.plugins.unshift({
197
+ id: 'mine', root: '/tmp/mine', source: 'local-path', enabled: true,
198
+ installedAt: '2026-01-01T00:00:00.000Z', originalSource: '/tmp/mine',
199
+ });
200
+ fs.writeFileSync(registryFile, JSON.stringify(registry, null, 2));
201
+
202
+ kimi.uninstall({ configDir: home });
203
+ assert.equal(fs.existsSync(pluginRoot), false);
204
+ assert.deepEqual(readJson(registryFile).plugins.map((entry) => entry.id), ['mine']);
205
+ } finally {
206
+ fs.rmSync(home, { recursive: true, force: true });
207
+ }
208
+ });
209
+
210
+ test('install and uninstall refuse to replace or remove an unmanaged Kimi root', () => {
211
+ const home = mkTarget();
212
+ const { pluginRoot, registryFile } = layout(home);
213
+ try {
214
+ fs.mkdirSync(pluginRoot, { recursive: true });
215
+ fs.writeFileSync(path.join(pluginRoot, 'user.txt'), 'keep');
216
+ assert.throws(
217
+ () => kimi.install({ configDir: home, repoRoot: REPO_ROOT }),
218
+ /unmanaged Kimi plugin/,
219
+ );
220
+
221
+ fs.mkdirSync(path.dirname(registryFile), { recursive: true });
222
+ fs.writeFileSync(registryFile, JSON.stringify({
223
+ version: 1,
224
+ plugins: [{
225
+ id: PLUGIN_ID, root: pluginRoot, source: 'local-path', enabled: true,
226
+ installedAt: '2026-01-01T00:00:00.000Z', originalSource: '/tmp/foreign',
227
+ }],
228
+ }));
229
+ assert.throws(() => kimi.uninstall({ configDir: home }), /unmanaged Kimi plugin/);
230
+ assert.equal(fs.readFileSync(path.join(pluginRoot, 'user.txt'), 'utf8'), 'keep');
231
+ } finally {
232
+ fs.rmSync(home, { recursive: true, force: true });
233
+ }
234
+ });
235
+
236
+ test('doctor validates registration and provenance without mutating the Kimi home', () => {
237
+ const home = mkTarget();
238
+ const { registryFile } = layout(home);
239
+ try {
240
+ kimi.install({ configDir: home, repoRoot: REPO_ROOT });
241
+ const before = fs.readFileSync(registryFile);
242
+ const healthy = kimi.doctor({ configDir: home, repoRoot: REPO_ROOT });
243
+ assert.equal(healthy.status, 'healthy', JSON.stringify(healthy, null, 2));
244
+ assert.deepEqual(fs.readFileSync(registryFile), before);
245
+
246
+ const registry = readJson(registryFile);
247
+ registry.plugins[0].enabled = false;
248
+ fs.writeFileSync(registryFile, JSON.stringify(registry, null, 2));
249
+ const degraded = kimi.doctor({ configDir: home, repoRoot: REPO_ROOT });
250
+ assert.equal(degraded.status, 'degraded');
251
+ assert.ok(degraded.issues.some((issue) => issue.code === 'PLUGIN_REGISTRATION_INVALID'));
252
+ } finally {
253
+ fs.rmSync(home, { recursive: true, force: true });
254
+ }
255
+ });
package/bin/handbook.js CHANGED
@@ -14,8 +14,8 @@ const {
14
14
  function usage() {
15
15
  return [
16
16
  'Usage:',
17
- ' ubp-handbook preview --runtime <claude|codex|opencode> [--file <path>]',
18
- ' ubp-handbook apply --runtime <claude|codex|opencode> [--file <path>]',
17
+ ' ubp-handbook preview --runtime <claude|codex|opencode|kimi> [--file <path>]',
18
+ ' ubp-handbook apply --runtime <claude|codex|opencode|kimi> [--file <path>]',
19
19
  '',
20
20
  'The apply command creates a timestamped backup before changing an existing file.',
21
21
  ].join('\n');
package/bin/install.js CHANGED
@@ -4,14 +4,14 @@
4
4
  * ultra-builder-pro-cli — multi-runtime installer.
5
5
  *
6
6
  * Distributes Ultra Builder Pro assets — commands, agents, skills, hooks,
7
- * MCP server — to Claude Code, OpenCode, and Codex CLI via
7
+ * MCP server — to Claude Code, OpenCode, Codex CLI, and Kimi Code via
8
8
  * runtime-specific adapters under adapters/. Install is idempotent and
9
9
  * uses atomic writes; uninstall reverses via sentinel/manifest blocks.
10
10
  *
11
11
  * Usage:
12
12
  * npx ultra-builder-pro-cli [options]
13
13
  *
14
- * --claude / --opencode / --codex select runtime(s)
14
+ * --claude / --opencode / --codex / --kimi select runtime(s)
15
15
  * --all install to all supported runtimes
16
16
  * -g, --global install to runtime's global config dir
17
17
  * -l, --local install into current working directory
@@ -58,7 +58,7 @@ function printBanner() {
58
58
  paint('cyan', ' ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝'),
59
59
  '',
60
60
  ` ${paint('bold', 'Ultra Builder Pro CLI')} ${paint('dim', 'v' + pkg.version)}`,
61
- ` ${paint('dim', 'Multi-runtime installer for Claude Code, OpenCode, and Codex')}`,
61
+ ` ${paint('dim', 'Multi-runtime installer for Claude Code, OpenCode, Codex, and Kimi Code')}`,
62
62
  '',
63
63
  ];
64
64
  console.log(banner.join('\n'));
@@ -71,6 +71,7 @@ function printHelp() {
71
71
  ${paint('cyan', '--claude')} Claude Code
72
72
  ${paint('cyan', '--opencode')} OpenCode
73
73
  ${paint('cyan', '--codex')} Codex CLI (OpenAI)
74
+ ${paint('cyan', '--kimi')} Kimi Code (Moonshot AI)
74
75
  ${paint('cyan', '--all')} all supported runtimes
75
76
 
76
77
  ${paint('yellow', 'Scope:')}
@@ -95,6 +96,9 @@ function printHelp() {
95
96
  ${paint('dim', '# Uninstall from OpenCode')}
96
97
  npx ultra-builder-pro-cli --opencode --global --uninstall
97
98
 
99
+ ${paint('dim', '# Install to Kimi Code globally')}
100
+ npx ultra-builder-pro-cli --kimi --global
101
+
98
102
  ${paint('dim', '# Verify all host installations without changing them')}
99
103
  npx ultra-builder-pro-cli --all --global --doctor
100
104
  `);
@@ -120,6 +124,7 @@ function parseArgs(argv) {
120
124
  case '--claude': runtimes.add('claude'); break;
121
125
  case '--opencode': runtimes.add('opencode'); break;
122
126
  case '--codex': runtimes.add('codex'); break;
127
+ case '--kimi': runtimes.add('kimi'); break;
123
128
  case '--all':
124
129
  SUPPORTED_RUNTIMES.forEach(r => runtimes.add(r));
125
130
  break;
@@ -199,7 +204,7 @@ async function main() {
199
204
  if (flags.json && !flags.doctor) bail('--json is available only with --doctor');
200
205
 
201
206
  if (runtimes.length === 0) {
202
- bail('no runtime selected; use --claude / --opencode / --codex / --all');
207
+ bail('no runtime selected; use --claude / --opencode / --codex / --kimi / --all');
203
208
  }
204
209
 
205
210
  const scope = resolveScope(flags);
@@ -47,7 +47,7 @@ function optInAllowed(settings) {
47
47
  }
48
48
 
49
49
  function parseRuntimes() {
50
- const raw = process.env.UBP_ORCH_RUNTIMES || 'claude,opencode,codex';
50
+ const raw = process.env.UBP_ORCH_RUNTIMES || 'claude,opencode,codex,kimi';
51
51
  const runtimes = [...new Set(raw.split(',').map((s) => s.trim()).filter(Boolean))];
52
52
  const invalid = runtimes.filter((runtime) => !isSupportedRuntime(runtime));
53
53
  if (invalid.length > 0) {
@@ -1,7 +1,7 @@
1
1
  # Ultra Builder Pro — Agent Context
2
2
 
3
- Current shared runtime contract for the native Claude Code, OpenCode, and Codex
4
- plugins.
3
+ Current shared runtime contract for the native Claude Code, OpenCode, Codex,
4
+ and Kimi Code plugins.
5
5
 
6
6
  ## 1. Ownership boundary
7
7
 
@@ -12,7 +12,8 @@ Ultra Builder Pro owns only:
12
12
  `ultra-think`, plus the daily `ultra-change` and diagnostic `ultra-doctor`;
13
13
  - four internal agent-only rule skills: `code-review-expert`, `security-rules`,
14
14
  `integration-rules`, and `testing-rules`;
15
- - the host-specific collaboration companions and `ultra-verify`;
15
+ - the host-specific collaboration companions, `ultra-verify`, and the minimal
16
+ Kimi session bootstrap required to establish the shared runtime boundary;
16
17
  - bounded review/debug agents, workflow-only hooks, MCP task state, and the
17
18
  portable CLI/orchestrator.
18
19
 
@@ -68,6 +69,7 @@ fixtures. Do not add an ad-hoc server handler first.
68
69
  | Claude Code | `.claude-plugin/plugin.json`, native commands/skills/agents, `.mcp.json` | `/ultra-*`, `/learn` | native `hooks/hooks.json` | `codex-collab`, `ultra-verify` |
69
70
  | Codex | personal plugin with `.codex-plugin/plugin.json`, namespaced skills, TOML agents, `.mcp.json` | `$ultra-builder-pro:ultra-*`, `$ultra-builder-pro:learn` | native `hooks/hooks.json` through the Codex wire adapter | `cc-collab`, `ultra-verify` |
70
71
  | OpenCode | config bundle plus native JavaScript plugin | `/ultra-*`, `/learn` | `event`, system transform, compaction, and tool lifecycle handlers | `cc-collab`, `codex-collab`, `ultra-verify` |
72
+ | Kimi Code 0.26+ | managed `kimi.plugin.json` plugin with commands, skills, hooks, and MCP | `/ultra-builder-pro:ultra-*`, `/ultra-builder-pro:learn` | native hooks through the Kimi wire adapter | `cc-collab`, `codex-collab`, `ultra-verify` |
71
73
 
72
74
  The current host remains primary. Collaboration skills call another runtime
73
75
  only when explicitly requested, use it as a read-only advisor, and return the
@@ -10,7 +10,7 @@
10
10
 
11
11
  `ultra-builder-pro-cli` is a multi-runtime plugin suite that **distributes** the
12
12
  Ultra Builder Pro engineering loop (skills + commands + workflow hooks) as
13
- native Claude Code, OpenCode, and Codex plugins, and
13
+ native Claude Code, OpenCode, Codex, and Kimi Code plugins, and
14
14
  **runs** that loop with isolated sessions sharing one authoritative state
15
15
  store (`.ultra/state.db`).
16
16
 
@@ -23,7 +23,7 @@ the supported hosts share.
23
23
  ┌──────────────────────────┐
24
24
  │ runtime CLI │
25
25
  │ (claude / opencode / │
26
- │ codex) │
26
+ │ codex / kimi) │
27
27
  └────────────┬─────────────┘
28
28
  │
29
29
  ┌───────────────────┼────────────────────┐
@@ -156,7 +156,7 @@ backup-first mechanical recovery when explicitly requested.
156
156
 
157
157
  ## 5. Sessions — the execution unit
158
158
 
159
- A **session** is the standard unit of execution across all three runtimes
159
+ A **session** is the standard unit of execution across all four runtimes
160
160
  (D20). One session =
161
161
 
162
162
  - a fresh OS process for the runtime,
@@ -189,7 +189,7 @@ durable authority alongside `.ultra/state.db`.
189
189
 
190
190
  ## 7. Installation provenance — read-only cross-host diagnosis
191
191
 
192
- Every Claude Code, OpenCode, and Codex adapter writes a normalized
192
+ Every Claude Code, OpenCode, Codex, and Kimi Code adapter writes a normalized
193
193
  `provenance.json` for the assets it owns. The manifest records adapter/package
194
194
  identity, source metadata, per-file SHA-256 hashes, an aggregate digest, and the
195
195
  host-specific plugin, MCP, hook, launcher, and runtime contracts expected at
@@ -6,12 +6,12 @@
6
6
  > 插件只保留 12 个 Ultra 公共 workflow(新增 daily `ultra-change` 与诊断
7
7
  > `ultra-doctor`)、4 个 agent-only 规则 skill、宿主专属 collab companion、
8
8
  > workflow/change hook 与 `.ultra/state.db` 权威。Memory 和代码图谱内容全部由外部
9
- > provider 所有,Ultra 只保存 provider 元数据引用。Claude Code、Codex、OpenCode
10
- > 采用各自 native plugin 呈现,其他旧 runtime adapter 已退役。下文所有与此冲突的
9
+ > provider 所有,Ultra 只保存 provider 元数据引用。Claude Code、Codex、OpenCode、
10
+ > Kimi Code 采用各自 native plugin 呈现,其他旧 runtime adapter 已退役。下文所有与此冲突的
11
11
  > memory/hindsight 章节仅作为历史实施记录,不再是当前需求或运行时契约。
12
12
 
13
- **当前状态**:v0.8.1;三宿主 native plugin、29 个 live MCP tool、
14
- schema 9.0(15 表)、持续 change/context/convergence/doctor、incident debug lane、
13
+ **当前状态**:v0.9.0;四宿主 native plugin、29 个 live MCP tool、
14
+ schema 9.1(15 表)、持续 change/context/convergence/doctor、incident debug lane、
15
15
  checkpoint 恢复消费与跨宿主安装 provenance/doctor 已落地;npm 发布由
16
16
  `v*.*.*` tag 触发。以下 Phase 0-9 正文保留为 2026-04 的实施历史,不再作为
17
17
  当前 runtime 或发布状态的事实来源。
package/docs/ROADMAP.md CHANGED
@@ -6,14 +6,14 @@
6
6
  > when historical plan text differs.
7
7
 
8
8
  **Goal**: distribute the Ultra Builder Pro engineering loop as native Claude
9
- Code, OpenCode, and Codex plugins, and run it with isolated sessions sharing one
10
- authoritative workflow store.
9
+ Code, OpenCode, Codex, and Kimi Code plugins, and run it with isolated sessions
10
+ sharing one authoritative workflow store.
11
11
 
12
- **First-class runtimes**: Claude Code · OpenCode · Codex.
12
+ **First-class runtimes**: Claude Code · OpenCode · Codex · Kimi Code.
13
13
 
14
14
  **Distribution channels (v1.0)**: npm · Homebrew · pip.
15
15
 
16
- **Release line**: `0.7.x`; every release is gated by `npm run verify:release`.
16
+ **Release line**: `0.9.x`; every release is gated by `npm run verify:release`.
17
17
 
18
18
  ---
19
19