ultra-builder-pro-cli 5.0.0 → 6.0.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 (159) hide show
  1. package/CHANGELOG.md +113 -3
  2. package/README.md +67 -478
  3. package/README.zh-CN.md +49 -422
  4. package/adapters/_shared/frontmatter.cjs +1 -18
  5. package/adapters/_shared/plugin-core.cjs +17 -83
  6. package/adapters/_shared/runtime-assets.cjs +6 -120
  7. package/adapters/claude.js +8 -24
  8. package/adapters/codex.js +16 -38
  9. package/bin/install.js +35 -57
  10. package/docs/PLUGIN-ISOLATION-CONTRACT.md +13 -101
  11. package/docs/examples/drive/claude-reviewer.sh +53 -0
  12. package/docs/examples/drive/claude-writer.sh +43 -0
  13. package/engine/FORMATS.md +1051 -0
  14. package/engine/checks.cjs +139 -0
  15. package/engine/cli.cjs +148 -0
  16. package/engine/completion.cjs +48 -0
  17. package/engine/context.cjs +94 -0
  18. package/engine/drive.cjs +220 -0
  19. package/engine/expect-worker.cjs +5 -0
  20. package/engine/expect.cjs +33 -0
  21. package/engine/formats.cjs +189 -0
  22. package/engine/git-state.cjs +112 -0
  23. package/engine/ledger.cjs +205 -0
  24. package/engine/loading.cjs +28 -0
  25. package/engine/merge.cjs +273 -0
  26. package/engine/milestone.cjs +89 -0
  27. package/engine/plan.cjs +232 -0
  28. package/engine/project.cjs +5 -0
  29. package/engine/signals.cjs +3 -0
  30. package/engine/states.cjs +175 -0
  31. package/hooks/session-start.cjs +54 -0
  32. package/package.json +17 -52
  33. package/skills/ultra/SKILL.md +395 -0
  34. package/.ultra-template/changes/abandoned/.gitkeep +0 -0
  35. package/.ultra-template/changes/active/.gitkeep +0 -1
  36. package/.ultra-template/changes/archive/.gitkeep +0 -1
  37. package/.ultra-template/contexts/TEMPLATE.md +0 -135
  38. package/.ultra-template/decisions/.gitkeep +0 -1
  39. package/.ultra-template/evidence/.gitkeep +0 -1
  40. package/.ultra-template/north-star.md +0 -52
  41. package/.ultra-template/project-brief.md +0 -28
  42. package/.ultra-template/research/.gitkeep +0 -1
  43. package/.ultra-template/specs/architecture.md +0 -81
  44. package/.ultra-template/specs/discovery.md +0 -74
  45. package/.ultra-template/specs/product.md +0 -80
  46. package/.ultra-template/specs/research-distillate.md +0 -57
  47. package/.ultra-template/tasks.json +0 -4
  48. package/.ultra-template/test-report.json +0 -44
  49. package/adapters/_shared/host-profile.cjs +0 -5
  50. package/adapters/grok.js +0 -242
  51. package/adapters/kimi.js +0 -232
  52. package/adapters/opencode.js +0 -408
  53. package/adapters/zcode.js +0 -375
  54. package/bin/delegate-worker.cjs +0 -5
  55. package/bin/delegate.cjs +0 -5
  56. package/docs/ARCHITECTURE.md +0 -274
  57. package/docs/ARTIFACT-AUTHORITY.md +0 -305
  58. package/docs/DECISIONS.md +0 -229
  59. package/docs/PHILOSOPHY.md +0 -261
  60. package/docs/RUNTIME-COMPAT-MATRIX.md +0 -315
  61. package/docs/SKILL-AUTHORING.md +0 -113
  62. package/docs/WORKFLOW-LIFECYCLE.md +0 -350
  63. package/docs/evals/adversarial-review-2026-08-14.md +0 -84
  64. package/docs/evals/ultra-5-0-journey-results.md +0 -62
  65. package/docs/evals/ultra-5-0-live-acp-conformance.md +0 -93
  66. package/docs/evals/ultra-5-0-route-journeys.md +0 -145
  67. package/docs/evals/zcode-automation-2026-08-14.md +0 -103
  68. package/hooks/README.md +0 -52
  69. package/hooks/_common.py +0 -1779
  70. package/hooks/adapters/codex.py +0 -295
  71. package/hooks/adapters/grok.py +0 -134
  72. package/hooks/adapters/kimi.py +0 -156
  73. package/hooks/adapters/zcode.py +0 -227
  74. package/hooks/block_dangerous_commands.py +0 -201
  75. package/hooks/compact_context.py +0 -224
  76. package/hooks/mid_workflow_recall.py +0 -47
  77. package/hooks/post_edit_guard.py +0 -215
  78. package/hooks/session_context.py +0 -741
  79. package/skills/ultra-change/SKILL.md +0 -165
  80. package/skills/ultra-change/references/change-contract.md +0 -199
  81. package/skills/ultra-change/references/execution-grant.md +0 -109
  82. package/skills/ultra-change/references/primary-transfer.md +0 -273
  83. package/skills/ultra-change/scripts/validate_primary_transfer.cjs +0 -1928
  84. package/skills/ultra-delegate/SKILL.md +0 -195
  85. package/skills/ultra-delegate/patches/zcode-acp-0.1.0-zcode-0.16.5.patch +0 -184
  86. package/skills/ultra-delegate/references/delegation-contract.md +0 -138
  87. package/skills/ultra-delegate/scripts/acp-conformance.cjs +0 -287
  88. package/skills/ultra-delegate/scripts/acp-driver.cjs +0 -762
  89. package/skills/ultra-delegate/scripts/agent-execution-driver.cjs +0 -320
  90. package/skills/ultra-delegate/scripts/cli-driver.cjs +0 -61
  91. package/skills/ultra-delegate/scripts/delegate-profiles/kimi-read-only.md +0 -15
  92. package/skills/ultra-delegate/scripts/delegate-profiles/kimi-write.md +0 -17
  93. package/skills/ultra-delegate/scripts/delegate-worker.cjs +0 -328
  94. package/skills/ultra-delegate/scripts/delegate.cjs +0 -690
  95. package/skills/ultra-delegate/scripts/delegate_wait.py +0 -86
  96. package/skills/ultra-delegate/scripts/host-profile.cjs +0 -267
  97. package/skills/ultra-deliver/SKILL.md +0 -194
  98. package/skills/ultra-deliver/references/baseline-reconciliation.md +0 -41
  99. package/skills/ultra-dev/SKILL.md +0 -265
  100. package/skills/ultra-dev/references/debugging.md +0 -22
  101. package/skills/ultra-domain-modeling/SKILL.md +0 -79
  102. package/skills/ultra-grilling/SKILL.md +0 -121
  103. package/skills/ultra-grilling/references/reframing.md +0 -76
  104. package/skills/ultra-init/SKILL.md +0 -143
  105. package/skills/ultra-init/references/brownfield-adoption.md +0 -32
  106. package/skills/ultra-init/scripts/init_project.cjs +0 -1031
  107. package/skills/ultra-plan/SKILL.md +0 -160
  108. package/skills/ultra-plan/references/task-evidence-v2.md +0 -300
  109. package/skills/ultra-plan/scripts/validate_task_evidence.cjs +0 -859
  110. package/skills/ultra-research/SKILL.md +0 -167
  111. package/skills/ultra-research/references/00-problem-validation.md +0 -36
  112. package/skills/ultra-research/references/01-opportunity-discovery.md +0 -33
  113. package/skills/ultra-research/references/02-market-assessment.md +0 -31
  114. package/skills/ultra-research/references/03-alternatives.md +0 -31
  115. package/skills/ultra-research/references/04-product-strategy.md +0 -41
  116. package/skills/ultra-research/references/05-assumptions-validation.md +0 -31
  117. package/skills/ultra-research/references/10-user-personas.md +0 -30
  118. package/skills/ultra-research/references/11-user-scenarios.md +0 -28
  119. package/skills/ultra-research/references/20-user-stories.md +0 -31
  120. package/skills/ultra-research/references/21-features-scope.md +0 -38
  121. package/skills/ultra-research/references/22-success-metrics.md +0 -33
  122. package/skills/ultra-research/references/30-architecture-context.md +0 -30
  123. package/skills/ultra-research/references/31-solution-strategy.md +0 -28
  124. package/skills/ultra-research/references/32-building-blocks.md +0 -28
  125. package/skills/ultra-research/references/40-deployment.md +0 -29
  126. package/skills/ultra-research/references/41-quality-risks.md +0 -28
  127. package/skills/ultra-research/references/99-synthesis.md +0 -59
  128. package/skills/ultra-research/references/north-star-v2.md +0 -168
  129. package/skills/ultra-research/references/wayfinding.md +0 -50
  130. package/skills/ultra-research/scripts/validate_north_star.cjs +0 -1174
  131. package/skills/ultra-review/SKILL.md +0 -275
  132. package/skills/ultra-review/references/adversarial-evaluation.md +0 -31
  133. package/skills/ultra-review/references/code.md +0 -17
  134. package/skills/ultra-review/references/comments.md +0 -12
  135. package/skills/ultra-review/references/design.md +0 -12
  136. package/skills/ultra-review/references/errors.md +0 -13
  137. package/skills/ultra-review/references/review-modes.md +0 -29
  138. package/skills/ultra-review/references/spec.md +0 -18
  139. package/skills/ultra-review/references/tests.md +0 -12
  140. package/skills/ultra-review/references/unified-schema.md +0 -284
  141. package/skills/ultra-review/references/worker-packet.md +0 -183
  142. package/skills/ultra-review/scripts/review_wait.py +0 -2943
  143. package/skills/ultra-status/SKILL.md +0 -209
  144. package/skills/ultra-status/references/route-contract.md +0 -100
  145. package/skills/ultra-tdd/SKILL.md +0 -91
  146. package/skills/ultra-tdd/references/templates/README.md +0 -20
  147. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +0 -79
  148. package/skills/ultra-tdd/references/templates/persistence-real.ts +0 -78
  149. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +0 -78
  150. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +0 -73
  151. package/skills/ultra-tdd/references/templates/vertical-slice.ts +0 -66
  152. package/skills/ultra-tdd/references/test-execution.md +0 -23
  153. package/skills/ultra-test/SKILL.md +0 -244
  154. package/skills/ultra-test/references/export-syntax.md +0 -18
  155. package/skills/ultra-test/scripts/validate_review_transport.cjs +0 -549
  156. package/skills/ultra-test/scripts/worktree_digest.cjs +0 -576
  157. package/skills/ultra-think/SKILL.md +0 -56
  158. package/skills/ultra-think/references/autonomy-boundary.md +0 -48
  159. package/skills/ultra-think/references/interaction-boundary.md +0 -42
package/adapters/kimi.js DELETED
@@ -1,232 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const os = require('node:os');
5
- const path = require('node:path');
6
-
7
- const {
8
- copyHooks,
9
- copySkills,
10
- inspectPlugin,
11
- markManaged,
12
- publishManagedTrees,
13
- removeManaged,
14
- writePluginProvenance,
15
- } = require('./_shared/plugin-core.cjs');
16
- const {
17
- captureAbsent,
18
- ensureDir,
19
- managedMetadata,
20
- pruneCreatedEmpty,
21
- removeTree,
22
- writeAtomic,
23
- } = require('./_shared/file-ops.cjs');
24
-
25
- const PLUGIN_ID = 'ultra-builder-pro';
26
-
27
- function resolveTarget(ctx = {}) {
28
- if (ctx.configDir) return path.resolve(ctx.configDir);
29
- if (ctx.scope === 'global') return process.env.KIMI_CODE_HOME || path.join(ctx.homeDir || os.homedir(), '.kimi-code');
30
- return path.join(ctx.cwd || process.cwd(), '.kimi-code');
31
- }
32
-
33
- function resolvePluginRoot(ctx = {}) {
34
- return path.join(resolveTarget(ctx), 'plugins', 'managed', PLUGIN_ID);
35
- }
36
-
37
- function resolveRegistryFile(ctx = {}) {
38
- return path.join(resolveTarget(ctx), 'plugins', 'installed.json');
39
- }
40
-
41
- function resolveRepoRoot(ctx = {}) {
42
- return ctx.repoRoot || path.resolve(__dirname, '..');
43
- }
44
-
45
- function assertPluginScope(ctx = {}) {
46
- if (ctx.scope === 'local' && !ctx.configDir) {
47
- throw new Error('Kimi plugins are user-scoped; use global scope or an isolated configDir');
48
- }
49
- }
50
-
51
- function hookCommand(name) {
52
- return `python3 ./hooks/adapters/kimi.py ${JSON.stringify(name)}`;
53
- }
54
-
55
- function buildHooksManifest() {
56
- return [
57
- { event: 'SessionStart', command: hookCommand('session_context.py'), timeout: 10 },
58
- { event: 'SessionStart', matcher: 'compact', command: hookCommand('compact_context.py'), timeout: 10 },
59
- { event: 'PreToolUse', matcher: 'Edit|Write|Grep', command: hookCommand('mid_workflow_recall.py'), timeout: 10 },
60
- { event: 'PreToolUse', matcher: 'Bash', command: hookCommand('block_dangerous_commands.py'), timeout: 10 },
61
- { event: 'PostToolUse', matcher: 'Edit|Write', command: hookCommand('post_edit_guard.py'), timeout: 10 },
62
- { event: 'PreCompact', command: hookCommand('compact_context.py'), timeout: 10 },
63
- { event: 'PostCompact', command: hookCommand('compact_context.py'), timeout: 10 },
64
- ];
65
- }
66
-
67
- function loadRegistry(file) {
68
- if (!fs.existsSync(file)) return { version: 1, plugins: [] };
69
- const value = JSON.parse(fs.readFileSync(file, 'utf8'));
70
- if (!value || value.version !== 1 || !Array.isArray(value.plugins)) {
71
- throw new Error(`invalid Kimi registry: ${file}`);
72
- }
73
- return value;
74
- }
75
-
76
- function nextRegistry(value, target, repoRoot) {
77
- const existing = value.plugins.find((entry) => entry?.id === PLUGIN_ID);
78
- if (existing && path.resolve(existing.root || '') !== path.resolve(target)) {
79
- throw new Error(`refusing to replace conflicting Kimi registration: ${existing.root}`);
80
- }
81
- const now = new Date().toISOString();
82
- const record = {
83
- id: PLUGIN_ID,
84
- root: target,
85
- source: 'local-path',
86
- enabled: existing?.enabled ?? true,
87
- installedAt: existing?.installedAt || now,
88
- updatedAt: now,
89
- originalSource: repoRoot,
90
- };
91
- return {
92
- version: 1,
93
- plugins: [...value.plugins.filter((entry) => entry?.id !== PLUGIN_ID), record],
94
- };
95
- }
96
-
97
- function buildStaging(repoRoot, staging, publishedRoot) {
98
- const skills = copySkills({ runtime: 'kimi', repoRoot, skillRoot: path.join(staging, 'skills') });
99
- const hooks = buildHooksManifest();
100
- copyHooks({ runtime: 'kimi', repoRoot, hookRoot: path.join(staging, 'hooks'), hooksManifest: hooks });
101
- const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
102
- writeAtomic(path.join(staging, 'kimi.plugin.json'), `${JSON.stringify({
103
- name: PLUGIN_ID,
104
- version: pkg.version,
105
- description: 'File-first Ultra Builder Pro workflows for Kimi Code.',
106
- keywords: ['kimi-code', 'skills', 'hooks', 'ultra-builder-pro'],
107
- author: { name: pkg.author },
108
- homepage: pkg.homepage,
109
- license: pkg.license,
110
- skills: ['./skills'],
111
- hooks,
112
- interface: {
113
- displayName: 'Ultra Builder Pro',
114
- shortDescription: 'File-first project workflows and evidence.',
115
- developerName: pkg.author,
116
- websiteURL: pkg.homepage,
117
- },
118
- }, null, 2)}\n`);
119
- markManaged(staging, { adapter: 'kimi', plugin: PLUGIN_ID, version: pkg.version });
120
- writePluginProvenance({
121
- adapter: 'kimi', repoRoot, stagingRoot: staging, publishedRoot,
122
- contracts: {
123
- plugin_manifest: { root: 'plugin', path: 'kimi.plugin.json' },
124
- hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
125
- hook_adapter: { root: 'plugin', path: 'hooks/adapters/kimi.py' },
126
- },
127
- });
128
- return { skills };
129
- }
130
-
131
- function install(ctx = {}) {
132
- assertPluginScope(ctx);
133
- const target = resolvePluginRoot(ctx);
134
- const configRoot = resolveTarget(ctx);
135
- const registryFile = resolveRegistryFile(ctx);
136
- const repoRoot = resolveRepoRoot(ctx);
137
- const previousMetadata = managedMetadata(target) || {};
138
- const cleanupAbsent = previousMetadata.cleanup_absent
139
- || captureAbsent(configRoot, ['.', 'plugins', path.join('plugins', 'managed')]);
140
- const registryExisted = fs.existsSync(registryFile);
141
- const registryCreated = previousMetadata.registry_created === true
142
- || (previousMetadata.registry_created === undefined && !registryExisted);
143
- const registryBefore = registryExisted ? fs.readFileSync(registryFile) : null;
144
- const registry = nextRegistry(loadRegistry(registryFile), target, repoRoot);
145
- ensureDir(path.dirname(target));
146
- const staging = fs.mkdtempSync(path.join(path.dirname(target), `.${PLUGIN_ID}-staging-`));
147
- try {
148
- const copied = buildStaging(repoRoot, staging, target);
149
- markManaged(staging, {
150
- cleanup_absent: cleanupAbsent,
151
- registry_created: registryCreated,
152
- });
153
- writeAtomic(registryFile, `${JSON.stringify(registry, null, 2)}\n`);
154
- try {
155
- publishManagedTrees([{ source: staging, target, label: 'Kimi plugin' }]);
156
- } catch (error) {
157
- if (registryExisted) writeAtomic(registryFile, registryBefore);
158
- else if (fs.existsSync(registryFile)) fs.unlinkSync(registryFile);
159
- throw error;
160
- }
161
- return {
162
- target,
163
- pluginRoot: target,
164
- skillRoot: path.join(target, 'skills'),
165
- hookRoot: path.join(target, 'hooks'),
166
- registry: registryFile,
167
- copied,
168
- };
169
- } catch (error) {
170
- if (fs.existsSync(staging)) removeTree(staging);
171
- pruneCreatedEmpty(configRoot, cleanupAbsent);
172
- throw error;
173
- }
174
- }
175
-
176
- function doctor(ctx = {}) {
177
- assertPluginScope(ctx);
178
- const target = resolvePluginRoot(ctx);
179
- const report = inspectPlugin({
180
- adapter: 'kimi', repoRoot: resolveRepoRoot(ctx), pluginRoot: target,
181
- skillRoot: path.join(target, 'skills'), hookRoot: path.join(target, 'hooks'),
182
- manifestFile: path.join(target, 'kimi.plugin.json'),
183
- });
184
- let registered = false;
185
- try {
186
- const record = loadRegistry(resolveRegistryFile(ctx)).plugins.find((entry) => entry?.id === PLUGIN_ID);
187
- registered = !!record && record.enabled === true && path.resolve(record.root) === path.resolve(target);
188
- } catch {}
189
- report.checks.registration = { status: registered ? 'pass' : 'fail' };
190
- if (!registered) report.issues.push({ code: 'PLUGIN_REGISTRATION_INVALID' });
191
- report.status = report.issues.length === 0 ? 'healthy' : 'degraded';
192
- return report;
193
- }
194
-
195
- function uninstall(ctx = {}) {
196
- assertPluginScope(ctx);
197
- const target = resolvePluginRoot(ctx);
198
- const configRoot = resolveTarget(ctx);
199
- const metadata = managedMetadata(target) || {};
200
- const cleanupAbsent = metadata.cleanup_absent || [];
201
- const registryFile = resolveRegistryFile(ctx);
202
- const registry = loadRegistry(registryFile);
203
- const record = registry.plugins.find((entry) => entry?.id === PLUGIN_ID);
204
- if (record && path.resolve(record.root || '') !== path.resolve(target)) {
205
- throw new Error(`refusing to remove conflicting Kimi registration: ${record.root}`);
206
- }
207
- if (record) {
208
- registry.plugins = registry.plugins.filter((entry) => entry?.id !== PLUGIN_ID);
209
- if (registry.plugins.length === 0 && metadata.registry_created === true) fs.unlinkSync(registryFile);
210
- else writeAtomic(registryFile, `${JSON.stringify(registry, null, 2)}\n`);
211
- }
212
- const removedPlugin = removeManaged(target, 'Kimi plugin');
213
- const cleaned = pruneCreatedEmpty(configRoot, cleanupAbsent);
214
- return {
215
- target,
216
- pluginRoot: target,
217
- removed: { plugin: removedPlugin, registration: !!record },
218
- cleaned,
219
- };
220
- }
221
-
222
- module.exports = {
223
- name: 'kimi',
224
- PLUGIN_ID,
225
- buildHooksManifest,
226
- resolveTarget,
227
- resolvePluginRoot,
228
- resolveRegistryFile,
229
- install,
230
- doctor,
231
- uninstall,
232
- };
@@ -1,408 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const os = require('node:os');
5
- const path = require('node:path');
6
- const crypto = require('node:crypto');
7
-
8
- const {
9
- copyHooks,
10
- copySkills,
11
- inspectPlugin,
12
- markManaged,
13
- removeManaged,
14
- writePluginProvenance,
15
- } = require('./_shared/plugin-core.cjs');
16
- const {
17
- captureAbsent,
18
- ensureDir,
19
- isManaged,
20
- managedMetadata,
21
- pruneCreatedEmpty,
22
- removeTree: removePath,
23
- writeAtomic,
24
- } = require('./_shared/file-ops.cjs');
25
- const { skillsForRuntime } = require('./_shared/runtime-assets.cjs');
26
-
27
- const PLUGIN_NAME = 'ultra-builder-pro';
28
- const BUNDLE_DIR = '.ultra-builder-pro';
29
- const PLUGIN_MARKER = '// Managed by Ultra Builder Pro.';
30
- const TEXT_MARKER = '<!-- ultra-builder-pro:managed -->';
31
-
32
- function resolveTarget(ctx = {}) {
33
- if (ctx.configDir) return path.resolve(ctx.configDir);
34
- if (ctx.scope === 'global') {
35
- if (process.env.OPENCODE_CONFIG_DIR) return process.env.OPENCODE_CONFIG_DIR;
36
- if (process.env.OPENCODE_CONFIG) return path.dirname(process.env.OPENCODE_CONFIG);
37
- return path.join(process.env.XDG_CONFIG_HOME || path.join(ctx.homeDir || os.homedir(), '.config'), 'opencode');
38
- }
39
- return path.join(ctx.cwd || process.cwd(), '.opencode');
40
- }
41
-
42
- function resolveRepoRoot(ctx = {}) {
43
- return ctx.repoRoot || path.resolve(__dirname, '..');
44
- }
45
-
46
- function buildHooksManifest() {
47
- return {
48
- SessionStart: ['session_context.py', 'compact_context.py'],
49
- PreToolUse: ['mid_workflow_recall.py', 'block_dangerous_commands.py'],
50
- PostToolUse: ['post_edit_guard.py'],
51
- PreCompact: ['compact_context.py'],
52
- };
53
- }
54
-
55
- function pluginSource() {
56
- return `${PLUGIN_MARKER}
57
- import path from "node:path";
58
- import { execFileSync } from "node:child_process";
59
- import { fileURLToPath } from "node:url";
60
-
61
- const PLUGIN_DIR = path.dirname(fileURLToPath(import.meta.url));
62
- const HOOK_ROOT = path.resolve(PLUGIN_DIR, "..", ${JSON.stringify(BUNDLE_DIR)}, "hooks");
63
-
64
- function runHook(name, root, payload) {
65
- try {
66
- const stdout = execFileSync("python3", [path.join(HOOK_ROOT, name)], {
67
- cwd: root,
68
- input: JSON.stringify({ cwd: root, ...payload }),
69
- encoding: "utf8",
70
- stdio: ["pipe", "pipe", "pipe"],
71
- timeout: 10000,
72
- }).trim();
73
- return stdout ? JSON.parse(stdout) : {};
74
- } catch (error) {
75
- return { systemMessage: "Ultra hook failed open: " + String(error?.message || error) };
76
- }
77
- }
78
-
79
- function contextOf(output) {
80
- return output?.hookSpecificOutput?.additionalContext || output?.systemMessage || "";
81
- }
82
-
83
- export const UltraBuilderProPlugin = async ({ directory, worktree }) => {
84
- const root = worktree || directory;
85
- return {
86
- "experimental.chat.system.transform": async (_input, output) => {
87
- const text = contextOf(runHook("session_context.py", root, { hook_event_name: "SessionStart" }));
88
- if (text) output.system.push(text);
89
- },
90
- "experimental.session.compacting": async (_input, output) => {
91
- runHook("compact_context.py", root, { hook_event_name: "PreCompact" });
92
- const text = contextOf(runHook("compact_context.py", root, {
93
- hook_event_name: "SessionStart", source: "compact",
94
- }));
95
- if (text) output.context.push(text);
96
- },
97
- "tool.execute.before": async (input, output) => {
98
- const tool = String(input?.tool || "").toLowerCase();
99
- if (tool === "bash") {
100
- const verdict = runHook("block_dangerous_commands.py", root, {
101
- hook_event_name: "PreToolUse", tool_name: "Bash", tool_input: output?.args || {},
102
- });
103
- if (verdict?.decision === "block") throw new Error(verdict.reason);
104
- }
105
- if (["write", "edit", "grep", "apply_patch"].includes(tool)) {
106
- runHook("mid_workflow_recall.py", root, {
107
- hook_event_name: "PreToolUse", tool_name: input?.tool, tool_input: output?.args || {},
108
- });
109
- }
110
- },
111
- "tool.execute.after": async (input) => {
112
- const tool = String(input?.tool || "").toLowerCase();
113
- if (["write", "edit", "apply_patch"].includes(tool)) {
114
- runHook("post_edit_guard.py", root, {
115
- hook_event_name: "PostToolUse", tool_name: input?.tool, tool_input: input?.args || {},
116
- });
117
- }
118
- },
119
- };
120
- };
121
- `;
122
- }
123
-
124
- function planRetiredCleanup(target) {
125
- const files = [];
126
- for (const dirName of ['commands', 'agents']) {
127
- const dir = path.join(target, dirName);
128
- if (!fs.existsSync(dir)) continue;
129
- for (const file of fs.readdirSync(dir)) {
130
- const candidate = path.join(dir, file);
131
- if (!fs.statSync(candidate).isFile()) continue;
132
- const text = fs.readFileSync(candidate, 'utf8');
133
- if (text.includes(TEXT_MARKER)) files.push(candidate);
134
- }
135
- }
136
- const configFile = path.join(target, 'opencode.json');
137
- let config = null;
138
- if (fs.existsSync(configFile)) {
139
- const value = JSON.parse(fs.readFileSync(configFile, 'utf8'));
140
- if (value?.mcp?.[PLUGIN_NAME]) {
141
- delete value.mcp[PLUGIN_NAME];
142
- if (Object.keys(value.mcp).length === 0) delete value.mcp;
143
- config = `${JSON.stringify(value, null, 2)}\n`;
144
- }
145
- }
146
- return { config, configFile, files };
147
- }
148
-
149
- function cleanupRetired(target, plan = planRetiredCleanup(target)) {
150
- for (const file of plan.files) {
151
- if (fs.existsSync(file) && fs.readFileSync(file, 'utf8').includes(TEXT_MARKER)) {
152
- fs.unlinkSync(file);
153
- }
154
- }
155
- for (const dirName of ['commands', 'agents']) {
156
- const dir = path.join(target, dirName);
157
- if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);
158
- }
159
- if (plan.config !== null) writeAtomic(plan.configFile, plan.config);
160
- }
161
-
162
- function createInstallTransaction() {
163
- const changes = [];
164
- const backups = [];
165
- const backupPath = (target) => path.join(
166
- path.dirname(target),
167
- `.${path.basename(target)}.ubp-backup-${crypto.randomUUID()}`,
168
- );
169
- return {
170
- replaceFile(source, target) {
171
- let previous = null;
172
- try {
173
- const stat = fs.statSync(target);
174
- previous = { contents: fs.readFileSync(target), mode: stat.mode & 0o7777 };
175
- } catch (error) {
176
- if (error.code !== 'ENOENT') throw error;
177
- }
178
- writeAtomic(target, fs.readFileSync(source));
179
- changes.push({ type: 'file', target, previous });
180
- },
181
- replaceTree(source, target) {
182
- ensureDir(path.dirname(target));
183
- let backup = null;
184
- if (fs.existsSync(target)) {
185
- backup = backupPath(target);
186
- fs.renameSync(target, backup);
187
- backups.push(backup);
188
- }
189
- try {
190
- fs.renameSync(source, target);
191
- } catch (error) {
192
- if (backup && fs.existsSync(backup)) fs.renameSync(backup, target);
193
- throw error;
194
- }
195
- changes.push({ type: 'tree', target, backup });
196
- },
197
- removeFile(target) {
198
- if (!fs.existsSync(target)) return;
199
- const link = fs.lstatSync(target).isSymbolicLink() ? fs.readlinkSync(target) : null;
200
- const stat = fs.statSync(target);
201
- const previous = { contents: fs.readFileSync(target), link, mode: stat.mode & 0o7777 };
202
- fs.unlinkSync(target);
203
- changes.push({ type: 'file', target, previous, removed: true });
204
- },
205
- removeTree(target) {
206
- if (!fs.existsSync(target)) return;
207
- const backup = backupPath(target);
208
- fs.renameSync(target, backup);
209
- backups.push(backup);
210
- changes.push({ type: 'tree', target, backup, removed: true });
211
- },
212
- commit() {
213
- for (const backup of backups) {
214
- if (fs.existsSync(backup)) removePath(backup);
215
- }
216
- changes.length = 0;
217
- backups.length = 0;
218
- },
219
- rollback() {
220
- const errors = [];
221
- for (const change of changes.reverse()) {
222
- try {
223
- if (change.type === 'file') {
224
- if (change.previous) {
225
- if (change.removed && change.previous.link !== null) {
226
- ensureDir(path.dirname(change.target));
227
- fs.symlinkSync(change.previous.link, change.target);
228
- } else {
229
- writeAtomic(change.target, change.previous.contents);
230
- fs.chmodSync(resolveTargetFile(change.target), change.previous.mode);
231
- }
232
- } else if (fs.existsSync(change.target)) {
233
- fs.unlinkSync(change.target);
234
- }
235
- } else {
236
- if (!change.removed && fs.existsSync(change.target)) removePath(change.target);
237
- if (change.backup && fs.existsSync(change.backup)) fs.renameSync(change.backup, change.target);
238
- }
239
- } catch (error) {
240
- errors.push(error);
241
- }
242
- }
243
- if (errors.length) throw new AggregateError(errors, 'OpenCode install rollback failed');
244
- },
245
- };
246
- }
247
-
248
- function resolveTargetFile(file) {
249
- return fs.lstatSync(file).isSymbolicLink() ? fs.realpathSync(file) : file;
250
- }
251
-
252
- function publishStaging({ staging, target, bundleRoot, skillRoot, pluginFile, skills, retiredCleanup }) {
253
- const expected = new Set(skills);
254
- const staleSkills = [];
255
- if (fs.existsSync(skillRoot)) {
256
- for (const entry of fs.readdirSync(skillRoot, { withFileTypes: true })) {
257
- const candidate = path.join(skillRoot, entry.name);
258
- if (entry.isDirectory() && !expected.has(entry.name) && isManaged(candidate)) staleSkills.push(candidate);
259
- }
260
- }
261
- if (fs.existsSync(bundleRoot) && !isManaged(bundleRoot)) {
262
- throw new Error(`refusing to replace unmanaged OpenCode bundle: ${bundleRoot}`);
263
- }
264
- for (const name of skills) {
265
- const candidate = path.join(skillRoot, name);
266
- if (fs.existsSync(candidate) && !isManaged(candidate)) {
267
- throw new Error(`refusing to replace unmanaged OpenCode skill: ${candidate}`);
268
- }
269
- }
270
-
271
- const stagedConfig = path.join(staging, 'opencode.json');
272
- const stagedPlugin = path.join(staging, `${PLUGIN_NAME}.js`);
273
- writeAtomic(stagedPlugin, pluginSource());
274
- if (retiredCleanup.config !== null) writeAtomic(stagedConfig, retiredCleanup.config);
275
-
276
- const transaction = createInstallTransaction();
277
- try {
278
- transaction.replaceTree(path.join(staging, 'bundle'), bundleRoot);
279
- for (const name of skills) {
280
- transaction.replaceTree(path.join(staging, 'skills', name), path.join(skillRoot, name));
281
- }
282
- for (const candidate of staleSkills) transaction.removeTree(candidate);
283
- transaction.replaceFile(stagedPlugin, pluginFile);
284
- for (const file of retiredCleanup.files) transaction.removeFile(file);
285
- if (retiredCleanup.config !== null) {
286
- transaction.replaceFile(stagedConfig, retiredCleanup.configFile);
287
- }
288
- transaction.commit();
289
- } catch (error) {
290
- try {
291
- transaction.rollback();
292
- } catch (rollbackError) {
293
- throw new AggregateError([error, rollbackError], 'OpenCode install and rollback both failed');
294
- }
295
- throw error;
296
- }
297
-
298
- for (const dirName of ['commands', 'agents']) {
299
- const directory = path.join(target, dirName);
300
- if (fs.existsSync(directory) && fs.readdirSync(directory).length === 0) fs.rmdirSync(directory);
301
- }
302
- }
303
-
304
- function install(ctx = {}) {
305
- const target = resolveTarget(ctx);
306
- const repoRoot = resolveRepoRoot(ctx);
307
- const bundleRoot = path.join(target, BUNDLE_DIR);
308
- const skillRoot = path.join(target, 'skills');
309
- const pluginFile = path.join(target, 'plugins', `${PLUGIN_NAME}.js`);
310
- const cleanupAbsent = managedMetadata(bundleRoot)?.cleanup_absent
311
- || captureAbsent(target, ['.', 'skills', 'plugins']);
312
- ensureDir(target);
313
-
314
- if (fs.existsSync(pluginFile) && !fs.readFileSync(pluginFile, 'utf8').startsWith(PLUGIN_MARKER)) {
315
- throw new Error(`refusing to replace unmanaged OpenCode plugin: ${pluginFile}`);
316
- }
317
- const retiredCleanup = planRetiredCleanup(target);
318
- const staging = fs.mkdtempSync(path.join(target, `.${PLUGIN_NAME}-staging-`));
319
- const stagedBundle = path.join(staging, 'bundle');
320
- const stagedSkills = path.join(staging, 'skills');
321
- ensureDir(stagedBundle);
322
- ensureDir(stagedSkills);
323
- try {
324
- const skills = copySkills({ runtime: 'opencode', repoRoot, skillRoot: stagedSkills });
325
- for (const name of skills) markManaged(path.join(stagedSkills, name), { adapter: 'opencode', asset: 'skill', name });
326
- copyHooks({
327
- runtime: 'opencode', repoRoot, hookRoot: path.join(stagedBundle, 'hooks'),
328
- hooksManifest: buildHooksManifest(),
329
- });
330
- markManaged(stagedBundle, { adapter: 'opencode', plugin: PLUGIN_NAME });
331
- writePluginProvenance({
332
- adapter: 'opencode', repoRoot, stagingRoot: stagedBundle, publishedRoot: bundleRoot,
333
- contracts: { hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' } },
334
- });
335
- markManaged(stagedBundle, { cleanup_absent: cleanupAbsent });
336
-
337
- publishStaging({
338
- staging, target, bundleRoot, skillRoot, pluginFile, skills, retiredCleanup,
339
- });
340
- removePath(staging);
341
- return {
342
- target: bundleRoot,
343
- pluginRoot: bundleRoot,
344
- skillRoot,
345
- hookRoot: path.join(bundleRoot, 'hooks'),
346
- pluginFile,
347
- copied: { skills },
348
- };
349
- } catch (error) {
350
- if (fs.existsSync(staging)) removePath(staging);
351
- pruneCreatedEmpty(target, cleanupAbsent);
352
- throw error;
353
- }
354
- }
355
-
356
- function doctor(ctx = {}) {
357
- const target = resolveTarget(ctx);
358
- const bundleRoot = path.join(target, BUNDLE_DIR);
359
- const pluginFile = path.join(target, 'plugins', `${PLUGIN_NAME}.js`);
360
- const report = inspectPlugin({
361
- adapter: 'opencode', repoRoot: resolveRepoRoot(ctx), pluginRoot: bundleRoot,
362
- skillRoot: path.join(target, 'skills'), hookRoot: path.join(bundleRoot, 'hooks'),
363
- manifestFile: pluginFile,
364
- });
365
- let configClean = true;
366
- try {
367
- const configFile = path.join(target, 'opencode.json');
368
- if (fs.existsSync(configFile)) {
369
- const value = JSON.parse(fs.readFileSync(configFile, 'utf8'));
370
- configClean = !value?.mcp?.[PLUGIN_NAME];
371
- }
372
- } catch { configClean = false; }
373
- report.checks.no_mcp_registration = { status: configClean ? 'pass' : 'fail' };
374
- if (!configClean) report.issues.push({ code: 'RETIRED_MCP_REGISTRATION_PRESENT' });
375
- report.status = report.issues.length === 0 ? 'healthy' : 'degraded';
376
- return report;
377
- }
378
-
379
- function uninstall(ctx = {}) {
380
- const target = resolveTarget(ctx);
381
- const pluginRoot = path.join(target, BUNDLE_DIR);
382
- const cleanupAbsent = managedMetadata(pluginRoot)?.cleanup_absent || [];
383
- const skillRoot = path.join(target, 'skills');
384
- const removedSkills = [];
385
- for (const name of skillsForRuntime('opencode')) {
386
- if (removeManaged(path.join(skillRoot, name), `OpenCode skill ${name}`)) removedSkills.push(name);
387
- }
388
- const pluginFile = path.join(target, 'plugins', `${PLUGIN_NAME}.js`);
389
- if (fs.existsSync(pluginFile) && fs.readFileSync(pluginFile, 'utf8').startsWith(PLUGIN_MARKER)) fs.unlinkSync(pluginFile);
390
- cleanupRetired(target);
391
- const removedBundle = removeManaged(pluginRoot, 'OpenCode bundle');
392
- const cleaned = pruneCreatedEmpty(target, cleanupAbsent);
393
- return {
394
- target: pluginRoot, pluginRoot,
395
- removed: { bundle: removedBundle, skills: removedSkills },
396
- cleaned,
397
- };
398
- }
399
-
400
- module.exports = {
401
- name: 'opencode',
402
- PLUGIN_NAME,
403
- buildHooksManifest,
404
- resolveTarget,
405
- install,
406
- doctor,
407
- uninstall,
408
- };