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,357 +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 { execFileSync } = require('node:child_process');
9
- const { pathToFileURL } = require('node:url');
10
-
11
- const opencode = require('../opencode.js');
12
- const { parse: parseFm } = require('../_shared/frontmatter.cjs');
13
- const { skillsForRuntime } = require('../_shared/runtime-assets.cjs');
14
- const { initStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
15
- const changes = require('../../mcp-server/lib/change-workflow.cjs');
16
- const { createTask } = require('../../mcp-server/lib/state-ops.cjs');
17
-
18
- const REPO_ROOT = path.resolve(__dirname, '..', '..');
19
-
20
- function mkTarget() {
21
- return fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-opencode-'));
22
- }
23
-
24
- test('install builds a native OpenCode plugin and copies only allowlisted skills', () => {
25
- const target = mkTarget();
26
- try {
27
- const r = opencode.install({ configDir: target, repoRoot: REPO_ROOT });
28
- assert.ok(r.copied.commands.includes('ultra-init.md'));
29
- assert.ok(r.copied.skills.some((p) => p.includes('ultra-init/SKILL.md')));
30
- assert.ok(r.copied.plugins.includes('ultra-builder-pro.js'));
31
- assert.ok(fs.existsSync(path.join(target, 'plugins', 'ultra-builder-pro.js')));
32
- assert.deepEqual(
33
- fs.readdirSync(path.join(target, 'skills'), { withFileTypes: true })
34
- .filter((entry) => entry.isDirectory())
35
- .map((entry) => entry.name)
36
- .sort(),
37
- skillsForRuntime('opencode').sort(),
38
- );
39
-
40
- // OpenCode commands expose only fields accepted by the native command contract.
41
- const dst = fs.readFileSync(path.join(target, 'commands', 'ultra-init.md'), 'utf8');
42
- const { fm: dstFm } = parseFm(dst);
43
- assert.deepEqual(Object.keys(dstFm), ['description']);
44
-
45
- // forge an upper-case key to prove the transform works
46
- const hack = path.join(target, 'commands', 'upper.md');
47
- fs.writeFileSync(hack, '---\nDescription: mixed\n---\nbody\n');
48
- opencode.install({ configDir: target, repoRoot: REPO_ROOT });
49
- // reinstall re-runs the transform; the hack was outside commands/ in repo so
50
- // install doesn't touch it; we verify transform via a direct call:
51
- const { lowercaseKeys } = require('../_shared/frontmatter.cjs');
52
- assert.deepEqual(lowercaseKeys({ Description: 'x', Tags: ['A'] }), { description: 'x', tags: ['A'] });
53
- } finally {
54
- fs.rmSync(target, { recursive: true, force: true });
55
- }
56
- });
57
-
58
- test('install performs content-level OpenCode adaptation for commands, skills, references, and agents', () => {
59
- const target = mkTarget();
60
- try {
61
- opencode.install({ configDir: target, repoRoot: REPO_ROOT });
62
-
63
- const plan = fs.readFileSync(path.join(target, 'skills', 'ultra-plan', 'SKILL.md'), 'utf8');
64
- const learn = fs.readFileSync(path.join(target, 'skills', 'learn', 'SKILL.md'), 'utf8');
65
- const review = fs.readFileSync(path.join(target, 'skills', 'ultra-review', 'SKILL.md'), 'utf8');
66
- const codexCollab = fs.readFileSync(path.join(target, 'skills', 'codex-collab', 'SKILL.md'), 'utf8');
67
- const verify = fs.readFileSync(path.join(target, 'skills', 'ultra-verify', 'SKILL.md'), 'utf8');
68
-
69
- assert.deepEqual(Object.keys(parseFm(plan).fm), ['name', 'description']);
70
- assert.match(learn, /`~\/.config\/opencode\/skills`/);
71
- assert.doesNotMatch(learn, /_unverified|learned-[^\s/]*-unverified/i);
72
- assert.match(review, /OpenCode `task` tool/);
73
- assert.match(review, /scripts\/review_wait\.py/);
74
- assert.match(codexCollab, /OpenCode remains primary/);
75
- assert.match(verify, /Keep OpenCode responsible/);
76
- assert.match(verify, /host-analysis\.md/);
77
- assert.match(verify, /installed collaboration companion/);
78
- assert.doesNotMatch(verify, /codex exec|claude --safe-mode/);
79
- assert.doesNotMatch(plan, /LEGACY_STATE_MIGRATION_REQUIRED|v4\.4|v4\.5/);
80
- assert.match(plan, /never read or\s+write .*tasks\.json/i);
81
- assert.doesNotMatch(plan, /ultra-tools task create/);
82
-
83
- const markdown = [];
84
- for (const root of ['commands', 'skills', 'agents']) {
85
- const pending = [path.join(target, root)];
86
- while (pending.length) {
87
- const current = pending.pop();
88
- for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
89
- const file = path.join(current, entry.name);
90
- if (entry.isDirectory()) pending.push(file);
91
- else if (entry.isFile() && entry.name.endsWith('.md')) markdown.push(file);
92
- }
93
- }
94
- }
95
- const incompatible = /~\/.claude|CLAUDE\.md|AskUserQuestion|TaskCreate|TaskUpdate|TaskList|run_in_background:\s*true|--yolo|--full-auto|\/codex:|ask\.question|review\.run/;
96
- for (const file of markdown) {
97
- const text = fs.readFileSync(file, 'utf8');
98
- assert.doesNotMatch(text, incompatible, file);
99
- assert.doesNotMatch(text, /[\u3400-\u9fff]|ultra-review-findings-v1|Context7|Exa MCP|confidence\s*>=?\s*\d+/iu, file);
100
- }
101
- } finally {
102
- fs.rmSync(target, { recursive: true, force: true });
103
- }
104
- });
105
-
106
- test('install translates Claude agent metadata to native OpenCode agent contracts', () => {
107
- const target = mkTarget();
108
- try {
109
- opencode.install({ configDir: target, repoRoot: REPO_ROOT });
110
-
111
- const reviewTests = parseFm(
112
- fs.readFileSync(path.join(target, 'agents', 'review-tests.md'), 'utf8'),
113
- ).fm;
114
- assert.equal(reviewTests.mode, 'subagent');
115
- assert.equal(reviewTests.steps, 18);
116
- assert.equal(reviewTests.name, undefined);
117
- assert.equal(reviewTests.model, undefined);
118
- assert.equal(reviewTests.maxturns, undefined);
119
- assert.equal(reviewTests.tools, undefined);
120
- assert.equal(reviewTests.skills, undefined);
121
- assert.deepEqual(reviewTests.permission, {
122
- read: 'allow',
123
- edit: 'allow',
124
- glob: 'allow',
125
- grep: 'allow',
126
- list: 'allow',
127
- bash: 'allow',
128
- task: 'deny',
129
- external_directory: 'deny',
130
- todowrite: 'deny',
131
- question: 'deny',
132
- webfetch: 'deny',
133
- websearch: 'deny',
134
- codesearch: 'deny',
135
- lsp: 'deny',
136
- doom_loop: 'deny',
137
- skill: {
138
- '*': 'deny',
139
- 'testing-rules': 'allow',
140
- },
141
- });
142
-
143
- const tddRunner = parseFm(
144
- fs.readFileSync(path.join(target, 'agents', 'tdd-runner.md'), 'utf8'),
145
- ).fm;
146
- assert.equal(tddRunner.permission.edit, 'deny');
147
- assert.deepEqual(tddRunner.permission.skill, {
148
- '*': 'deny',
149
- 'testing-rules': 'allow',
150
- });
151
-
152
- const debuggerAgent = parseFm(
153
- fs.readFileSync(path.join(target, 'agents', 'debugger.md'), 'utf8'),
154
- ).fm;
155
- assert.equal(debuggerAgent.steps, 40);
156
- assert.equal(debuggerAgent.permission.skill, 'deny');
157
- } finally {
158
- fs.rmSync(target, { recursive: true, force: true });
159
- }
160
- });
161
-
162
- test('install writes a schema-safe opencode.json and keeps ownership outside host config', () => {
163
- const target = mkTarget();
164
- try {
165
- opencode.install({ configDir: target, repoRoot: REPO_ROOT });
166
- const config = JSON.parse(fs.readFileSync(path.join(target, 'opencode.json'), 'utf8'));
167
- assert.ok(config.mcp);
168
- assert.ok(config.mcp[opencode.MCP_SERVER_NAME]);
169
- assert.equal(config.mcp[opencode.MCP_SERVER_NAME].type, 'local');
170
- assert.equal(config.mcp[opencode.MCP_SERVER_NAME].enabled, true);
171
- assert.ok(Array.isArray(config.mcp[opencode.MCP_SERVER_NAME].command));
172
- assert.equal('_ubp_manifest' in config, false);
173
- assert.ok(fs.existsSync(path.join(target, opencode.BUNDLE_DIR, '.ubp-managed')));
174
-
175
- const plugin = fs.readFileSync(path.join(target, 'plugins', 'ultra-builder-pro.js'), 'utf8');
176
- assert.match(plugin, /experimental\.chat\.system\.transform/);
177
- assert.match(plugin, /experimental\.session\.compacting/);
178
- assert.match(plugin, /tool\.execute\.before/);
179
- assert.match(plugin, /\.ultra[\\/]tasks[\\/]tasks\.json/);
180
- assert.match(plugin, /throw new Error/);
181
- assert.match(plugin, /session\.compacted/);
182
- assert.match(plugin, /breadcrumb\.cjs/);
183
- assert.doesNotMatch(plugin, /workflow-state\.json|context-manifest\.json/);
184
- assert.doesNotMatch(plugin, /memory\.(?:retain|recall|reflect)|journal|observation|prompt[_ -]?capture/);
185
- assert.match(plugin, /canonical breadcrumb/);
186
- assert.doesNotMatch(plugin, /providerMetadata|External providers/);
187
- } finally {
188
- fs.rmSync(target, { recursive: true, force: true });
189
- }
190
- });
191
-
192
- test('OpenCode routes a missing authority to init and always protects task projections', async () => {
193
- const target = mkTarget();
194
- const project = mkTarget();
195
- try {
196
- opencode.install({ configDir: target, repoRoot: REPO_ROOT });
197
- fs.mkdirSync(path.join(project, '.ultra'), { recursive: true });
198
- const module = await import(`${pathToFileURL(path.join(target, 'plugins', 'ultra-builder-pro.js')).href}?baseline`);
199
- const plugin = await module.UltraBuilderProPlugin({ directory: project });
200
- const output = { system: [] };
201
- await plugin['experimental.chat.system.transform']({}, output);
202
- const text = output.system.join('\n');
203
- assert.match(text, /STATE_DB_MISSING/);
204
- assert.match(text, /ultra-init/);
205
- await assert.rejects(
206
- plugin['tool.execute.before'](
207
- { tool: 'apply_patch' },
208
- { args: { patch: '*** Begin Patch\n*** Update File: .ultra/tasks/tasks.json\n*** End Patch' } },
209
- ),
210
- /state\.db is authoritative/,
211
- );
212
- } finally {
213
- fs.rmSync(target, { recursive: true, force: true });
214
- fs.rmSync(project, { recursive: true, force: true });
215
- }
216
- });
217
-
218
- test('OpenCode injects the DB breadcrumb and ignores conflicting workflow projections', async () => {
219
- const target = mkTarget();
220
- const project = mkTarget();
221
- let state;
222
- try {
223
- opencode.install({ configDir: target, repoRoot: REPO_ROOT });
224
- execFileSync('git', ['init'], { cwd: project, stdio: 'ignore' });
225
- fs.writeFileSync(path.join(project, 'app.txt'), 'initial\n');
226
- execFileSync('git', ['add', 'app.txt'], { cwd: project, stdio: 'ignore' });
227
- execFileSync('git', ['-c', 'user.name=Ultra Tests', '-c', 'user.email=ultra@example.invalid', 'commit', '-m', 'initial'], {
228
- cwd: project, stdio: 'ignore',
229
- });
230
- state = initStateDb(path.join(project, '.ultra', 'state.db'));
231
- state.db.prepare(
232
- `INSERT INTO baselines
233
- (id, project_name, mode, status, approved_by, approval_note, converged_at)
234
- VALUES ('baseline-db', 'fixture', 'greenfield', 'ready', 'test', 'fixture', ?)`,
235
- ).run(new Date().toISOString());
236
- const { change } = changes.createChange(state.db, {
237
- id: 'db-authority-change', title: 'DB authority', kind: 'quick',
238
- intent: 'Use one authoritative injected state.',
239
- docs_impact: { status: 'none', rationale: 'test fixture' },
240
- }, { rootDir: project });
241
- const task = createTask(state.db, {
242
- id: 'db-authority-task', title: 'Use DB authority', type: 'bugfix', priority: 'P0',
243
- change_id: change.id,
244
- });
245
- changes.compileContext(state.db, {
246
- id: change.id,
247
- task_id: task.id,
248
- role: 'implement',
249
- gate: 'implementation',
250
- execution_contract: {
251
- slice_kind: 'tracer_bullet', public_seam: 'OpenCode injection',
252
- verification_command: 'node --test adapters/tests/opencode.test.cjs',
253
- },
254
- next_action: 'Verify the DB-only OpenCode injection.',
255
- }, { rootDir: project });
256
-
257
- fs.writeFileSync(path.join(project, '.ultra', 'workflow-state.json'), JSON.stringify({
258
- command: 'ultra-dev', task_id: 'projection-task', status: 'active', step: 'wrong',
259
- }));
260
- const projectionRoot = path.join(project, '.ultra', 'changes', 'active', 'projection-change');
261
- fs.mkdirSync(projectionRoot, { recursive: true });
262
- fs.writeFileSync(path.join(projectionRoot, 'context-manifest.json'), JSON.stringify({
263
- schema_version: '2.0',
264
- baseline: { id: 'projection-baseline', mode: 'brownfield', status: 'ready' },
265
- change: { id: 'projection-change', kind: 'quick', status: 'active' },
266
- selected_task: { id: 'projection-task', status: 'in_progress' },
267
- readiness: { status: 'ready', blockers: [], warnings: [] },
268
- }));
269
-
270
- const module = await import(`${pathToFileURL(path.join(target, 'plugins', 'ultra-builder-pro.js')).href}?db-authority`);
271
- const plugin = await module.UltraBuilderProPlugin({ directory: project });
272
- const output = { system: [] };
273
- await plugin['experimental.chat.system.transform']({}, output);
274
- const text = output.system.join('\n');
275
- assert.match(text, /Change: db-authority-change/);
276
- assert.match(text, /Task: db-authority-task/);
277
- assert.match(text, /Role: implement/);
278
- assert.doesNotMatch(text, /projection-change|projection-task|projection-baseline/);
279
- } finally {
280
- if (state) closeStateDb(state.db);
281
- fs.rmSync(target, { recursive: true, force: true });
282
- fs.rmSync(project, { recursive: true, force: true });
283
- }
284
- });
285
-
286
- test('install preserves user mcp entries; uninstall removes only owned Ultra assets', () => {
287
- const target = mkTarget();
288
- const configFile = path.join(target, 'opencode.json');
289
- try {
290
- fs.writeFileSync(configFile, JSON.stringify({
291
- theme: 'dark',
292
- mcp: { my_server: { command: 'node', args: ['./mine.js'] } },
293
- }, null, 2));
294
-
295
- opencode.install({ configDir: target, repoRoot: REPO_ROOT });
296
- const merged = JSON.parse(fs.readFileSync(configFile, 'utf8'));
297
- assert.equal(merged.theme, 'dark');
298
- assert.ok(merged.mcp.my_server);
299
- assert.ok(merged.mcp[opencode.MCP_SERVER_NAME]);
300
-
301
- opencode.uninstall({ configDir: target });
302
- const after = JSON.parse(fs.readFileSync(configFile, 'utf8'));
303
- assert.equal(after.theme, 'dark');
304
- assert.ok(after.mcp.my_server);
305
- assert.ok(!after.mcp[opencode.MCP_SERVER_NAME]);
306
- assert.ok(!('_ubp_manifest' in after));
307
- assert.ok(!fs.existsSync(path.join(target, 'commands')));
308
- assert.ok(!fs.existsSync(path.join(target, 'plugins', 'ultra-builder-pro.js')));
309
- } finally {
310
- fs.rmSync(target, { recursive: true, force: true });
311
- }
312
- });
313
-
314
- test('shared OpenCode asset roots keep unrelated user commands, skills, agents, and runtime files', () => {
315
- const target = mkTarget();
316
- const userFiles = [
317
- ['commands', 'user-command.md'],
318
- ['skills', 'user-skill', 'SKILL.md'],
319
- ['agents', 'user-agent.md'],
320
- ['runtime', 'user-runtime.txt'],
321
- ];
322
- try {
323
- for (const parts of userFiles) {
324
- const file = path.join(target, ...parts);
325
- fs.mkdirSync(path.dirname(file), { recursive: true });
326
- fs.writeFileSync(file, 'user-owned');
327
- }
328
-
329
- opencode.install({ configDir: target, repoRoot: REPO_ROOT });
330
- opencode.uninstall({ configDir: target });
331
-
332
- for (const parts of userFiles) {
333
- assert.equal(fs.readFileSync(path.join(target, ...parts), 'utf8'), 'user-owned');
334
- }
335
- assert.equal(fs.existsSync(path.join(target, 'commands', '.ubp-managed')), false);
336
- assert.equal(fs.existsSync(path.join(target, 'skills', '.ubp-managed')), false);
337
- assert.equal(fs.existsSync(path.join(target, 'agents', '.ubp-managed')), false);
338
- } finally {
339
- fs.rmSync(target, { recursive: true, force: true });
340
- }
341
- });
342
-
343
- test('install refuses to overwrite an unmanaged OpenCode asset with the same name', () => {
344
- const target = mkTarget();
345
- const conflict = path.join(target, 'skills', 'ultra-init', 'SKILL.md');
346
- try {
347
- fs.mkdirSync(path.dirname(conflict), { recursive: true });
348
- fs.writeFileSync(conflict, 'user-owned');
349
- assert.throws(
350
- () => opencode.install({ configDir: target, repoRoot: REPO_ROOT }),
351
- /unmanaged OpenCode skill/,
352
- );
353
- assert.equal(fs.readFileSync(conflict, 'utf8'), 'user-owned');
354
- } finally {
355
- fs.rmSync(target, { recursive: true, force: true });
356
- }
357
- });
@@ -1,35 +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 journal = require('./archive-journal.cjs');
10
-
11
- test('archive journal restores the exact active directory when finalization cannot run', () => {
12
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-archive-journal-'));
13
- const source = path.join(rootDir, '.ultra', 'changes', 'active', 'change-a');
14
- fs.mkdirSync(source, { recursive: true });
15
- fs.writeFileSync(path.join(source, 'intent.md'), '# Change A\n');
16
- try {
17
- const prepared = journal.prepareArchiveMove({
18
- rootDir,
19
- change: { id: 'change-a', title: 'Change A', artifact_root: '.ultra/changes/active/change-a' },
20
- summary: 'Archive Change A.', baselineUpdates: [],
21
- noBaselineChangeReason: 'No baseline content changed.',
22
- now: new Date('2026-07-18T00:00:00.000Z'),
23
- });
24
- assert.equal(fs.existsSync(prepared.source), false);
25
- assert.equal(journal.listArchiveIntents(rootDir).length, 1);
26
-
27
- journal.rollbackArchiveIntent(rootDir, prepared.intent);
28
- assert.equal(fs.existsSync(source), true);
29
- assert.equal(fs.existsSync(path.join(source, 'intent.md')), true);
30
- assert.equal(fs.existsSync(path.join(source, 'archive-summary.md')), false);
31
- assert.equal(journal.listArchiveIntents(rootDir).length, 0);
32
- } finally {
33
- fs.rmSync(rootDir, { recursive: true, force: true });
34
- }
35
- });