ultra-builder-pro-cli 0.14.0 → 0.16.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.
Files changed (163) hide show
  1. package/.ultra-template/docs/research/README.md +11 -7
  2. package/.ultra-template/specs/architecture.md +36 -36
  3. package/.ultra-template/specs/discovery.md +46 -33
  4. package/.ultra-template/specs/product.md +38 -29
  5. package/.ultra-template/specs/research-distillate.md +48 -0
  6. package/.ultra-template/tasks/contexts/TEMPLATE.md +11 -28
  7. package/CHANGELOG.md +77 -1
  8. package/README.md +46 -24
  9. package/adapters/_shared/codex-assets.cjs +6 -2
  10. package/adapters/_shared/file-ops.cjs +6 -2
  11. package/adapters/_shared/handbook.cjs +2 -1
  12. package/adapters/claude.js +3 -2
  13. package/adapters/codex.js +1 -0
  14. package/adapters/kimi.js +3 -2
  15. package/adapters/opencode.js +3 -2
  16. package/agents/review-coordinator.md +9 -6
  17. package/commands/ultra-change.md +1 -1
  18. package/commands/ultra-plan.md +1 -1
  19. package/commands/ultra-research.md +1 -1
  20. package/commands/ultra-think.md +2 -2
  21. package/docs/AGENT-CONTEXT.md +43 -19
  22. package/docs/ARCHITECTURE.md +54 -13
  23. package/docs/PLAN.zh-CN.md +4 -2
  24. package/docs/ROADMAP.md +10 -4
  25. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  26. package/docs/STATE-DB-ACCESS-POLICY.md +33 -29
  27. package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
  28. package/docs/WORKFLOW-LIFECYCLE.md +342 -0
  29. package/hooks/README.md +6 -1
  30. package/hooks/health_check.py +12 -3
  31. package/hooks/subagent_tracker.py +52 -77
  32. package/mcp-server/hook-event.cjs +34 -0
  33. package/mcp-server/lib/archive-journal.cjs +17 -5
  34. package/mcp-server/lib/baseline-workflow.cjs +158 -18
  35. package/mcp-server/lib/change-workflow.cjs +598 -85
  36. package/mcp-server/lib/context-spine.cjs +305 -25
  37. package/mcp-server/lib/decision-dialogue.cjs +713 -0
  38. package/mcp-server/lib/doctor.cjs +42 -3
  39. package/mcp-server/lib/init-project.cjs +210 -16
  40. package/mcp-server/lib/plan-store.cjs +4 -1
  41. package/mcp-server/lib/pricing.cjs +7 -10
  42. package/mcp-server/lib/project-breadcrumb.cjs +15 -0
  43. package/mcp-server/lib/projector.cjs +57 -3
  44. package/mcp-server/lib/spec-learning.cjs +79 -2
  45. package/mcp-server/lib/state-db.cjs +168 -8
  46. package/mcp-server/lib/state-ops.cjs +160 -9
  47. package/mcp-server/lib/workflow-state.cjs +2305 -0
  48. package/mcp-server/server.cjs +215 -15
  49. package/orchestrator/daemon.cjs +1 -2
  50. package/orchestrator/dispatch-rules.cjs +5 -31
  51. package/orchestrator/planner/plan-builder.cjs +21 -12
  52. package/package.json +13 -3
  53. package/skills/ultra-change/SKILL.md +94 -80
  54. package/skills/ultra-change/references/change-contract.md +36 -0
  55. package/skills/ultra-deliver/SKILL.md +76 -79
  56. package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
  57. package/skills/ultra-dev/SKILL.md +62 -59
  58. package/skills/ultra-doctor/SKILL.md +61 -82
  59. package/skills/ultra-init/SKILL.md +101 -103
  60. package/skills/ultra-init/references/brownfield-adoption.md +2 -2
  61. package/skills/ultra-plan/SKILL.md +99 -85
  62. package/skills/ultra-plan/references/semantic-preflight.md +18 -0
  63. package/skills/ultra-research/SKILL.md +102 -60
  64. package/skills/ultra-research/references/00-problem-validation.md +36 -0
  65. package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
  66. package/skills/ultra-research/references/02-market-assessment.md +30 -0
  67. package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
  68. package/skills/ultra-research/references/04-product-strategy.md +32 -0
  69. package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
  70. package/skills/ultra-research/references/10-user-personas.md +29 -0
  71. package/skills/ultra-research/references/11-user-scenarios.md +28 -0
  72. package/skills/ultra-research/references/20-user-stories.md +30 -0
  73. package/skills/ultra-research/references/21-features-scope.md +29 -0
  74. package/skills/ultra-research/references/22-success-metrics.md +28 -0
  75. package/skills/ultra-research/references/30-architecture-context.md +30 -0
  76. package/skills/ultra-research/references/31-solution-strategy.md +28 -0
  77. package/skills/ultra-research/references/32-building-blocks.md +28 -0
  78. package/skills/ultra-research/references/40-deployment.md +29 -0
  79. package/skills/ultra-research/references/41-quality-risks.md +28 -0
  80. package/skills/ultra-research/references/99-synthesis.md +35 -0
  81. package/skills/ultra-review/SKILL.md +63 -73
  82. package/skills/ultra-review/references/review-modes.md +27 -0
  83. package/skills/ultra-review/references/unified-schema.md +25 -0
  84. package/skills/ultra-review/scripts/review_wait.py +40 -3
  85. package/skills/ultra-status/SKILL.md +41 -41
  86. package/skills/ultra-test/SKILL.md +54 -55
  87. package/skills/ultra-think/SKILL.md +26 -12
  88. package/skills/ultra-think/references/decision-dialogue.md +130 -0
  89. package/skills/ultra-verify/SKILL.md +1 -1
  90. package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
  91. package/spec/cli-protocol.md +17 -3
  92. package/spec/mcp-tools.yaml +683 -64
  93. package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
  94. package/spec/schemas/delivery-report.v1.schema.json +111 -0
  95. package/spec/schemas/mcp-tools.schema.json +1 -1
  96. package/spec/schemas/state-db.sql +140 -1
  97. package/spec/schemas/tasks.v4.5.schema.json +8 -1
  98. package/spec/schemas/test-report.v1.schema.json +194 -0
  99. package/ultra-tools/cli.cjs +1 -1
  100. package/ultra-tools/commands/migrate.cjs +3 -3
  101. package/ultra-tools/commands/status.cjs +228 -12
  102. package/.ultra-template/delivery-report.json +0 -9
  103. package/.ultra-template/test-report.json +0 -8
  104. package/adapters/_shared/tests/file-ops.test.cjs +0 -54
  105. package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
  106. package/adapters/_shared/tests/handbook.test.cjs +0 -101
  107. package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
  108. package/adapters/_shared/tests/provenance.test.cjs +0 -133
  109. package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
  110. package/adapters/_shared/tests/runtime-assets.test.cjs +0 -149
  111. package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
  112. package/adapters/_shared/tests/validate.test.cjs +0 -30
  113. package/adapters/tests/claude.test.cjs +0 -138
  114. package/adapters/tests/codex-hook.test.cjs +0 -142
  115. package/adapters/tests/codex.test.cjs +0 -430
  116. package/adapters/tests/kimi-hook.test.cjs +0 -149
  117. package/adapters/tests/kimi.test.cjs +0 -259
  118. package/adapters/tests/opencode.test.cjs +0 -357
  119. package/mcp-server/lib/archive-journal.test.cjs +0 -35
  120. package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
  121. package/mcp-server/lib/change-workflow.test.cjs +0 -971
  122. package/mcp-server/lib/context-spine.test.cjs +0 -93
  123. package/mcp-server/lib/doctor.test.cjs +0 -148
  124. package/mcp-server/lib/plan-store.test.cjs +0 -132
  125. package/mcp-server/lib/prd-parser.test.cjs +0 -82
  126. package/mcp-server/lib/project-breadcrumb.test.cjs +0 -112
  127. package/mcp-server/lib/runtime-state.test.cjs +0 -92
  128. package/mcp-server/lib/spec-learning.test.cjs +0 -67
  129. package/mcp-server/lib/state-db.test.cjs +0 -314
  130. package/mcp-server/lib/task-expander.test.cjs +0 -169
  131. package/mcp-server/lib/topo.test.cjs +0 -158
  132. package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
  133. package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
  134. package/mcp-server/tests/concurrency-update.test.cjs +0 -96
  135. package/mcp-server/tests/concurrency.test.cjs +0 -112
  136. package/mcp-server/tests/db-ops.test.cjs +0 -121
  137. package/mcp-server/tests/init-project.test.cjs +0 -393
  138. package/mcp-server/tests/migrate.test.cjs +0 -232
  139. package/mcp-server/tests/projector.test.cjs +0 -162
  140. package/mcp-server/tests/server-contract.test.cjs +0 -1467
  141. package/mcp-server/tests/staleness.test.cjs +0 -190
  142. package/mcp-server/tests/state-db.test.cjs +0 -106
  143. package/mcp-server/tests/state-ops.test.cjs +0 -449
  144. package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
  145. package/mcp-server/tests/telemetry.test.cjs +0 -219
  146. package/orchestrator/planner/plan-builder.test.cjs +0 -165
  147. package/orchestrator/tests/auto-merge.test.cjs +0 -216
  148. package/orchestrator/tests/daemon.test.cjs +0 -311
  149. package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
  150. package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
  151. package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
  152. package/orchestrator/tests/recovery.test.cjs +0 -190
  153. package/orchestrator/tests/session-runner.test.cjs +0 -314
  154. package/orchestrator/tests/skill-miner.test.cjs +0 -147
  155. package/orchestrator/tests/worktree-manager.test.cjs +0 -148
  156. package/skills/ultra-research/references/architecture.md +0 -38
  157. package/skills/ultra-research/references/problem-product.md +0 -39
  158. package/skills/ultra-research/references/quality-delivery.md +0 -34
  159. package/skills/ultra-research/references/synthesis.md +0 -38
  160. package/skills/ultra-research/references/users-scope.md +0 -40
  161. package/ultra-tools/legacy-memory.test.cjs +0 -80
  162. package/ultra-tools/status-command.test.cjs +0 -181
  163. package/ultra-tools/system-command.test.cjs +0 -309
@@ -1,149 +0,0 @@
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
- const { initStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
10
- const { createChange } = require('../../mcp-server/lib/change-workflow.cjs');
11
- const { createTask } = require('../../mcp-server/lib/state-ops.cjs');
12
- const { compileRoleContext } = require('../../mcp-server/lib/context-spine.cjs');
13
-
14
- const REPO_ROOT = path.resolve(__dirname, '..', '..');
15
- const ADAPTER = path.join(REPO_ROOT, 'hooks', 'adapters', 'kimi.py');
16
-
17
- function run(feature, payload, args = []) {
18
- return spawnSync('python3', [ADAPTER, feature, ...args], {
19
- cwd: REPO_ROOT,
20
- input: JSON.stringify(payload),
21
- encoding: 'utf8',
22
- });
23
- }
24
-
25
- function seedContext(project, taskId = 'task-7') {
26
- const state = initStateDb(path.join(project, '.ultra', 'state.db'));
27
- state.db.prepare(
28
- `INSERT INTO baselines
29
- (id, project_name, mode, status, approved_by, approval_note, converged_at)
30
- VALUES ('baseline', 'fixture', 'greenfield', 'ready', 'test', 'fixture', ?)`,
31
- ).run(new Date().toISOString());
32
- const { change } = createChange(state.db, {
33
- id: 'hook-change', title: 'Hook change', kind: 'quick',
34
- intent: 'Inject the authoritative task.',
35
- docs_impact: { status: 'none', rationale: 'fixture' },
36
- }, { rootDir: project });
37
- const task = createTask(state.db, {
38
- id: taskId, title: 'Hook task', type: 'bugfix', priority: 'P0', change_id: change.id,
39
- });
40
- compileRoleContext(state.db, {
41
- input: {
42
- task_id: task.id, role: 'implement', gate: 'implementation',
43
- execution_contract: {
44
- slice_kind: 'tracer_bullet', public_seam: 'hook injection',
45
- verification_command: 'node --test adapters/tests/kimi-hook.test.cjs',
46
- },
47
- next_action: 'Continue the authoritative hook task.',
48
- },
49
- change, tasks: [task], rootDir: project,
50
- });
51
- closeStateDb(state.db);
52
- }
53
-
54
- test('Kimi hook adapter rejects non-workflow policy hooks without blocking the host', () => {
55
- const result = run('block_dangerous_commands.py', {
56
- session_id: 'session-1',
57
- cwd: REPO_ROOT,
58
- hook_event_name: 'PreToolUse',
59
- tool_name: 'Shell',
60
- tool_input: { command: 'git reset --hard' },
61
- });
62
- assert.equal(result.status, 0, result.stderr);
63
- assert.match(JSON.parse(result.stdout).message, /refused unknown feature/);
64
- });
65
-
66
- test('Kimi hook adapter translates active edit context to the native message field', () => {
67
- const project = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-kimi-hook-'));
68
- try {
69
- seedContext(project);
70
- const result = run('active_task_context.py', {
71
- session_id: 'session-2',
72
- cwd: project,
73
- hook_event_name: 'PreToolUse',
74
- tool_name: 'Edit',
75
- tool_input: { file_path: 'src/a.py' },
76
- });
77
- assert.equal(result.status, 0, result.stderr);
78
- const output = JSON.parse(result.stdout);
79
- assert.match(output.message, /task-7/);
80
- assert.equal(output.hookSpecificOutput, undefined);
81
- } finally {
82
- fs.rmSync(project, { recursive: true, force: true });
83
- }
84
- });
85
-
86
- test('Kimi hook adapter emits the native deny contract for projection writes', () => {
87
- const project = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-kimi-hook-deny-'));
88
- try {
89
- fs.mkdirSync(path.join(project, '.ultra'));
90
- const result = run('active_task_context.py', {
91
- session_id: 'session-deny',
92
- cwd: project,
93
- hook_event_name: 'PreToolUse',
94
- tool_name: 'Write',
95
- tool_input: { file_path: '.ultra/tasks/tasks.json' },
96
- });
97
- assert.equal(result.status, 0, result.stderr);
98
- const output = JSON.parse(result.stdout);
99
- assert.equal(output.hookSpecificOutput.permissionDecision, 'deny');
100
- assert.match(output.hookSpecificOutput.permissionDecisionReason, /\.ultra\/state\.db/);
101
- } finally {
102
- fs.rmSync(project, { recursive: true, force: true });
103
- }
104
- });
105
-
106
- test('Kimi subagent fields are normalized for the existing lifecycle tracker', () => {
107
- const script = [
108
- 'import importlib.util, json, pathlib',
109
- `p = pathlib.Path(${JSON.stringify(ADAPTER)})`,
110
- 's = importlib.util.spec_from_file_location("kimi_hook_adapter", p)',
111
- 'm = importlib.util.module_from_spec(s)',
112
- 's.loader.exec_module(m)',
113
- 'payload = {"hook_event_name":"SubagentStart","agent_name":"review-code","session_id":"s"}',
114
- 'print(json.dumps(m.normalize_input(payload)))',
115
- ].join('; ');
116
- const result = spawnSync('python3', ['-c', script], { cwd: REPO_ROOT, encoding: 'utf8' });
117
- assert.equal(result.status, 0, result.stderr);
118
- assert.deepEqual(JSON.parse(result.stdout), {
119
- hook_event_name: 'SubagentStart',
120
- agent_name: 'review-code',
121
- session_id: 's',
122
- agent_id: 'review-code',
123
- agent_type: 'review-code',
124
- });
125
- });
126
-
127
- test('Kimi adapter preserves an explicit Stop deny and recovery context schema', () => {
128
- const script = [
129
- 'import importlib.util, json, pathlib',
130
- `p = pathlib.Path(${JSON.stringify(ADAPTER)})`,
131
- 's = importlib.util.spec_from_file_location("kimi_hook_adapter", p)',
132
- 'm = importlib.util.module_from_spec(s)',
133
- 's.loader.exec_module(m)',
134
- 'values = [m.adapt_output({"decision":"block","reason":"finish gates"}, "Stop"), m.adapt_output({"additionalContext":"restored"}, "PostCompact")]',
135
- 'print(json.dumps(values))',
136
- ].join('; ');
137
- const result = spawnSync('python3', ['-c', script], { cwd: REPO_ROOT, encoding: 'utf8' });
138
- assert.equal(result.status, 0, result.stderr);
139
- assert.deepEqual(JSON.parse(result.stdout), [
140
- {
141
- hookSpecificOutput: {
142
- message: 'finish gates',
143
- permissionDecision: 'deny',
144
- permissionDecisionReason: 'finish gates',
145
- },
146
- },
147
- { message: 'restored' },
148
- ]);
149
- });
@@ -1,259 +0,0 @@
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.filter((value) => value !== 'context_spine.py')) {
89
- assert.match(hookText, new RegExp(hook.replace('.', '\\.')));
90
- }
91
- assert.ok(fs.existsSync(path.join(pluginRoot, 'hooks', 'context_spine.py')));
92
- assert.doesNotMatch(hookText, /memory|recall|journal|prompt[_ -]?capture|block_dangerous|post_edit_guard/i);
93
-
94
- const commands = fs.readdirSync(path.join(pluginRoot, 'commands')).sort();
95
- assert.deepEqual(commands, CORE_PUBLIC_SKILLS.map((name) => `${name}.md`).sort());
96
- const commandText = fs.readFileSync(path.join(pluginRoot, 'commands', 'ultra-init.md'), 'utf8');
97
- assert.match(commandText, /Use the registered `ultra-init` skill/);
98
- assert.match(commandText, /\$ARGUMENTS/);
99
- } finally {
100
- fs.rmSync(home, { recursive: true, force: true });
101
- }
102
- });
103
-
104
- test('Kimi assets are allowlisted and adapted to native tools and paths', () => {
105
- const home = mkTarget();
106
- const { pluginRoot } = layout(home);
107
- try {
108
- kimi.install({ configDir: home, repoRoot: REPO_ROOT });
109
-
110
- const skills = fs.readdirSync(path.join(pluginRoot, 'skills'), { withFileTypes: true })
111
- .filter((entry) => entry.isDirectory()
112
- && fs.existsSync(path.join(pluginRoot, 'skills', entry.name, 'SKILL.md')))
113
- .map((entry) => entry.name)
114
- .sort();
115
- assert.deepEqual(skills, [...skillsForRuntime('kimi'), 'using-ultra-builder-pro'].sort());
116
-
117
- const learn = fs.readFileSync(path.join(pluginRoot, 'skills', 'learn', 'SKILL.md'), 'utf8');
118
- const review = fs.readFileSync(path.join(pluginRoot, 'skills', 'ultra-review', 'SKILL.md'), 'utf8');
119
- const init = fs.readFileSync(path.join(pluginRoot, 'skills', 'ultra-init', 'SKILL.md'), 'utf8');
120
- const bootstrap = fs.readFileSync(
121
- path.join(pluginRoot, 'skills', 'using-ultra-builder-pro', 'SKILL.md'),
122
- 'utf8',
123
- );
124
- assert.match(learn, /`~\/.kimi-code\/skills`/);
125
- assert.doesNotMatch(learn, /_unverified|learned-[^\s/]*-unverified/i);
126
- assert.match(review, /Kimi `AgentSwarm`/);
127
- assert.match(review, /\$KIMI_PLUGIN_ROOT\/agents\//);
128
- assert.match(review, /scripts\/review_wait\.py/);
129
- assert.match(review, /spec_fidelity/);
130
- assert.match(review, /engineering_standards/);
131
- assert.doesNotMatch(review, /background mode|run_in_background/);
132
- assert.match(init, /task\.init_project/);
133
- assert.doesNotMatch(init, /Claude Code|OpenCode|Codex/);
134
- assert.match(bootstrap, /\.ultra\/state\.db/);
135
- assert.match(bootstrap, /external providers/);
136
- assert.match(fs.readFileSync(path.join(pluginRoot, 'skills', 'codex-collab', 'SKILL.md'), 'utf8'), /--ephemeral/);
137
-
138
- const allPromptAssets = [];
139
- for (const root of ['commands', 'skills', 'agents']) {
140
- const pending = [path.join(pluginRoot, root)];
141
- while (pending.length) {
142
- const current = pending.pop();
143
- for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
144
- const file = path.join(current, entry.name);
145
- if (entry.isDirectory()) pending.push(file);
146
- else if (entry.isFile() && /\.(?:md|json)$/.test(entry.name)) allPromptAssets.push(file);
147
- }
148
- }
149
- }
150
- const foreign = /~\/.claude|~\/.codex|~\/.config\/opencode|CLAUDE\.md|TaskCreate|TaskUpdate|TaskList|AskUserQuestion|run_in_background:\s*true|\$CLAUDE_PLUGIN_ROOT|Kimi `Shell`/;
151
- for (const file of allPromptAssets) {
152
- const text = fs.readFileSync(file, 'utf8');
153
- assert.doesNotMatch(text, foreign, file);
154
- assert.doesNotMatch(text, /(^|[\s`("'])\/ultra-(?!builder-pro:)[a-z]/m, file);
155
- assert.doesNotMatch(text, /[\u3400-\u9fff]|ultra-review-findings-v1|Context7|Exa MCP|confidence\s*>=?\s*\d+/iu, file);
156
- }
157
- } finally {
158
- fs.rmSync(home, { recursive: true, force: true });
159
- }
160
- });
161
-
162
- test('reinstall preserves unrelated records and existing Kimi capability choices', () => {
163
- const home = mkTarget();
164
- const { registryFile } = layout(home);
165
- try {
166
- fs.mkdirSync(path.dirname(registryFile), { recursive: true });
167
- fs.writeFileSync(registryFile, JSON.stringify({
168
- version: 1,
169
- plugins: [{
170
- id: 'mine', root: '/tmp/mine', source: 'local-path', enabled: true,
171
- installedAt: '2026-01-01T00:00:00.000Z', originalSource: '/tmp/mine',
172
- }],
173
- }, null, 2));
174
-
175
- kimi.install({ configDir: home, repoRoot: REPO_ROOT });
176
- const first = readJson(registryFile);
177
- const ultra = first.plugins.find((entry) => entry.id === PLUGIN_ID);
178
- ultra.enabled = false;
179
- ultra.capabilities = { mcpServers: { [PLUGIN_ID]: { enabled: false } } };
180
- fs.writeFileSync(registryFile, JSON.stringify(first, null, 2));
181
-
182
- kimi.install({ configDir: home, repoRoot: REPO_ROOT });
183
- const second = readJson(registryFile);
184
- assert.deepEqual(second.plugins.map((entry) => entry.id).sort(), ['mine', PLUGIN_ID]);
185
- const refreshed = second.plugins.find((entry) => entry.id === PLUGIN_ID);
186
- assert.equal(refreshed.installedAt, ultra.installedAt);
187
- assert.equal(refreshed.enabled, false);
188
- assert.deepEqual(refreshed.capabilities, ultra.capabilities);
189
- } finally {
190
- fs.rmSync(home, { recursive: true, force: true });
191
- }
192
- });
193
-
194
- test('uninstall removes only the owned Kimi plugin record and managed root', () => {
195
- const home = mkTarget();
196
- const { pluginRoot, registryFile } = layout(home);
197
- try {
198
- kimi.install({ configDir: home, repoRoot: REPO_ROOT });
199
- const registry = readJson(registryFile);
200
- registry.plugins.unshift({
201
- id: 'mine', root: '/tmp/mine', source: 'local-path', enabled: true,
202
- installedAt: '2026-01-01T00:00:00.000Z', originalSource: '/tmp/mine',
203
- });
204
- fs.writeFileSync(registryFile, JSON.stringify(registry, null, 2));
205
-
206
- kimi.uninstall({ configDir: home });
207
- assert.equal(fs.existsSync(pluginRoot), false);
208
- assert.deepEqual(readJson(registryFile).plugins.map((entry) => entry.id), ['mine']);
209
- } finally {
210
- fs.rmSync(home, { recursive: true, force: true });
211
- }
212
- });
213
-
214
- test('install and uninstall refuse to replace or remove an unmanaged Kimi root', () => {
215
- const home = mkTarget();
216
- const { pluginRoot, registryFile } = layout(home);
217
- try {
218
- fs.mkdirSync(pluginRoot, { recursive: true });
219
- fs.writeFileSync(path.join(pluginRoot, 'user.txt'), 'keep');
220
- assert.throws(
221
- () => kimi.install({ configDir: home, repoRoot: REPO_ROOT }),
222
- /unmanaged Kimi plugin/,
223
- );
224
-
225
- fs.mkdirSync(path.dirname(registryFile), { recursive: true });
226
- fs.writeFileSync(registryFile, JSON.stringify({
227
- version: 1,
228
- plugins: [{
229
- id: PLUGIN_ID, root: pluginRoot, source: 'local-path', enabled: true,
230
- installedAt: '2026-01-01T00:00:00.000Z', originalSource: '/tmp/foreign',
231
- }],
232
- }));
233
- assert.throws(() => kimi.uninstall({ configDir: home }), /unmanaged Kimi plugin/);
234
- assert.equal(fs.readFileSync(path.join(pluginRoot, 'user.txt'), 'utf8'), 'keep');
235
- } finally {
236
- fs.rmSync(home, { recursive: true, force: true });
237
- }
238
- });
239
-
240
- test('doctor validates registration and provenance without mutating the Kimi home', () => {
241
- const home = mkTarget();
242
- const { registryFile } = layout(home);
243
- try {
244
- kimi.install({ configDir: home, repoRoot: REPO_ROOT });
245
- const before = fs.readFileSync(registryFile);
246
- const healthy = kimi.doctor({ configDir: home, repoRoot: REPO_ROOT });
247
- assert.equal(healthy.status, 'healthy', JSON.stringify(healthy, null, 2));
248
- assert.deepEqual(fs.readFileSync(registryFile), before);
249
-
250
- const registry = readJson(registryFile);
251
- registry.plugins[0].enabled = false;
252
- fs.writeFileSync(registryFile, JSON.stringify(registry, null, 2));
253
- const degraded = kimi.doctor({ configDir: home, repoRoot: REPO_ROOT });
254
- assert.equal(degraded.status, 'degraded');
255
- assert.ok(degraded.issues.some((issue) => issue.code === 'PLUGIN_REGISTRATION_INVALID'));
256
- } finally {
257
- fs.rmSync(home, { recursive: true, force: true });
258
- }
259
- });