ultra-builder-pro-cli 0.14.0 → 0.15.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 (154) 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 +44 -1
  8. package/README.md +34 -19
  9. package/adapters/_shared/codex-assets.cjs +6 -2
  10. package/adapters/_shared/file-ops.cjs +6 -2
  11. package/adapters/claude.js +3 -2
  12. package/adapters/codex.js +1 -0
  13. package/adapters/kimi.js +3 -2
  14. package/adapters/opencode.js +3 -2
  15. package/agents/review-coordinator.md +9 -6
  16. package/docs/AGENT-CONTEXT.md +15 -8
  17. package/docs/ARCHITECTURE.md +33 -12
  18. package/docs/PLAN.zh-CN.md +3 -2
  19. package/docs/ROADMAP.md +8 -4
  20. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  21. package/docs/STATE-DB-ACCESS-POLICY.md +27 -25
  22. package/docs/WORKFLOW-LIFECYCLE.md +266 -0
  23. package/hooks/README.md +6 -1
  24. package/hooks/health_check.py +12 -3
  25. package/hooks/subagent_tracker.py +52 -77
  26. package/mcp-server/hook-event.cjs +34 -0
  27. package/mcp-server/lib/archive-journal.cjs +17 -5
  28. package/mcp-server/lib/baseline-workflow.cjs +158 -18
  29. package/mcp-server/lib/change-workflow.cjs +550 -85
  30. package/mcp-server/lib/context-spine.cjs +242 -24
  31. package/mcp-server/lib/doctor.cjs +33 -3
  32. package/mcp-server/lib/init-project.cjs +210 -16
  33. package/mcp-server/lib/plan-store.cjs +4 -1
  34. package/mcp-server/lib/pricing.cjs +7 -10
  35. package/mcp-server/lib/project-breadcrumb.cjs +6 -0
  36. package/mcp-server/lib/projector.cjs +57 -3
  37. package/mcp-server/lib/spec-learning.cjs +79 -2
  38. package/mcp-server/lib/state-db.cjs +145 -8
  39. package/mcp-server/lib/state-ops.cjs +160 -9
  40. package/mcp-server/lib/workflow-state.cjs +2292 -0
  41. package/mcp-server/server.cjs +161 -15
  42. package/orchestrator/daemon.cjs +1 -2
  43. package/orchestrator/dispatch-rules.cjs +5 -31
  44. package/orchestrator/planner/plan-builder.cjs +21 -12
  45. package/package.json +13 -3
  46. package/skills/ultra-change/SKILL.md +53 -68
  47. package/skills/ultra-change/references/change-contract.md +36 -0
  48. package/skills/ultra-deliver/SKILL.md +69 -79
  49. package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
  50. package/skills/ultra-dev/SKILL.md +54 -59
  51. package/skills/ultra-doctor/SKILL.md +40 -71
  52. package/skills/ultra-init/SKILL.md +96 -103
  53. package/skills/ultra-init/references/brownfield-adoption.md +2 -2
  54. package/skills/ultra-plan/SKILL.md +79 -85
  55. package/skills/ultra-plan/references/semantic-preflight.md +18 -0
  56. package/skills/ultra-research/SKILL.md +87 -60
  57. package/skills/ultra-research/references/00-problem-validation.md +36 -0
  58. package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
  59. package/skills/ultra-research/references/02-market-assessment.md +30 -0
  60. package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
  61. package/skills/ultra-research/references/04-product-strategy.md +32 -0
  62. package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
  63. package/skills/ultra-research/references/10-user-personas.md +29 -0
  64. package/skills/ultra-research/references/11-user-scenarios.md +28 -0
  65. package/skills/ultra-research/references/20-user-stories.md +30 -0
  66. package/skills/ultra-research/references/21-features-scope.md +29 -0
  67. package/skills/ultra-research/references/22-success-metrics.md +28 -0
  68. package/skills/ultra-research/references/30-architecture-context.md +30 -0
  69. package/skills/ultra-research/references/31-solution-strategy.md +28 -0
  70. package/skills/ultra-research/references/32-building-blocks.md +28 -0
  71. package/skills/ultra-research/references/40-deployment.md +29 -0
  72. package/skills/ultra-research/references/41-quality-risks.md +28 -0
  73. package/skills/ultra-research/references/99-synthesis.md +35 -0
  74. package/skills/ultra-review/SKILL.md +57 -73
  75. package/skills/ultra-review/references/review-modes.md +27 -0
  76. package/skills/ultra-review/references/unified-schema.md +25 -0
  77. package/skills/ultra-review/scripts/review_wait.py +40 -3
  78. package/skills/ultra-status/SKILL.md +41 -45
  79. package/skills/ultra-test/SKILL.md +48 -55
  80. package/skills/ultra-verify/SKILL.md +1 -1
  81. package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
  82. package/spec/cli-protocol.md +7 -2
  83. package/spec/mcp-tools.yaml +384 -63
  84. package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
  85. package/spec/schemas/delivery-report.v1.schema.json +111 -0
  86. package/spec/schemas/mcp-tools.schema.json +1 -1
  87. package/spec/schemas/state-db.sql +82 -1
  88. package/spec/schemas/tasks.v4.5.schema.json +8 -1
  89. package/spec/schemas/test-report.v1.schema.json +194 -0
  90. package/ultra-tools/cli.cjs +1 -1
  91. package/ultra-tools/commands/migrate.cjs +3 -3
  92. package/ultra-tools/commands/status.cjs +213 -12
  93. package/.ultra-template/delivery-report.json +0 -9
  94. package/.ultra-template/test-report.json +0 -8
  95. package/adapters/_shared/tests/file-ops.test.cjs +0 -54
  96. package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
  97. package/adapters/_shared/tests/handbook.test.cjs +0 -101
  98. package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
  99. package/adapters/_shared/tests/provenance.test.cjs +0 -133
  100. package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
  101. package/adapters/_shared/tests/runtime-assets.test.cjs +0 -149
  102. package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
  103. package/adapters/_shared/tests/validate.test.cjs +0 -30
  104. package/adapters/tests/claude.test.cjs +0 -138
  105. package/adapters/tests/codex-hook.test.cjs +0 -142
  106. package/adapters/tests/codex.test.cjs +0 -430
  107. package/adapters/tests/kimi-hook.test.cjs +0 -149
  108. package/adapters/tests/kimi.test.cjs +0 -259
  109. package/adapters/tests/opencode.test.cjs +0 -357
  110. package/mcp-server/lib/archive-journal.test.cjs +0 -35
  111. package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
  112. package/mcp-server/lib/change-workflow.test.cjs +0 -971
  113. package/mcp-server/lib/context-spine.test.cjs +0 -93
  114. package/mcp-server/lib/doctor.test.cjs +0 -148
  115. package/mcp-server/lib/plan-store.test.cjs +0 -132
  116. package/mcp-server/lib/prd-parser.test.cjs +0 -82
  117. package/mcp-server/lib/project-breadcrumb.test.cjs +0 -112
  118. package/mcp-server/lib/runtime-state.test.cjs +0 -92
  119. package/mcp-server/lib/spec-learning.test.cjs +0 -67
  120. package/mcp-server/lib/state-db.test.cjs +0 -314
  121. package/mcp-server/lib/task-expander.test.cjs +0 -169
  122. package/mcp-server/lib/topo.test.cjs +0 -158
  123. package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
  124. package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
  125. package/mcp-server/tests/concurrency-update.test.cjs +0 -96
  126. package/mcp-server/tests/concurrency.test.cjs +0 -112
  127. package/mcp-server/tests/db-ops.test.cjs +0 -121
  128. package/mcp-server/tests/init-project.test.cjs +0 -393
  129. package/mcp-server/tests/migrate.test.cjs +0 -232
  130. package/mcp-server/tests/projector.test.cjs +0 -162
  131. package/mcp-server/tests/server-contract.test.cjs +0 -1467
  132. package/mcp-server/tests/staleness.test.cjs +0 -190
  133. package/mcp-server/tests/state-db.test.cjs +0 -106
  134. package/mcp-server/tests/state-ops.test.cjs +0 -449
  135. package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
  136. package/mcp-server/tests/telemetry.test.cjs +0 -219
  137. package/orchestrator/planner/plan-builder.test.cjs +0 -165
  138. package/orchestrator/tests/auto-merge.test.cjs +0 -216
  139. package/orchestrator/tests/daemon.test.cjs +0 -311
  140. package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
  141. package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
  142. package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
  143. package/orchestrator/tests/recovery.test.cjs +0 -190
  144. package/orchestrator/tests/session-runner.test.cjs +0 -314
  145. package/orchestrator/tests/skill-miner.test.cjs +0 -147
  146. package/orchestrator/tests/worktree-manager.test.cjs +0 -148
  147. package/skills/ultra-research/references/architecture.md +0 -38
  148. package/skills/ultra-research/references/problem-product.md +0 -39
  149. package/skills/ultra-research/references/quality-delivery.md +0 -34
  150. package/skills/ultra-research/references/synthesis.md +0 -38
  151. package/skills/ultra-research/references/users-scope.md +0 -40
  152. package/ultra-tools/legacy-memory.test.cjs +0 -80
  153. package/ultra-tools/status-command.test.cjs +0 -181
  154. 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
- });