ultra-builder-pro-cli 0.5.3 → 0.7.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/changes/active/.gitkeep +1 -0
- package/.ultra-template/changes/archive/.gitkeep +1 -0
- package/CHANGELOG.md +90 -14
- package/README.md +38 -32
- package/adapters/_shared/codex-assets.cjs +32 -151
- package/adapters/_shared/frontmatter.cjs +2 -2
- package/adapters/_shared/handbook.cjs +10 -10
- package/adapters/_shared/path-rewrite.cjs +0 -3
- package/adapters/_shared/runtime-assets.cjs +13 -4
- package/adapters/_shared/tests/handbook.test.cjs +4 -1
- package/adapters/_shared/tests/path-rewrite.test.cjs +1 -3
- package/adapters/_shared/tests/resolve-target.test.cjs +1 -11
- package/adapters/_shared/tests/runtime-assets.test.cjs +7 -6
- package/adapters/opencode.js +82 -26
- package/adapters/tests/claude.test.cjs +1 -4
- package/adapters/tests/codex.test.cjs +9 -8
- package/adapters/tests/opencode.test.cjs +17 -11
- package/bin/install.js +6 -28
- package/bin/orchestrator.js +12 -30
- package/commands/ultra-change.md +47 -0
- package/commands/ultra-deliver.md +14 -7
- package/commands/ultra-dev.md +6 -3
- package/commands/ultra-doctor.md +36 -0
- package/commands/ultra-init.md +4 -3
- package/commands/ultra-status.md +7 -5
- package/commands/ultra-test.md +2 -1
- package/docs/AGENT-CONTEXT.md +35 -28
- package/docs/ARCHITECTURE.md +49 -21
- package/docs/PLAN.zh-CN.md +67 -68
- package/docs/ROADMAP.md +17 -17
- package/docs/RUNTIME-COMPAT-MATRIX.md +63 -62
- package/hooks/README.md +8 -7
- package/hooks/active_task_context.py +22 -15
- package/hooks/health_check.py +136 -38
- package/hooks/workflow_context.py +120 -30
- package/mcp-server/lib/change-workflow.cjs +556 -0
- package/mcp-server/lib/change-workflow.test.cjs +259 -0
- package/mcp-server/lib/doctor.cjs +112 -0
- package/mcp-server/lib/doctor.test.cjs +86 -0
- package/mcp-server/lib/init-project.cjs +32 -6
- package/mcp-server/lib/pricing.cjs +0 -6
- package/mcp-server/lib/runtime-state.cjs +222 -0
- package/mcp-server/lib/runtime-state.test.cjs +92 -0
- package/mcp-server/lib/state-db.cjs +31 -8
- package/mcp-server/lib/state-ops.cjs +24 -3
- package/mcp-server/lib/telemetry.cjs +4 -0
- package/mcp-server/server.cjs +102 -10
- package/mcp-server/tests/db-ops.test.cjs +5 -1
- package/mcp-server/tests/init-project.test.cjs +45 -0
- package/mcp-server/tests/server-contract.test.cjs +140 -1
- package/mcp-server/tests/state-db.test.cjs +44 -7
- package/mcp-server/tests/state-ops.test.cjs +42 -0
- package/mcp-server/tests/telemetry.test.cjs +14 -0
- package/orchestrator/daemon.cjs +39 -0
- package/orchestrator/dispatch-rules.cjs +15 -4
- package/orchestrator/tests/daemon.test.cjs +63 -12
- package/orchestrator/tests/dispatch-rules.test.cjs +12 -10
- package/package.json +5 -7
- package/settings.json +1 -2
- package/skills/ultra-change/SKILL.md +140 -0
- package/skills/ultra-deliver/SKILL.md +81 -15
- package/skills/ultra-dev/SKILL.md +32 -12
- package/skills/ultra-doctor/SKILL.md +90 -0
- package/skills/ultra-init/SKILL.md +8 -6
- package/skills/ultra-review/SKILL.md +16 -0
- package/skills/ultra-status/SKILL.md +30 -7
- package/skills/ultra-test/SKILL.md +14 -2
- package/skills/ultra-verify/SKILL.md +22 -41
- package/skills/ultra-verify/evals/evals.json +22 -24
- package/skills/ultra-verify/references/confidence-system.md +15 -32
- package/skills/ultra-verify/references/cross-verify-modes.md +14 -59
- package/skills/ultra-verify/scripts/verify_wait.py +44 -81
- package/spec/cli-protocol.md +12 -1
- package/spec/mcp-tools.yaml +340 -4
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/skill-manifest.schema.json +2 -2
- package/spec/schemas/state-db.sql +119 -2
- package/ultra-tools/cli.cjs +3 -0
- package/ultra-tools/commands/system.cjs +57 -0
- package/ultra-tools/system-command.test.cjs +53 -0
- package/adapters/_shared/md-to-toml.cjs +0 -54
- package/adapters/_shared/rtk-detect.cjs +0 -86
- package/adapters/_shared/tests/md-to-toml.test.cjs +0 -48
- package/adapters/_shared/tests/rtk-detect.test.cjs +0 -116
- package/adapters/gemini.js +0 -204
- package/adapters/tests/gemini.test.cjs +0 -112
- package/docs/LEGACY-HERMES.md +0 -1175
- package/orchestrator/code-graph-watcher.cjs +0 -121
- package/orchestrator/tests/code-graph-watcher.test.cjs +0 -183
- package/skills/gemini-collab/SKILL.md +0 -51
- package/skills/gemini-collab/evals/evals.json +0 -42
|
@@ -14,22 +14,22 @@ const HOSTS = Object.freeze({
|
|
|
14
14
|
claude: Object.freeze({
|
|
15
15
|
name: 'Claude Code',
|
|
16
16
|
handbook: ['.claude', 'CLAUDE.md'],
|
|
17
|
-
invocation: '`/ultra-init`, `/ultra-research`, `/ultra-plan`, `/ultra-dev`, `/ultra-test`, `/ultra-review`, and `/ultra-deliver`; diagnostics use `/ultra-status
|
|
18
|
-
collaboration: '`/codex-collab`
|
|
17
|
+
invocation: '`/ultra-init`, `/ultra-research`, `/ultra-plan`, `/ultra-change`, `/ultra-dev`, `/ultra-test`, `/ultra-review`, and `/ultra-deliver`; diagnostics use `/ultra-status`, `/ultra-think`, and `/ultra-doctor`',
|
|
18
|
+
collaboration: '`/codex-collab` is an explicitly requested read-only advisor',
|
|
19
19
|
coordination: 'Claude Code task UI is host coordination; it does not replace Ultra task state',
|
|
20
20
|
}),
|
|
21
21
|
codex: Object.freeze({
|
|
22
22
|
name: 'Codex',
|
|
23
23
|
handbook: ['.codex', 'AGENTS.md'],
|
|
24
|
-
invocation: '`$ultra-builder-pro:ultra-init`, `$ultra-builder-pro:ultra-research`, `$ultra-builder-pro:ultra-plan`, `$ultra-builder-pro:ultra-dev`, `$ultra-builder-pro:ultra-test`, `$ultra-builder-pro:ultra-review`, and `$ultra-builder-pro:ultra-deliver`; diagnostics use `$ultra-builder-pro:ultra-status
|
|
25
|
-
collaboration: '`$ultra-builder-pro:cc-collab`
|
|
24
|
+
invocation: '`$ultra-builder-pro:ultra-init`, `$ultra-builder-pro:ultra-research`, `$ultra-builder-pro:ultra-plan`, `$ultra-builder-pro:ultra-change`, `$ultra-builder-pro:ultra-dev`, `$ultra-builder-pro:ultra-test`, `$ultra-builder-pro:ultra-review`, and `$ultra-builder-pro:ultra-deliver`; diagnostics use `$ultra-builder-pro:ultra-status`, `$ultra-builder-pro:ultra-think`, and `$ultra-builder-pro:ultra-doctor`',
|
|
25
|
+
collaboration: '`$ultra-builder-pro:cc-collab` is an explicitly requested read-only advisor',
|
|
26
26
|
coordination: 'The Codex plan is turn-facing coordination; it does not replace Ultra task state',
|
|
27
27
|
}),
|
|
28
28
|
opencode: Object.freeze({
|
|
29
29
|
name: 'OpenCode',
|
|
30
30
|
handbook: ['.config', 'opencode', 'AGENTS.md'],
|
|
31
|
-
invocation: '`/ultra-init`, `/ultra-research`, `/ultra-plan`, `/ultra-dev`, `/ultra-test`, `/ultra-review`, and `/ultra-deliver`; diagnostics use `/ultra-status
|
|
32
|
-
collaboration: '`/cc-collab
|
|
31
|
+
invocation: '`/ultra-init`, `/ultra-research`, `/ultra-plan`, `/ultra-change`, `/ultra-dev`, `/ultra-test`, `/ultra-review`, and `/ultra-deliver`; diagnostics use `/ultra-status`, `/ultra-think`, and `/ultra-doctor`',
|
|
32
|
+
collaboration: '`/cc-collab` and `/codex-collab` are explicitly requested read-only advisors',
|
|
33
33
|
coordination: 'OpenCode session coordination does not replace Ultra task state',
|
|
34
34
|
}),
|
|
35
35
|
});
|
|
@@ -52,11 +52,11 @@ function renderHandbook(runtime) {
|
|
|
52
52
|
`- Workflow entry points: ${value.invocation}.`,
|
|
53
53
|
`- Collaboration boundary: ${value.collaboration}. The current host remains primary and owns final verification.`,
|
|
54
54
|
`- Coordination boundary: ${value.coordination}.`,
|
|
55
|
-
'- Authority: `.ultra/state.db` is the only durable Ultra authority for tasks, sessions, events, telemetry, and review evidence. Generated JSON and Markdown are projections or workflow artifacts.',
|
|
56
|
-
'- Memory boundary: Ultra Builder Pro does not capture prompts, transcripts, observations, summaries,
|
|
57
|
-
'- Hook boundary: Ultra hooks
|
|
55
|
+
'- Authority: `.ultra/state.db` is the only durable Ultra authority for changes, tasks, sessions, events, incidents, projection state, telemetry, and review evidence. Generated JSON and Markdown are projections or workflow artifacts.',
|
|
56
|
+
'- Memory and graph boundary: Ultra Builder Pro does not capture prompts, transcripts, observations, summaries, cross-session memory, or code-graph content. Separately installed providers own that data; Ultra may store only their metadata references in a change context manifest.',
|
|
57
|
+
'- Hook boundary: Ultra hooks observe workflow/change lifecycle only. Health/context may run when `.ultra/state.db` exists; projection protection is always authoritative, while edit/compact/stop/subagent enforcement remains active-workflow scoped. Generic command blocking and post-edit policy stay in user or repository governance.',
|
|
58
58
|
'- Agent boundary: the bundled review and debugging agents are bounded workers. They use the current checkout and parent-supplied context, do not own private persistent state, and never replace the primary agent.',
|
|
59
|
-
'- Package boundary: only the
|
|
59
|
+
'- Package boundary: only the twelve Ultra workflows, four internal review-rule skills, and host-specific collaboration companions belong to this plugin. General browser, deployment, discovery, and framework skills must be installed from their owners.',
|
|
60
60
|
END_MARKER,
|
|
61
61
|
].join('\n');
|
|
62
62
|
}
|
|
@@ -14,7 +14,6 @@ const RUNTIME_SKILL_ROOT = {
|
|
|
14
14
|
claude: '.claude/skills',
|
|
15
15
|
opencode: '.config/opencode/skills',
|
|
16
16
|
codex: '.agents/skills',
|
|
17
|
-
gemini: '.gemini/extensions/ultra-builder-pro/skills',
|
|
18
17
|
};
|
|
19
18
|
|
|
20
19
|
function parseSkillRef(ref) {
|
|
@@ -52,7 +51,6 @@ function runtimeRoot(runtime) {
|
|
|
52
51
|
case 'claude': return '.claude';
|
|
53
52
|
case 'opencode': return '.config/opencode';
|
|
54
53
|
case 'codex': return '.agents';
|
|
55
|
-
case 'gemini': return '.gemini/extensions/ultra-builder-pro';
|
|
56
54
|
default: throw new Error(`unknown runtime: ${runtime}`);
|
|
57
55
|
}
|
|
58
56
|
}
|
|
@@ -62,7 +60,6 @@ function localRoot(runtime) {
|
|
|
62
60
|
case 'claude': return '.claude';
|
|
63
61
|
case 'opencode': return '.opencode';
|
|
64
62
|
case 'codex': return '.agents';
|
|
65
|
-
case 'gemini': return '.gemini/extensions/ultra-builder-pro';
|
|
66
63
|
default: throw new Error(`unknown runtime: ${runtime}`);
|
|
67
64
|
}
|
|
68
65
|
}
|
|
@@ -18,6 +18,8 @@ const CORE_PUBLIC_SKILLS = Object.freeze([
|
|
|
18
18
|
'ultra-deliver',
|
|
19
19
|
'ultra-status',
|
|
20
20
|
'ultra-think',
|
|
21
|
+
'ultra-change',
|
|
22
|
+
'ultra-doctor',
|
|
21
23
|
]);
|
|
22
24
|
|
|
23
25
|
const INTERNAL_AGENT_SKILLS = Object.freeze([
|
|
@@ -27,11 +29,12 @@ const INTERNAL_AGENT_SKILLS = Object.freeze([
|
|
|
27
29
|
'testing-rules',
|
|
28
30
|
]);
|
|
29
31
|
|
|
32
|
+
const SUPPORTED_RUNTIMES = Object.freeze(['claude', 'opencode', 'codex']);
|
|
33
|
+
|
|
30
34
|
const COLLAB_SKILLS_BY_RUNTIME = Object.freeze({
|
|
31
|
-
claude: Object.freeze(['codex-collab', '
|
|
32
|
-
codex: Object.freeze(['cc-collab', '
|
|
33
|
-
opencode: Object.freeze(['cc-collab', 'codex-collab', '
|
|
34
|
-
gemini: Object.freeze(['cc-collab', 'codex-collab', 'ultra-verify']),
|
|
35
|
+
claude: Object.freeze(['codex-collab', 'ultra-verify']),
|
|
36
|
+
codex: Object.freeze(['cc-collab', 'ultra-verify']),
|
|
37
|
+
opencode: Object.freeze(['cc-collab', 'codex-collab', 'ultra-verify']),
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
const RETIRED_SKILLS = Object.freeze([
|
|
@@ -60,11 +63,17 @@ function skillsForRuntime(runtime) {
|
|
|
60
63
|
return [...CORE_PUBLIC_SKILLS, ...INTERNAL_AGENT_SKILLS, ...collab];
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
function isSupportedRuntime(runtime) {
|
|
67
|
+
return SUPPORTED_RUNTIMES.includes(runtime);
|
|
68
|
+
}
|
|
69
|
+
|
|
63
70
|
module.exports = {
|
|
64
71
|
CORE_PUBLIC_SKILLS,
|
|
65
72
|
INTERNAL_AGENT_SKILLS,
|
|
73
|
+
SUPPORTED_RUNTIMES,
|
|
66
74
|
COLLAB_SKILLS_BY_RUNTIME,
|
|
67
75
|
RETIRED_SKILLS,
|
|
68
76
|
WORKFLOW_HOOK_FILES,
|
|
77
|
+
isSupportedRuntime,
|
|
69
78
|
skillsForRuntime,
|
|
70
79
|
};
|
|
@@ -25,7 +25,10 @@ test('renders one common contract with host-native invocation syntax', () => {
|
|
|
25
25
|
assert.match(opencode, /\/ultra-plan/);
|
|
26
26
|
for (const rendered of [claude, codex, opencode]) {
|
|
27
27
|
assert.match(rendered, /\.ultra\/state\.db/);
|
|
28
|
-
assert.match(rendered, /
|
|
28
|
+
assert.match(rendered, /Separately installed providers/);
|
|
29
|
+
assert.match(rendered, /ultra-change/);
|
|
30
|
+
assert.match(rendered, /ultra-doctor/);
|
|
31
|
+
assert.match(rendered, /metadata references/);
|
|
29
32
|
assert.doesNotMatch(rendered, /\.ultra\/memory|memory\.retain|memory\.recall/);
|
|
30
33
|
assert.match(rendered, new RegExp(BEGIN_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
31
34
|
assert.match(rendered, new RegExp(END_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
@@ -26,12 +26,10 @@ test('resolveSkillRef routes to runtime-specific root under homeDir', () => {
|
|
|
26
26
|
const claude = resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'claude', homeDir: home });
|
|
27
27
|
const opencode = resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'opencode', homeDir: home });
|
|
28
28
|
const codex = resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'codex', homeDir: home });
|
|
29
|
-
const gemini = resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'gemini', homeDir: home });
|
|
30
29
|
|
|
31
30
|
assert.equal(claude, path.join(home, '.claude/skills/ultra-init/SKILL.md'));
|
|
32
31
|
assert.equal(opencode, path.join(home, '.config/opencode/skills/ultra-init/SKILL.md'));
|
|
33
32
|
assert.equal(codex, path.join(home, '.agents/skills/ultra-init/SKILL.md'));
|
|
34
|
-
assert.equal(gemini, path.join(home, '.gemini/extensions/ultra-builder-pro/skills/ultra-init/SKILL.md'));
|
|
35
33
|
|
|
36
34
|
assert.throws(
|
|
37
35
|
() => resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'unknown', homeDir: home }),
|
|
@@ -47,7 +45,7 @@ test('rewriteWorkflowRefInText + resolveAssetTarget give predictable paths', ()
|
|
|
47
45
|
assert.equal(out, `see ${path.join(home, '.claude/skills/ultra-init/SKILL.md')} for details`);
|
|
48
46
|
|
|
49
47
|
// Cover every runtime × every scope so runtimeRoot/localRoot branches hit
|
|
50
|
-
for (const rt of ['claude', 'opencode', 'codex'
|
|
48
|
+
for (const rt of ['claude', 'opencode', 'codex']) {
|
|
51
49
|
const g = resolveAssetTarget('x', { runtime: rt, scope: 'global', homeDir: home, cwd });
|
|
52
50
|
const l = resolveAssetTarget('x', { runtime: rt, scope: 'local', homeDir: home, cwd });
|
|
53
51
|
assert.ok(g.startsWith(home));
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
// four adapter.resolveTarget implementations.
|
|
3
|
+
// Scope-precedence contract across every supported adapter.resolveTarget.
|
|
5
4
|
//
|
|
6
5
|
// Each adapter honors the same ordering:
|
|
7
6
|
// ctx.configDir > ctx.scope='global' → env var or homeDir/.<runtime>
|
|
8
7
|
// > ctx.scope='local' → cwd/.<runtime>
|
|
9
|
-
//
|
|
10
|
-
// Gemini also exposes resolveExtensionRoot that appends extensions/<NAME>.
|
|
11
8
|
|
|
12
9
|
const { test } = require('node:test');
|
|
13
10
|
const assert = require('node:assert/strict');
|
|
@@ -16,7 +13,6 @@ const path = require('node:path');
|
|
|
16
13
|
const claude = require('../../../adapters/claude.js');
|
|
17
14
|
const opencode = require('../../../adapters/opencode.js');
|
|
18
15
|
const codex = require('../../../adapters/codex.js');
|
|
19
|
-
const gemini = require('../../../adapters/gemini.js');
|
|
20
16
|
|
|
21
17
|
// Stable fake values so assertions don't depend on real $HOME / $CWD.
|
|
22
18
|
const FAKE_HOME = '/fake-home';
|
|
@@ -43,7 +39,6 @@ const CASES = [
|
|
|
43
39
|
{ name: 'claude', resolve: claude.resolveTarget, localLeaf: '.claude', globalHome: '.claude', envKey: 'CLAUDE_CONFIG_DIR' },
|
|
44
40
|
{ name: 'opencode', resolve: opencode.resolveTarget, localLeaf: '.opencode', globalHome: path.join('.config', 'opencode'), envKey: 'OPENCODE_CONFIG_DIR' },
|
|
45
41
|
{ name: 'codex', resolve: codex.resolveTarget, localLeaf: '.codex', globalHome: '.codex', envKey: 'CODEX_HOME' },
|
|
46
|
-
{ name: 'gemini', resolve: gemini.resolveTarget, localLeaf: '.gemini', globalHome: '.gemini', envKey: 'GEMINI_CONFIG_DIR' },
|
|
47
42
|
];
|
|
48
43
|
|
|
49
44
|
for (const tc of CASES) {
|
|
@@ -78,8 +73,3 @@ for (const tc of CASES) {
|
|
|
78
73
|
assert.equal(out, path.join(FAKE_CWD, tc.localLeaf));
|
|
79
74
|
});
|
|
80
75
|
}
|
|
81
|
-
|
|
82
|
-
test('gemini.resolveExtensionRoot layers extensions/<NAME> on top of resolveTarget', () => {
|
|
83
|
-
const out = gemini.resolveExtensionRoot({ configDir: '/explicit' });
|
|
84
|
-
assert.equal(out, path.join('/explicit', 'extensions', 'ultra-builder-pro'));
|
|
85
|
-
});
|
|
@@ -9,6 +9,7 @@ const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
|
|
|
9
9
|
const {
|
|
10
10
|
CORE_PUBLIC_SKILLS,
|
|
11
11
|
INTERNAL_AGENT_SKILLS,
|
|
12
|
+
SUPPORTED_RUNTIMES,
|
|
12
13
|
COLLAB_SKILLS_BY_RUNTIME,
|
|
13
14
|
RETIRED_SKILLS,
|
|
14
15
|
WORKFLOW_HOOK_FILES,
|
|
@@ -26,6 +27,8 @@ const CORE = [
|
|
|
26
27
|
'ultra-deliver',
|
|
27
28
|
'ultra-status',
|
|
28
29
|
'ultra-think',
|
|
30
|
+
'ultra-change',
|
|
31
|
+
'ultra-doctor',
|
|
29
32
|
];
|
|
30
33
|
|
|
31
34
|
const INTERNAL = [
|
|
@@ -51,19 +54,17 @@ test('runtime asset manifest exposes only Ultra-owned core and internal skills',
|
|
|
51
54
|
assert.deepEqual(RETIRED_SKILLS, RETIRED);
|
|
52
55
|
|
|
53
56
|
assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.claude, [
|
|
54
|
-
'codex-collab', '
|
|
57
|
+
'codex-collab', 'ultra-verify',
|
|
55
58
|
]);
|
|
56
59
|
assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.codex, [
|
|
57
|
-
'cc-collab', '
|
|
60
|
+
'cc-collab', 'ultra-verify',
|
|
58
61
|
]);
|
|
59
62
|
assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.opencode, [
|
|
60
|
-
'cc-collab', 'codex-collab', 'gemini-collab', 'ultra-verify',
|
|
61
|
-
]);
|
|
62
|
-
assert.deepEqual(COLLAB_SKILLS_BY_RUNTIME.gemini, [
|
|
63
63
|
'cc-collab', 'codex-collab', 'ultra-verify',
|
|
64
64
|
]);
|
|
65
|
+
assert.deepEqual(SUPPORTED_RUNTIMES, ['claude', 'opencode', 'codex']);
|
|
65
66
|
|
|
66
|
-
for (const runtime of
|
|
67
|
+
for (const runtime of SUPPORTED_RUNTIMES) {
|
|
67
68
|
const names = skillsForRuntime(runtime);
|
|
68
69
|
for (const retired of RETIRED) assert.ok(!names.includes(retired));
|
|
69
70
|
assert.ok(!names.some((name) => /impeccable/i.test(name)));
|
package/adapters/opencode.js
CHANGED
|
@@ -94,7 +94,7 @@ function openCodeTextTransform(input, assetName = '') {
|
|
|
94
94
|
text = text.replaceAll('Claude runtime', 'OpenCode runtime');
|
|
95
95
|
text = text.replaceAll('Claude-only', 'OpenCode-only');
|
|
96
96
|
|
|
97
|
-
if (assetName === 'codex-collab'
|
|
97
|
+
if (assetName === 'codex-collab') {
|
|
98
98
|
text = text.replaceAll('within Claude Code', 'from OpenCode');
|
|
99
99
|
text = text.replaceAll('Claude Code remains primary', 'OpenCode remains primary');
|
|
100
100
|
text = text.replaceAll('Claude orchestrates', 'OpenCode remains primary and orchestrates');
|
|
@@ -104,13 +104,8 @@ function openCodeTextTransform(input, assetName = '') {
|
|
|
104
104
|
}
|
|
105
105
|
if (assetName === 'ultra-verify') {
|
|
106
106
|
text = text.replaceAll('claude-analysis.md', 'opencode-analysis.md');
|
|
107
|
-
text = text.replaceAll('Claude + Gemini + Codex', 'OpenCode + Gemini + Codex');
|
|
108
107
|
text = text.replaceAll('Claude Code', 'OpenCode');
|
|
109
108
|
text = text.replaceAll('Claude', 'OpenCode');
|
|
110
|
-
text = text.replace(
|
|
111
|
-
/Orchestrate OpenCode \+ Gemini \+ Codex for independent three-way analysis\./,
|
|
112
|
-
'OpenCode remains primary while Gemini and Codex provide independent read-only analyses.',
|
|
113
|
-
);
|
|
114
109
|
}
|
|
115
110
|
if (assetName === 'learn') {
|
|
116
111
|
text = text.replaceAll(
|
|
@@ -342,20 +337,55 @@ import path from "node:path";
|
|
|
342
337
|
|
|
343
338
|
const TERMINAL = new Set(["committed", "completed", "done", "cancelled"]);
|
|
344
339
|
const TASKS_PROJECTION = ".ultra/tasks/tasks.json";
|
|
340
|
+
const PROVIDER_FIELDS = new Set(["provider", "project", "reference", "revision", "indexed_head", "status"]);
|
|
341
|
+
|
|
342
|
+
function providerMetadata(value) {
|
|
343
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
344
|
+
const clean = {};
|
|
345
|
+
for (const [kind, ref] of Object.entries(value)) {
|
|
346
|
+
if (!["memory", "code_graph"].includes(kind) || !ref || typeof ref !== "object" || Array.isArray(ref)) continue;
|
|
347
|
+
clean[kind] = Object.fromEntries(Object.entries(ref).filter(([key]) => PROVIDER_FIELDS.has(key)));
|
|
348
|
+
}
|
|
349
|
+
return clean;
|
|
350
|
+
}
|
|
345
351
|
|
|
346
|
-
function
|
|
352
|
+
function findUltraContext(directory) {
|
|
347
353
|
let current = path.resolve(directory);
|
|
348
354
|
while (true) {
|
|
349
|
-
const
|
|
350
|
-
|
|
355
|
+
const ultra = path.join(current, ".ultra");
|
|
356
|
+
const workflowFile = path.join(ultra, "workflow-state.json");
|
|
357
|
+
const statePresent = fs.existsSync(path.join(ultra, "state.db"));
|
|
358
|
+
let workflow = null;
|
|
359
|
+
if (fs.existsSync(workflowFile)) {
|
|
351
360
|
try {
|
|
352
|
-
const state = JSON.parse(fs.readFileSync(
|
|
353
|
-
|
|
361
|
+
const state = JSON.parse(fs.readFileSync(workflowFile, "utf8"));
|
|
362
|
+
workflow = state && !TERMINAL.has(state.status) ? state : null;
|
|
354
363
|
} catch (error) {
|
|
355
|
-
process.stderr.write(\`[ultra-builder-pro] cannot read \${
|
|
356
|
-
return null;
|
|
364
|
+
process.stderr.write(\`[ultra-builder-pro] cannot read \${workflowFile}: \${error.message}\\n\`);
|
|
357
365
|
}
|
|
358
366
|
}
|
|
367
|
+
const changes = [];
|
|
368
|
+
const changeRoot = path.join(ultra, "changes", "active");
|
|
369
|
+
if (fs.existsSync(changeRoot)) {
|
|
370
|
+
for (const entry of fs.readdirSync(changeRoot, { withFileTypes: true })) {
|
|
371
|
+
if (!entry.isDirectory()) continue;
|
|
372
|
+
const manifestPath = path.join(changeRoot, entry.name, "context-manifest.json");
|
|
373
|
+
if (!fs.existsSync(manifestPath)) continue;
|
|
374
|
+
try {
|
|
375
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
376
|
+
if (manifest?.change) changes.push({
|
|
377
|
+
...manifest.change,
|
|
378
|
+
providers: providerMetadata(manifest.providers),
|
|
379
|
+
manifestPath: path.relative(current, manifestPath),
|
|
380
|
+
});
|
|
381
|
+
} catch (error) {
|
|
382
|
+
process.stderr.write(\`[ultra-builder-pro] cannot read \${manifestPath}: \${error.message}\\n\`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (statePresent || workflow || changes.length > 0) {
|
|
387
|
+
return { root: current, workflow, changes, statePresent };
|
|
388
|
+
}
|
|
359
389
|
const parent = path.dirname(current);
|
|
360
390
|
if (parent === current) return null;
|
|
361
391
|
current = parent;
|
|
@@ -363,16 +393,42 @@ function findActiveWorkflow(directory) {
|
|
|
363
393
|
}
|
|
364
394
|
|
|
365
395
|
function contextText(active) {
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
396
|
+
const lines = [];
|
|
397
|
+
if (active.workflow) {
|
|
398
|
+
const state = active.workflow;
|
|
399
|
+
lines.push(
|
|
400
|
+
"[Ultra active workflow]",
|
|
401
|
+
\`Project: \${active.root}\`,
|
|
402
|
+
\`Command: \${state.command ?? "unknown"}\`,
|
|
403
|
+
\`Task: \${state.task_id ?? state.task ?? "unknown"}\`,
|
|
404
|
+
\`Step: \${state.step ?? "unknown"}\`,
|
|
405
|
+
\`Status: \${state.status ?? "active"}\`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if (active.changes.length > 0) {
|
|
409
|
+
const change = active.changes[0];
|
|
410
|
+
lines.push(
|
|
411
|
+
"[Ultra continuous change]",
|
|
412
|
+
\`Project: \${active.root}\`,
|
|
413
|
+
\`Change: \${change.id} (\${change.kind ?? "unknown"}, \${change.status ?? "active"})\`,
|
|
414
|
+
\`Title: \${change.title ?? "unknown"}\`,
|
|
415
|
+
\`Intent: \${change.intent ?? "unknown"}\`,
|
|
416
|
+
\`Context manifest: \${change.manifestPath}\`,
|
|
417
|
+
\`External providers (metadata references only): \${JSON.stringify(change.providers)}\`,
|
|
418
|
+
);
|
|
419
|
+
if (active.changes.length > 1) lines.push(
|
|
420
|
+
\`Active change count: \${active.changes.length}; resolve scope with change.list before mutation.\`,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
if (!active.workflow && active.changes.length === 0) {
|
|
424
|
+
lines.push(
|
|
425
|
+
"[Ultra baseline]",
|
|
426
|
+
\`Project: \${active.root}\`,
|
|
427
|
+
"No active continuous change. Start daily work with the ultra-change workflow.",
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
lines.push("Authority: .ultra/state.db; generated JSON/Markdown are projections or artifacts.");
|
|
431
|
+
return lines.join("\\n");
|
|
376
432
|
}
|
|
377
433
|
|
|
378
434
|
function targetPaths(tool, args) {
|
|
@@ -396,8 +452,8 @@ function isTasksProjection(active, candidate) {
|
|
|
396
452
|
|
|
397
453
|
export const UltraBuilderProPlugin = async ({ directory, worktree }) => {
|
|
398
454
|
const root = worktree || directory;
|
|
399
|
-
let active =
|
|
400
|
-
const refresh = () => { active =
|
|
455
|
+
let active = findUltraContext(root);
|
|
456
|
+
const refresh = () => { active = findUltraContext(root); };
|
|
401
457
|
return {
|
|
402
458
|
event: async ({ event }) => {
|
|
403
459
|
if (["session.created", "session.compacted", "session.idle"].includes(event?.type)) refresh();
|
|
@@ -418,7 +474,7 @@ export const UltraBuilderProPlugin = async ({ directory, worktree }) => {
|
|
|
418
474
|
if (targetPaths(tool, output?.args).some((candidate) => isTasksProjection(active, candidate))) {
|
|
419
475
|
throw new Error(
|
|
420
476
|
"Ultra Builder Pro refused a direct write to .ultra/tasks/tasks.json. " +
|
|
421
|
-
".ultra/state.db is authoritative; use MCP task tools
|
|
477
|
+
".ultra/state.db is authoritative; use MCP task tools and run ultra-doctor when state or projection health is degraded."
|
|
422
478
|
);
|
|
423
479
|
}
|
|
424
480
|
},
|
|
@@ -52,7 +52,6 @@ test('Claude plugin collaboration and learn workflows are safe native plugin ass
|
|
|
52
52
|
const read = (name) => fs.readFileSync(path.join(target, 'skills', name, 'SKILL.md'), 'utf8');
|
|
53
53
|
const learn = read('learn');
|
|
54
54
|
const codexCollab = read('codex-collab');
|
|
55
|
-
const geminiCollab = read('gemini-collab');
|
|
56
55
|
const verify = read('ultra-verify');
|
|
57
56
|
const review = read('ultra-review');
|
|
58
57
|
const plan = read('ultra-plan');
|
|
@@ -61,8 +60,6 @@ test('Claude plugin collaboration and learn workflows are safe native plugin ass
|
|
|
61
60
|
assert.match(learn, /~\/.claude\/skills\/learned-<pattern-slug>-unverified\/SKILL\.md/);
|
|
62
61
|
assert.doesNotMatch(learn, /_unverified\.md/);
|
|
63
62
|
assert.match(codexCollab, /-s read-only/);
|
|
64
|
-
assert.match(geminiCollab, /--approval-mode plan/);
|
|
65
|
-
assert.match(verify, /--approval-mode plan/);
|
|
66
63
|
assert.match(verify, /-s read-only/);
|
|
67
64
|
assert.match(verify, /\$CLAUDE_PLUGIN_ROOT\/skills\/ultra-verify\/scripts\/verify_wait\.py/);
|
|
68
65
|
assert.match(review, /\$CLAUDE_PLUGIN_ROOT\/skills\/ultra-review\/scripts\/review_wait\.py/);
|
|
@@ -71,7 +68,7 @@ test('Claude plugin collaboration and learn workflows are safe native plugin ass
|
|
|
71
68
|
assert.match(status, /never fall back .*tasks\.json/i);
|
|
72
69
|
assert.doesNotMatch(plan, /ultra-tools task create/);
|
|
73
70
|
|
|
74
|
-
for (const [name, contents] of Object.entries({ codexCollab,
|
|
71
|
+
for (const [name, contents] of Object.entries({ codexCollab, verify })) {
|
|
75
72
|
assert.doesNotMatch(contents, /--yolo|--full-auto|\/codex:/, name);
|
|
76
73
|
}
|
|
77
74
|
} finally {
|
|
@@ -21,8 +21,10 @@ const {
|
|
|
21
21
|
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
22
22
|
const COMMANDS = [
|
|
23
23
|
'learn',
|
|
24
|
+
'ultra-change',
|
|
24
25
|
'ultra-deliver',
|
|
25
26
|
'ultra-dev',
|
|
27
|
+
'ultra-doctor',
|
|
26
28
|
'ultra-init',
|
|
27
29
|
'ultra-plan',
|
|
28
30
|
'ultra-research',
|
|
@@ -89,7 +91,7 @@ test('install builds one Codex-native plugin with complete skill and command cov
|
|
|
89
91
|
|
|
90
92
|
const expectedSkills = skillsForRuntime('codex').sort();
|
|
91
93
|
assert.deepEqual(skillNames(layout.pluginRoot), expectedSkills);
|
|
92
|
-
assert.equal(expectedSkills.length,
|
|
94
|
+
assert.equal(expectedSkills.length, 18);
|
|
93
95
|
assert.ok(!fs.existsSync(path.join(layout.pluginRoot, 'skills', 'codex-collab')));
|
|
94
96
|
assert.ok(!fs.existsSync(path.join(layout.pluginRoot, 'skills', 'learned')));
|
|
95
97
|
|
|
@@ -117,7 +119,7 @@ test('every generated skill is Codex-valid, UI-visible, and free of Claude host
|
|
|
117
119
|
assert.equal(fm.name, name);
|
|
118
120
|
assert.doesNotMatch(
|
|
119
121
|
text,
|
|
120
|
-
/~\/\.claude|CLAUDE\.md|AskUserQuestion|(^|[\s`(>])\/(?:ultra-[a-z-]+|recall|learn|
|
|
122
|
+
/~\/\.claude|CLAUDE\.md|AskUserQuestion|(^|[\s`(>])\/(?:ultra-[a-z-]+|recall|learn|codex-collab)(?=$|[\s`,.;):])/m,
|
|
121
123
|
);
|
|
122
124
|
assert.doesNotMatch(text, /TaskCreate|TaskUpdate|TaskList|TaskOutput|run_in_background|~\/\.codex\/skills|mcp__claude/);
|
|
123
125
|
|
|
@@ -135,13 +137,12 @@ test('every generated skill is Codex-valid, UI-visible, and free of Claude host
|
|
|
135
137
|
const verify = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'ultra-verify', 'SKILL.md'), 'utf8');
|
|
136
138
|
assert.match(verify, /Codex owns the task/);
|
|
137
139
|
assert.match(verify, /claude --safe-mode/);
|
|
138
|
-
assert.match(verify, /gemini --approval-mode plan/);
|
|
139
140
|
assert.match(verify, /codex-analysis\.md/);
|
|
140
141
|
assert.doesNotMatch(verify, /codex-output\.md|Claude synthesizes|Claude-only/);
|
|
141
142
|
|
|
142
143
|
const waiter = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'ultra-verify', 'scripts', 'verify_wait.py'), 'utf8');
|
|
143
|
-
assert.match(waiter, /
|
|
144
|
-
assert.
|
|
144
|
+
assert.match(waiter, /ADVISOR = "claude"/);
|
|
145
|
+
assert.match(waiter, /OUTPUT_FILE = "claude-output\.md"/);
|
|
145
146
|
|
|
146
147
|
const review = fs.readFileSync(path.join(layout.pluginRoot, 'skills', 'ultra-review', 'SKILL.md'), 'utf8');
|
|
147
148
|
assert.match(review, /native Codex custom agents/);
|
|
@@ -179,7 +180,7 @@ test('install converts all nine Claude agents into native Codex agent TOML', ()
|
|
|
179
180
|
assert.match(text, /^developer_instructions = """/m);
|
|
180
181
|
assert.doesNotMatch(
|
|
181
182
|
text,
|
|
182
|
-
/model = "opus"|^tools =|^memory =|maxTurns|CLAUDE\.md|AskUserQuestion|(^|[\s`(>])\/(?:ultra-[a-z-]+|recall|learn|
|
|
183
|
+
/model = "opus"|^tools =|^memory =|maxTurns|CLAUDE\.md|AskUserQuestion|(^|[\s`(>])\/(?:ultra-[a-z-]+|recall|learn|codex-collab)(?=$|[\s`,.;):])/m,
|
|
183
184
|
);
|
|
184
185
|
}
|
|
185
186
|
} finally {
|
|
@@ -230,8 +231,8 @@ test('plugin declares current Codex hooks and a project-local Ultra MCP server',
|
|
|
230
231
|
const liveSpec = yaml.load(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'mcp-tools.yaml'), 'utf8'));
|
|
231
232
|
const upstreamSpec = yaml.load(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'upstream-mcp-tools.yaml'), 'utf8'));
|
|
232
233
|
const capabilityMap = JSON.parse(fs.readFileSync(path.join(layout.pluginRoot, 'spec', 'codex-capability-map.json'), 'utf8'));
|
|
233
|
-
assert.equal(liveSpec.tools.length,
|
|
234
|
-
assert.equal(upstreamSpec.tools.length,
|
|
234
|
+
assert.equal(liveSpec.tools.length, 29);
|
|
235
|
+
assert.equal(upstreamSpec.tools.length, 29);
|
|
235
236
|
assert.deepEqual(upstreamSpec.tools.map((tool) => tool.name).sort(), liveSpec.tools.map((tool) => tool.name).sort());
|
|
236
237
|
assert.deepEqual(capabilityMap.live_mcp_tools.sort(), liveSpec.tools.map((tool) => tool.name).sort());
|
|
237
238
|
assert.equal(Object.keys(capabilityMap.codex_native_replacements).length, 9);
|
|
@@ -60,7 +60,6 @@ test('install performs content-level OpenCode adaptation for commands, skills, r
|
|
|
60
60
|
const learn = fs.readFileSync(path.join(target, 'skills', 'learn', 'SKILL.md'), 'utf8');
|
|
61
61
|
const review = fs.readFileSync(path.join(target, 'skills', 'ultra-review', 'SKILL.md'), 'utf8');
|
|
62
62
|
const codexCollab = fs.readFileSync(path.join(target, 'skills', 'codex-collab', 'SKILL.md'), 'utf8');
|
|
63
|
-
const geminiCollab = fs.readFileSync(path.join(target, 'skills', 'gemini-collab', 'SKILL.md'), 'utf8');
|
|
64
63
|
const verify = fs.readFileSync(path.join(target, 'skills', 'ultra-verify', 'SKILL.md'), 'utf8');
|
|
65
64
|
|
|
66
65
|
assert.deepEqual(Object.keys(parseFm(plan).fm), ['name', 'description']);
|
|
@@ -68,9 +67,9 @@ test('install performs content-level OpenCode adaptation for commands, skills, r
|
|
|
68
67
|
assert.doesNotMatch(learn, /_unverified\.md/);
|
|
69
68
|
assert.match(review, /~\/.config\/opencode\/skills\/ultra-review\/scripts\/review_wait\.py/);
|
|
70
69
|
assert.match(codexCollab, /OpenCode remains primary/);
|
|
71
|
-
assert.match(geminiCollab, /OpenCode remains primary/);
|
|
72
70
|
assert.match(verify, /OpenCode remains primary/);
|
|
73
71
|
assert.match(verify, /opencode-analysis\.md/);
|
|
72
|
+
assert.match(verify, /codex exec -s read-only/);
|
|
74
73
|
assert.match(plan, /LEGACY_STATE_MIGRATION_REQUIRED/);
|
|
75
74
|
assert.match(plan, /never read or write .*tasks\.json/i);
|
|
76
75
|
assert.doesNotMatch(plan, /ultra-tools task create/);
|
|
@@ -172,13 +171,14 @@ test('install writes a schema-safe opencode.json and keeps ownership outside hos
|
|
|
172
171
|
assert.match(plugin, /\.ultra[\\/]tasks[\\/]tasks\.json/);
|
|
173
172
|
assert.match(plugin, /throw new Error/);
|
|
174
173
|
assert.match(plugin, /session\.compacted/);
|
|
175
|
-
assert.doesNotMatch(plugin, /memory|recall|journal|observation/);
|
|
174
|
+
assert.doesNotMatch(plugin, /memory\.(?:retain|recall|reflect)|journal|observation|prompt[_ -]?capture/);
|
|
175
|
+
assert.match(plugin, /metadata references only/);
|
|
176
176
|
} finally {
|
|
177
177
|
fs.rmSync(target, { recursive: true, force: true });
|
|
178
178
|
}
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
test('OpenCode
|
|
181
|
+
test('OpenCode context covers active changes and projection writes are blocked for Ultra projects', async () => {
|
|
182
182
|
const target = mkTarget();
|
|
183
183
|
const project = mkTarget();
|
|
184
184
|
try {
|
|
@@ -187,15 +187,21 @@ test('OpenCode before-tool hook blocks tasks.json writes only during an active w
|
|
|
187
187
|
const module = await import(pathToFileURL(pluginFile).href);
|
|
188
188
|
const plugin = await module.UltraBuilderProPlugin({ directory: project });
|
|
189
189
|
|
|
190
|
-
await plugin['tool.execute.before'](
|
|
191
|
-
{ tool: 'edit' },
|
|
192
|
-
{ args: { filePath: '.ultra/tasks/tasks.json' } },
|
|
193
|
-
);
|
|
194
|
-
|
|
195
190
|
fs.mkdirSync(path.join(project, '.ultra'), { recursive: true });
|
|
196
|
-
fs.writeFileSync(path.join(project, '.ultra', '
|
|
197
|
-
|
|
191
|
+
fs.writeFileSync(path.join(project, '.ultra', 'state.db'), 'owned-by-ultra');
|
|
192
|
+
const changeRoot = path.join(project, '.ultra', 'changes', 'active', 'daily-fix');
|
|
193
|
+
fs.mkdirSync(changeRoot, { recursive: true });
|
|
194
|
+
fs.writeFileSync(path.join(changeRoot, 'context-manifest.json'), JSON.stringify({
|
|
195
|
+
change: { id: 'daily-fix', title: 'Daily fix', kind: 'quick', status: 'active', intent: 'Fix drift' },
|
|
196
|
+
providers: { memory: { provider: 'cloud-mem', status: 'available' } },
|
|
198
197
|
}));
|
|
198
|
+
|
|
199
|
+
const output = { system: [] };
|
|
200
|
+
await plugin['experimental.chat.system.transform']({}, output);
|
|
201
|
+
assert.match(output.system.join('\n'), /Ultra continuous change/);
|
|
202
|
+
assert.match(output.system.join('\n'), /daily-fix/);
|
|
203
|
+
assert.match(output.system.join('\n'), /metadata references only/);
|
|
204
|
+
|
|
199
205
|
await assert.rejects(
|
|
200
206
|
plugin['tool.execute.before'](
|
|
201
207
|
{ tool: 'apply_patch' },
|