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.
- package/.ultra-template/docs/research/README.md +11 -7
- package/.ultra-template/specs/architecture.md +36 -36
- package/.ultra-template/specs/discovery.md +46 -33
- package/.ultra-template/specs/product.md +38 -29
- package/.ultra-template/specs/research-distillate.md +48 -0
- package/.ultra-template/tasks/contexts/TEMPLATE.md +11 -28
- package/CHANGELOG.md +77 -1
- package/README.md +46 -24
- package/adapters/_shared/codex-assets.cjs +6 -2
- package/adapters/_shared/file-ops.cjs +6 -2
- package/adapters/_shared/handbook.cjs +2 -1
- package/adapters/claude.js +3 -2
- package/adapters/codex.js +1 -0
- package/adapters/kimi.js +3 -2
- package/adapters/opencode.js +3 -2
- package/agents/review-coordinator.md +9 -6
- package/commands/ultra-change.md +1 -1
- package/commands/ultra-plan.md +1 -1
- package/commands/ultra-research.md +1 -1
- package/commands/ultra-think.md +2 -2
- package/docs/AGENT-CONTEXT.md +43 -19
- package/docs/ARCHITECTURE.md +54 -13
- package/docs/PLAN.zh-CN.md +4 -2
- package/docs/ROADMAP.md +10 -4
- package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
- package/docs/STATE-DB-ACCESS-POLICY.md +33 -29
- package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
- package/docs/WORKFLOW-LIFECYCLE.md +342 -0
- package/hooks/README.md +6 -1
- package/hooks/health_check.py +12 -3
- package/hooks/subagent_tracker.py +52 -77
- package/mcp-server/hook-event.cjs +34 -0
- package/mcp-server/lib/archive-journal.cjs +17 -5
- package/mcp-server/lib/baseline-workflow.cjs +158 -18
- package/mcp-server/lib/change-workflow.cjs +598 -85
- package/mcp-server/lib/context-spine.cjs +305 -25
- package/mcp-server/lib/decision-dialogue.cjs +713 -0
- package/mcp-server/lib/doctor.cjs +42 -3
- package/mcp-server/lib/init-project.cjs +210 -16
- package/mcp-server/lib/plan-store.cjs +4 -1
- package/mcp-server/lib/pricing.cjs +7 -10
- package/mcp-server/lib/project-breadcrumb.cjs +15 -0
- package/mcp-server/lib/projector.cjs +57 -3
- package/mcp-server/lib/spec-learning.cjs +79 -2
- package/mcp-server/lib/state-db.cjs +168 -8
- package/mcp-server/lib/state-ops.cjs +160 -9
- package/mcp-server/lib/workflow-state.cjs +2305 -0
- package/mcp-server/server.cjs +215 -15
- package/orchestrator/daemon.cjs +1 -2
- package/orchestrator/dispatch-rules.cjs +5 -31
- package/orchestrator/planner/plan-builder.cjs +21 -12
- package/package.json +13 -3
- package/skills/ultra-change/SKILL.md +94 -80
- package/skills/ultra-change/references/change-contract.md +36 -0
- package/skills/ultra-deliver/SKILL.md +76 -79
- package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
- package/skills/ultra-dev/SKILL.md +62 -59
- package/skills/ultra-doctor/SKILL.md +61 -82
- package/skills/ultra-init/SKILL.md +101 -103
- package/skills/ultra-init/references/brownfield-adoption.md +2 -2
- package/skills/ultra-plan/SKILL.md +99 -85
- package/skills/ultra-plan/references/semantic-preflight.md +18 -0
- package/skills/ultra-research/SKILL.md +102 -60
- package/skills/ultra-research/references/00-problem-validation.md +36 -0
- package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
- package/skills/ultra-research/references/02-market-assessment.md +30 -0
- package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
- package/skills/ultra-research/references/04-product-strategy.md +32 -0
- package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
- package/skills/ultra-research/references/10-user-personas.md +29 -0
- package/skills/ultra-research/references/11-user-scenarios.md +28 -0
- package/skills/ultra-research/references/20-user-stories.md +30 -0
- package/skills/ultra-research/references/21-features-scope.md +29 -0
- package/skills/ultra-research/references/22-success-metrics.md +28 -0
- package/skills/ultra-research/references/30-architecture-context.md +30 -0
- package/skills/ultra-research/references/31-solution-strategy.md +28 -0
- package/skills/ultra-research/references/32-building-blocks.md +28 -0
- package/skills/ultra-research/references/40-deployment.md +29 -0
- package/skills/ultra-research/references/41-quality-risks.md +28 -0
- package/skills/ultra-research/references/99-synthesis.md +35 -0
- package/skills/ultra-review/SKILL.md +63 -73
- package/skills/ultra-review/references/review-modes.md +27 -0
- package/skills/ultra-review/references/unified-schema.md +25 -0
- package/skills/ultra-review/scripts/review_wait.py +40 -3
- package/skills/ultra-status/SKILL.md +41 -41
- package/skills/ultra-test/SKILL.md +54 -55
- package/skills/ultra-think/SKILL.md +26 -12
- package/skills/ultra-think/references/decision-dialogue.md +130 -0
- package/skills/ultra-verify/SKILL.md +1 -1
- package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
- package/spec/cli-protocol.md +17 -3
- package/spec/mcp-tools.yaml +683 -64
- package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
- package/spec/schemas/delivery-report.v1.schema.json +111 -0
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/state-db.sql +140 -1
- package/spec/schemas/tasks.v4.5.schema.json +8 -1
- package/spec/schemas/test-report.v1.schema.json +194 -0
- package/ultra-tools/cli.cjs +1 -1
- package/ultra-tools/commands/migrate.cjs +3 -3
- package/ultra-tools/commands/status.cjs +228 -12
- package/.ultra-template/delivery-report.json +0 -9
- package/.ultra-template/test-report.json +0 -8
- package/adapters/_shared/tests/file-ops.test.cjs +0 -54
- package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
- package/adapters/_shared/tests/handbook.test.cjs +0 -101
- package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
- package/adapters/_shared/tests/provenance.test.cjs +0 -133
- package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
- package/adapters/_shared/tests/runtime-assets.test.cjs +0 -149
- package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
- package/adapters/_shared/tests/validate.test.cjs +0 -30
- package/adapters/tests/claude.test.cjs +0 -138
- package/adapters/tests/codex-hook.test.cjs +0 -142
- package/adapters/tests/codex.test.cjs +0 -430
- package/adapters/tests/kimi-hook.test.cjs +0 -149
- package/adapters/tests/kimi.test.cjs +0 -259
- package/adapters/tests/opencode.test.cjs +0 -357
- package/mcp-server/lib/archive-journal.test.cjs +0 -35
- package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
- package/mcp-server/lib/change-workflow.test.cjs +0 -971
- package/mcp-server/lib/context-spine.test.cjs +0 -93
- package/mcp-server/lib/doctor.test.cjs +0 -148
- package/mcp-server/lib/plan-store.test.cjs +0 -132
- package/mcp-server/lib/prd-parser.test.cjs +0 -82
- package/mcp-server/lib/project-breadcrumb.test.cjs +0 -112
- package/mcp-server/lib/runtime-state.test.cjs +0 -92
- package/mcp-server/lib/spec-learning.test.cjs +0 -67
- package/mcp-server/lib/state-db.test.cjs +0 -314
- package/mcp-server/lib/task-expander.test.cjs +0 -169
- package/mcp-server/lib/topo.test.cjs +0 -158
- package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
- package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
- package/mcp-server/tests/concurrency-update.test.cjs +0 -96
- package/mcp-server/tests/concurrency.test.cjs +0 -112
- package/mcp-server/tests/db-ops.test.cjs +0 -121
- package/mcp-server/tests/init-project.test.cjs +0 -393
- package/mcp-server/tests/migrate.test.cjs +0 -232
- package/mcp-server/tests/projector.test.cjs +0 -162
- package/mcp-server/tests/server-contract.test.cjs +0 -1467
- package/mcp-server/tests/staleness.test.cjs +0 -190
- package/mcp-server/tests/state-db.test.cjs +0 -106
- package/mcp-server/tests/state-ops.test.cjs +0 -449
- package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
- package/mcp-server/tests/telemetry.test.cjs +0 -219
- package/orchestrator/planner/plan-builder.test.cjs +0 -165
- package/orchestrator/tests/auto-merge.test.cjs +0 -216
- package/orchestrator/tests/daemon.test.cjs +0 -311
- package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
- package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
- package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
- package/orchestrator/tests/recovery.test.cjs +0 -190
- package/orchestrator/tests/session-runner.test.cjs +0 -314
- package/orchestrator/tests/skill-miner.test.cjs +0 -147
- package/orchestrator/tests/worktree-manager.test.cjs +0 -148
- package/skills/ultra-research/references/architecture.md +0 -38
- package/skills/ultra-research/references/problem-product.md +0 -39
- package/skills/ultra-research/references/quality-delivery.md +0 -34
- package/skills/ultra-research/references/synthesis.md +0 -38
- package/skills/ultra-research/references/users-scope.md +0 -40
- package/ultra-tools/legacy-memory.test.cjs +0 -80
- package/ultra-tools/status-command.test.cjs +0 -181
- 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
|
-
});
|