ultra-builder-pro-cli 0.22.0 → 0.24.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 +4 -4
- package/CHANGELOG.md +83 -0
- package/README.md +68 -35
- package/adapters/_shared/codex-assets.cjs +121 -60
- package/adapters/_shared/interaction-contract.cjs +20 -7
- package/adapters/_shared/provenance.cjs +7 -2
- package/adapters/_shared/runtime-assets.cjs +4 -3
- package/adapters/_shared/runtime-native.cjs +132 -0
- package/adapters/claude.js +61 -31
- package/adapters/codex.js +178 -81
- package/adapters/grok.js +372 -0
- package/adapters/kimi.js +8 -4
- package/adapters/opencode.js +211 -33
- package/agents/code-reviewer.md +4 -2
- package/agents/debugger.md +4 -1
- package/agents/review-code.md +7 -3
- package/agents/review-comments.md +7 -4
- package/agents/review-coordinator.md +11 -7
- package/agents/review-design.md +8 -4
- package/agents/review-errors.md +8 -4
- package/agents/review-spec.md +8 -5
- package/agents/review-tests.md +7 -4
- package/agents/tdd-runner.md +5 -2
- package/bin/install.js +9 -4
- package/bin/orchestrator.js +4 -13
- package/docs/AGENT-CONTEXT.md +141 -231
- package/docs/ARCHITECTURE.md +203 -498
- package/docs/ARTIFACT-AUTHORITY.md +38 -35
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +12 -9
- package/docs/ROADMAP.md +85 -110
- package/docs/RUNTIME-COMPAT-MATRIX.md +139 -174
- package/docs/SKILL-AUTHORING.md +3 -3
- package/docs/STATE-DB-ACCESS-POLICY.md +25 -20
- package/docs/WORKFLOW-LIFECYCLE.md +174 -442
- package/hooks/README.md +11 -8
- package/hooks/active_task_context.py +16 -16
- package/hooks/adapters/grok.py +134 -0
- package/hooks/{context_spine.py → context_envelope.py} +26 -22
- package/hooks/health_check.py +19 -29
- package/hooks/pre_stop_check.py +22 -12
- package/hooks/subagent_tracker.py +1 -1
- package/hooks/workflow_checkpoint.py +14 -13
- package/hooks/workflow_context.py +11 -12
- package/hooks/workflow_resume.py +14 -13
- package/mcp-server/{breadcrumb.cjs → hook-context.cjs} +7 -9
- package/mcp-server/lib/artifact-registry.cjs +25 -1
- package/mcp-server/lib/baseline-workflow.cjs +36 -38
- package/mcp-server/lib/canonical-json.cjs +19 -0
- package/mcp-server/lib/change-workflow.cjs +637 -49
- package/mcp-server/lib/context-envelope.cjs +508 -0
- package/mcp-server/lib/decision-records.cjs +337 -0
- package/mcp-server/lib/doctor.cjs +91 -33
- package/mcp-server/lib/init-project.cjs +78 -39
- package/mcp-server/lib/managed-file-write.cjs +116 -0
- package/mcp-server/lib/plan-checkpoint.cjs +177 -0
- package/mcp-server/lib/project-context.cjs +178 -0
- package/mcp-server/lib/stage-checkpoints.cjs +309 -0
- package/mcp-server/lib/state-db.cjs +155 -24
- package/mcp-server/lib/task-ledger.cjs +522 -13
- package/mcp-server/lib/ultra-facade.cjs +834 -0
- package/mcp-server/lib/worker-packet.cjs +358 -0
- package/mcp-server/server.cjs +208 -1059
- package/orchestrator/daemon.cjs +27 -9
- package/orchestrator/parallel-orchestrator.cjs +111 -76
- package/orchestrator/session-runner.cjs +135 -34
- package/package.json +10 -3
- package/skills/ultra-change/SKILL.md +39 -98
- package/skills/ultra-deliver/SKILL.md +42 -65
- package/skills/ultra-dev/SKILL.md +52 -99
- package/skills/ultra-doctor/SKILL.md +35 -57
- package/skills/ultra-init/SKILL.md +48 -88
- package/skills/ultra-init/references/brownfield-adoption.md +11 -8
- package/skills/ultra-plan/SKILL.md +54 -132
- package/skills/ultra-research/SKILL.md +58 -112
- package/skills/ultra-review/SKILL.md +28 -46
- package/skills/ultra-review/references/unified-schema.md +2 -0
- package/skills/ultra-review/scripts/review_wait.py +8 -0
- package/skills/ultra-status/SKILL.md +22 -45
- package/skills/ultra-test/SKILL.md +27 -61
- package/skills/ultra-think/SKILL.md +29 -48
- package/skills/ultra-think/references/interaction-boundary.md +56 -0
- package/spec/cli-protocol.md +80 -187
- package/spec/mcp-tools.yaml +130 -2738
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/state-db.sql +107 -4
- package/spec/schemas/{task-ledger.v1.schema.json → task-ledger.v2.schema.json} +70 -6
- package/ultra-tools/commands/status.cjs +369 -243
- package/mcp-server/lib/context-spine.cjs +0 -1208
- package/mcp-server/lib/decision-dialogue.cjs +0 -1128
- package/mcp-server/lib/project-breadcrumb.cjs +0 -173
- package/mcp-server/lib/spec-learning.cjs +0 -284
- package/mcp-server/lib/workflow-state.cjs +0 -2965
- package/skills/ultra-think/references/decision-dialogue.md +0 -153
|
@@ -23,16 +23,21 @@ const SURFACES = Object.freeze({
|
|
|
23
23
|
fallback: 'direct_user_interaction',
|
|
24
24
|
availability: 'interactive_non_auto_mode',
|
|
25
25
|
}),
|
|
26
|
+
grok: Object.freeze({
|
|
27
|
+
primary: 'AskUserQuestion',
|
|
28
|
+
fallback: 'direct_user_interaction',
|
|
29
|
+
availability: 'interactive_session',
|
|
30
|
+
}),
|
|
26
31
|
});
|
|
27
32
|
|
|
28
33
|
const GENERIC_QUESTION_GUIDANCE =
|
|
29
|
-
/Use the host-native structured question surface declared by the installed interaction\s+contract(?: when it exists)?\./g;
|
|
34
|
+
/Use the host-native (?:structured )?question surface declared by the installed interaction\s+contract(?: when it exists)?(?:;?\s+prefer its structured form when the host exposes one)?\./g;
|
|
30
35
|
|
|
31
36
|
function interactionContract(runtime) {
|
|
32
37
|
const question = SURFACES[runtime];
|
|
33
38
|
if (!question) throw new Error(`unsupported interaction runtime: ${runtime}`);
|
|
34
39
|
return {
|
|
35
|
-
schema_version: '1.
|
|
40
|
+
schema_version: '1.3',
|
|
36
41
|
runtime,
|
|
37
42
|
public_capability_graph: PUBLIC_CAPABILITY_GRAPH,
|
|
38
43
|
authority: {
|
|
@@ -50,9 +55,9 @@ function interactionContract(runtime) {
|
|
|
50
55
|
'accepted_intent_normalization',
|
|
51
56
|
],
|
|
52
57
|
ultra_mcp: [
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'
|
|
58
|
+
'durable_checkpoints_and_evidence_refs',
|
|
59
|
+
'digests_freshness_leases_archives_and_recovery',
|
|
60
|
+
'mutable_semantic_diagnostics',
|
|
56
61
|
],
|
|
57
62
|
},
|
|
58
63
|
interaction: {
|
|
@@ -85,8 +90,15 @@ function interactionContract(runtime) {
|
|
|
85
90
|
routing: {
|
|
86
91
|
semantic_recommendation_owner: 'host_model',
|
|
87
92
|
durable_recommendation_authority: false,
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
current_context_tool: 'ultra.context',
|
|
94
|
+
checkpoint_diagnostic_fields: ['warnings', 'blockers'],
|
|
95
|
+
hard_blocker_boundary: [
|
|
96
|
+
'corruption',
|
|
97
|
+
'unsafe_path',
|
|
98
|
+
'concurrency_conflict',
|
|
99
|
+
'permission',
|
|
100
|
+
'irreversible_external_effect',
|
|
101
|
+
],
|
|
90
102
|
},
|
|
91
103
|
};
|
|
92
104
|
}
|
|
@@ -99,6 +111,7 @@ function interactionPrompt(runtime) {
|
|
|
99
111
|
codex: 'Codex',
|
|
100
112
|
opencode: 'OpenCode',
|
|
101
113
|
kimi: 'Kimi Code',
|
|
114
|
+
grok: 'Grok Build',
|
|
102
115
|
}[runtime];
|
|
103
116
|
const primary = runtime === 'opencode' ? `\`${surface.primary}\`` : surface.primary;
|
|
104
117
|
return `Use ${host} ${primary} when ${surface.availability.replaceAll('_', ' ')} permits it.`;
|
|
@@ -187,7 +187,8 @@ function assetRefsForTree(rootName, root, { exclude = [] } = {}) {
|
|
|
187
187
|
function writeProvenance({
|
|
188
188
|
file, adapter, packageInfo, repository, sourceCommit: commit = null,
|
|
189
189
|
sourceDirty: dirty = null, sourceWorktreeDigest: worktreeDigest = null,
|
|
190
|
-
roots: inputRoots,
|
|
190
|
+
roots: inputRoots, assetRoots: inputAssetRoots = inputRoots,
|
|
191
|
+
assets: inputAssets, contracts: inputContracts = {},
|
|
191
192
|
}) {
|
|
192
193
|
if (!file || !adapter || !packageInfo?.name || !packageInfo?.version || !repository) {
|
|
193
194
|
throw new Error('provenance file, adapter, package, version, and repository are required');
|
|
@@ -199,9 +200,13 @@ function writeProvenance({
|
|
|
199
200
|
throw new Error('provenance source worktree digest must be a SHA-256 digest or null');
|
|
200
201
|
}
|
|
201
202
|
const roots = normalizeRoots(inputRoots);
|
|
203
|
+
const assetRoots = normalizeRoots(inputAssetRoots);
|
|
204
|
+
for (const root of Object.keys(roots)) {
|
|
205
|
+
if (!assetRoots[root]) throw new Error(`missing provenance asset root: ${root}`);
|
|
206
|
+
}
|
|
202
207
|
const refs = normalizeAssetRefs(inputAssets, roots);
|
|
203
208
|
const assets = refs.map((ref) => {
|
|
204
|
-
const { target } = resolveRef(
|
|
209
|
+
const { target } = resolveRef(assetRoots, ref);
|
|
205
210
|
const stat = fs.statSync(target);
|
|
206
211
|
if (!stat.isFile()) throw new Error(`provenance asset is not a file: ${target}`);
|
|
207
212
|
return { ...ref, size_bytes: stat.size, sha256: hashFile(target) };
|
|
@@ -41,7 +41,7 @@ const PUBLIC_CAPABILITY_GRAPH = Object.freeze(Object.fromEntries(
|
|
|
41
41
|
Object.freeze({
|
|
42
42
|
mode: PUBLIC_CAPABILITY_MODES[name],
|
|
43
43
|
activation: 'explicit_only',
|
|
44
|
-
next_capability_source: '
|
|
44
|
+
next_capability_source: 'host_model_from_ultra_context',
|
|
45
45
|
recommendation_owner: 'host_model',
|
|
46
46
|
selection_owner: 'user',
|
|
47
47
|
automatic_invocation: false,
|
|
@@ -56,13 +56,14 @@ const INTERNAL_AGENT_SKILLS = Object.freeze([
|
|
|
56
56
|
'testing-rules',
|
|
57
57
|
]);
|
|
58
58
|
|
|
59
|
-
const SUPPORTED_RUNTIMES = Object.freeze(['claude', 'opencode', 'codex', 'kimi']);
|
|
59
|
+
const SUPPORTED_RUNTIMES = Object.freeze(['claude', 'opencode', 'codex', 'kimi', 'grok']);
|
|
60
60
|
|
|
61
61
|
const COLLAB_SKILLS_BY_RUNTIME = Object.freeze({
|
|
62
62
|
claude: Object.freeze(['codex-collab', 'ultra-verify']),
|
|
63
63
|
codex: Object.freeze(['cc-collab', 'ultra-verify']),
|
|
64
64
|
opencode: Object.freeze(['cc-collab', 'codex-collab', 'ultra-verify']),
|
|
65
65
|
kimi: Object.freeze(['cc-collab', 'codex-collab', 'ultra-verify']),
|
|
66
|
+
grok: Object.freeze(['cc-collab', 'codex-collab', 'ultra-verify']),
|
|
66
67
|
});
|
|
67
68
|
|
|
68
69
|
const MCP_DEPENDENT_SKILLS = Object.freeze([
|
|
@@ -80,7 +81,7 @@ const MCP_DEPENDENT_SKILLS = Object.freeze([
|
|
|
80
81
|
|
|
81
82
|
const WORKFLOW_HOOK_FILES = Object.freeze([
|
|
82
83
|
'active_task_context.py',
|
|
83
|
-
'
|
|
84
|
+
'context_envelope.py',
|
|
84
85
|
'health_check.py',
|
|
85
86
|
'pre_stop_check.py',
|
|
86
87
|
'runtime_paths.py',
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const MANIFEST_FILE = 'native-runtime.json';
|
|
8
|
+
|
|
9
|
+
class RuntimeNativeError extends Error {
|
|
10
|
+
constructor(code, message, details = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'RuntimeNativeError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.details = details;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sha256(file) {
|
|
19
|
+
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readManifest(runtimeRoot) {
|
|
23
|
+
const file = path.join(path.resolve(runtimeRoot), MANIFEST_FILE);
|
|
24
|
+
if (!fs.existsSync(file)) {
|
|
25
|
+
throw new RuntimeNativeError(
|
|
26
|
+
'RUNTIME_NATIVE_MISSING',
|
|
27
|
+
`Ultra native runtime manifest is missing: ${file}`,
|
|
28
|
+
{ path: file },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
let manifest;
|
|
32
|
+
try {
|
|
33
|
+
manifest = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
34
|
+
} catch (cause) {
|
|
35
|
+
throw new RuntimeNativeError(
|
|
36
|
+
'RUNTIME_NATIVE_MISSING',
|
|
37
|
+
`Ultra native runtime manifest is invalid: ${file}`,
|
|
38
|
+
{ path: file, cause: cause.message },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (manifest?.schema_version !== 1
|
|
42
|
+
|| typeof manifest.native_module !== 'string'
|
|
43
|
+
|| !/^[0-9a-f]{64}$/i.test(manifest.native_sha256 || '')) {
|
|
44
|
+
throw new RuntimeNativeError(
|
|
45
|
+
'RUNTIME_NATIVE_MISSING',
|
|
46
|
+
`Ultra native runtime manifest is incomplete: ${file}`,
|
|
47
|
+
{ path: file },
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return { file, manifest };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function inspectRuntimeNative(runtimeRoot, { load = true } = {}) {
|
|
54
|
+
const root = path.resolve(runtimeRoot);
|
|
55
|
+
const { file, manifest } = readManifest(root);
|
|
56
|
+
const expected = {
|
|
57
|
+
platform: process.platform,
|
|
58
|
+
arch: process.arch,
|
|
59
|
+
modules: process.versions.modules,
|
|
60
|
+
};
|
|
61
|
+
const actual = {
|
|
62
|
+
platform: manifest.platform,
|
|
63
|
+
arch: manifest.arch,
|
|
64
|
+
modules: String(manifest.modules || ''),
|
|
65
|
+
};
|
|
66
|
+
if (actual.platform !== expected.platform
|
|
67
|
+
|| actual.arch !== expected.arch
|
|
68
|
+
|| actual.modules !== expected.modules) {
|
|
69
|
+
throw new RuntimeNativeError(
|
|
70
|
+
'RUNTIME_ABI_MISMATCH',
|
|
71
|
+
`Ultra native runtime requires ${actual.platform}/${actual.arch} ABI ${
|
|
72
|
+
actual.modules
|
|
73
|
+
}, but the active Node is ${expected.platform}/${expected.arch} ABI ${expected.modules}`,
|
|
74
|
+
{ expected: actual, actual: expected, manifest: file },
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const nativeFile = path.resolve(root, manifest.native_module);
|
|
78
|
+
if (nativeFile !== root && !nativeFile.startsWith(`${root}${path.sep}`)) {
|
|
79
|
+
throw new RuntimeNativeError(
|
|
80
|
+
'RUNTIME_NATIVE_MISSING',
|
|
81
|
+
'Ultra native module path escapes the runtime root',
|
|
82
|
+
{ path: manifest.native_module },
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (!fs.existsSync(nativeFile) || !fs.statSync(nativeFile).isFile()) {
|
|
86
|
+
throw new RuntimeNativeError(
|
|
87
|
+
'RUNTIME_NATIVE_MISSING',
|
|
88
|
+
`Ultra native SQLite driver is missing: ${nativeFile}`,
|
|
89
|
+
{ path: nativeFile },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const digest = sha256(nativeFile);
|
|
93
|
+
if (digest !== manifest.native_sha256) {
|
|
94
|
+
throw new RuntimeNativeError(
|
|
95
|
+
'RUNTIME_NATIVE_MISSING',
|
|
96
|
+
`Ultra native SQLite driver digest does not match its manifest: ${nativeFile}`,
|
|
97
|
+
{ path: nativeFile, expected: manifest.native_sha256, actual: digest },
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (load) {
|
|
101
|
+
let Database;
|
|
102
|
+
try {
|
|
103
|
+
Database = require(require.resolve('better-sqlite3', {
|
|
104
|
+
paths: [path.join(root, 'node_modules')],
|
|
105
|
+
}));
|
|
106
|
+
const db = new Database(':memory:');
|
|
107
|
+
db.prepare('SELECT 1 AS ok').get();
|
|
108
|
+
db.close();
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
throw new RuntimeNativeError(
|
|
111
|
+
'RUNTIME_ABI_MISMATCH',
|
|
112
|
+
`Ultra native SQLite driver cannot load in the active Node runtime: ${cause.message}`,
|
|
113
|
+
{ path: nativeFile, cause: cause.code || cause.message },
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
status: 'healthy',
|
|
119
|
+
manifest: file,
|
|
120
|
+
platform: actual.platform,
|
|
121
|
+
arch: actual.arch,
|
|
122
|
+
modules: actual.modules,
|
|
123
|
+
native_module: nativeFile,
|
|
124
|
+
native_sha256: digest,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
MANIFEST_FILE,
|
|
130
|
+
RuntimeNativeError,
|
|
131
|
+
inspectRuntimeNative,
|
|
132
|
+
};
|
package/adapters/claude.js
CHANGED
|
@@ -14,7 +14,11 @@ const {
|
|
|
14
14
|
removeTree,
|
|
15
15
|
writeAtomic,
|
|
16
16
|
} = require('./_shared/file-ops.cjs');
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
applyNativeDoctor,
|
|
19
|
+
buildMcpRuntime,
|
|
20
|
+
mcpCommand,
|
|
21
|
+
} = require('./_shared/codex-assets.cjs');
|
|
18
22
|
const { parse: parseFrontmatter, serialize: serializeFrontmatter } = require('./_shared/frontmatter.cjs');
|
|
19
23
|
const { adaptInteractionGuidance } = require('./_shared/interaction-contract.cjs');
|
|
20
24
|
const provenance = require('./_shared/provenance.cjs');
|
|
@@ -66,7 +70,7 @@ function copySkills(repoRoot, target, names) {
|
|
|
66
70
|
let adaptedBody = adaptInteractionGuidance(body, 'claude');
|
|
67
71
|
if (name === 'ultra-review') {
|
|
68
72
|
adaptedBody = adaptedBody.replace(
|
|
69
|
-
/the current host's native bounded-worker\s+mechanism/g,
|
|
73
|
+
/(?:the current host's native|the host-native) bounded-worker\s+mechanism/g,
|
|
70
74
|
'Claude Code Task workers using the installed review agent definitions',
|
|
71
75
|
);
|
|
72
76
|
}
|
|
@@ -139,83 +143,109 @@ function buildHooksManifest() {
|
|
|
139
143
|
};
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
function
|
|
143
|
-
const target = resolvePluginRoot(ctx);
|
|
144
|
-
const repoRoot = resolveRepoRoot(ctx);
|
|
145
|
-
if (fs.existsSync(target)) {
|
|
146
|
-
const entries = fs.readdirSync(target);
|
|
147
|
-
if (entries.length > 0 && !isManaged(target)) {
|
|
148
|
-
throw new Error(`refusing to replace unmanaged Claude plugin: ${target}`);
|
|
149
|
-
}
|
|
150
|
-
if (entries.length > 0) removeTree(target);
|
|
151
|
-
}
|
|
152
|
-
ensureDir(target);
|
|
153
|
-
|
|
146
|
+
function buildStaging(repoRoot, staging, target) {
|
|
154
147
|
const report = { target, copied: {}, config: { updated: false } };
|
|
155
|
-
report.copied.commands = copyCommands(repoRoot,
|
|
156
|
-
report.copied.skills = copySkills(repoRoot,
|
|
157
|
-
report.copied.agents = copyTree(path.join(repoRoot, 'agents'), path.join(
|
|
148
|
+
report.copied.commands = copyCommands(repoRoot, staging);
|
|
149
|
+
report.copied.skills = copySkills(repoRoot, staging, skillsForRuntime('claude'));
|
|
150
|
+
report.copied.agents = copyTree(path.join(repoRoot, 'agents'), path.join(staging, 'agents'));
|
|
158
151
|
|
|
159
|
-
ensureDir(path.join(
|
|
152
|
+
ensureDir(path.join(staging, 'hooks'));
|
|
160
153
|
report.copied.hooks = [];
|
|
161
154
|
for (const name of WORKFLOW_HOOK_FILES) {
|
|
162
|
-
fs.copyFileSync(path.join(repoRoot, 'hooks', name), path.join(
|
|
155
|
+
fs.copyFileSync(path.join(repoRoot, 'hooks', name), path.join(staging, 'hooks', name));
|
|
163
156
|
report.copied.hooks.push(name);
|
|
164
157
|
}
|
|
165
|
-
writeAtomic(path.join(
|
|
158
|
+
writeAtomic(path.join(staging, 'hooks', 'hooks.json'), JSON.stringify(buildHooksManifest(), null, 2) + '\n');
|
|
166
159
|
|
|
167
|
-
|
|
168
|
-
|
|
160
|
+
buildMcpRuntime(repoRoot, staging, { runtime: 'claude' });
|
|
161
|
+
const command = mcpCommand(path.join(target, 'runtime', 'launch.cjs'));
|
|
162
|
+
writeAtomic(path.join(staging, '.mcp.json'), JSON.stringify({
|
|
169
163
|
mcpServers: {
|
|
170
164
|
[MCP_SERVER_NAME]: {
|
|
171
|
-
command:
|
|
172
|
-
args:
|
|
165
|
+
command: command.command,
|
|
166
|
+
args: command.args,
|
|
173
167
|
},
|
|
174
168
|
},
|
|
175
169
|
}, null, 2) + '\n');
|
|
176
170
|
|
|
177
171
|
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
|
|
178
|
-
writeAtomic(path.join(
|
|
172
|
+
writeAtomic(path.join(staging, '.claude-plugin', 'plugin.json'), JSON.stringify({
|
|
179
173
|
name: PLUGIN_NAME,
|
|
180
174
|
version: pkg.version,
|
|
181
|
-
description: 'Claude Code native Ultra Builder Pro workflows
|
|
175
|
+
description: 'Claude Code native Ultra Builder Pro workflows with a persistent safety kernel.',
|
|
182
176
|
author: { name: typeof pkg.author === 'string' ? pkg.author : 'Ultra Builder Pro contributors' },
|
|
183
177
|
repository: pkg.homepage,
|
|
184
178
|
license: pkg.license || 'MIT',
|
|
185
179
|
}, null, 2) + '\n');
|
|
186
|
-
markManaged(
|
|
180
|
+
markManaged(staging, { adapter: 'claude', plugin: PLUGIN_NAME });
|
|
187
181
|
const source = provenance.packageSource(repoRoot);
|
|
188
|
-
const provenanceFile = path.join(
|
|
182
|
+
const provenanceFile = path.join(staging, PROVENANCE_FILE);
|
|
189
183
|
report.provenance = provenance.writeProvenance({
|
|
190
184
|
file: provenanceFile,
|
|
191
185
|
adapter: 'claude',
|
|
192
186
|
...source,
|
|
193
187
|
roots: { plugin: target },
|
|
194
|
-
|
|
188
|
+
assetRoots: { plugin: staging },
|
|
189
|
+
assets: provenance.assetRefsForTree('plugin', staging, {
|
|
195
190
|
exclude: ['.ubp-managed', PROVENANCE_FILE],
|
|
196
191
|
}),
|
|
197
192
|
contracts: {
|
|
198
193
|
plugin_manifest: { root: 'plugin', path: '.claude-plugin/plugin.json' },
|
|
199
194
|
mcp_registration: { root: 'plugin', path: '.mcp.json' },
|
|
200
195
|
mcp_launcher: { root: 'plugin', path: 'runtime/launch.cjs' },
|
|
196
|
+
native_runtime: { root: 'plugin', path: 'runtime/native-runtime.json' },
|
|
197
|
+
context_envelope_helper: { root: 'plugin', path: 'runtime/hook-context.cjs' },
|
|
201
198
|
hook_event_helper: { root: 'plugin', path: 'runtime/hook-event.cjs' },
|
|
202
199
|
hooks_manifest: { root: 'plugin', path: 'hooks/hooks.json' },
|
|
203
200
|
checkpoint_hook: { root: 'plugin', path: 'hooks/workflow_checkpoint.py' },
|
|
204
201
|
resume_hook: { root: 'plugin', path: 'hooks/workflow_resume.py' },
|
|
205
202
|
},
|
|
206
203
|
});
|
|
207
|
-
report.provenance.file =
|
|
204
|
+
report.provenance.file = path.join(target, PROVENANCE_FILE);
|
|
208
205
|
return report;
|
|
209
206
|
}
|
|
210
207
|
|
|
208
|
+
function install(ctx = {}) {
|
|
209
|
+
const target = resolvePluginRoot(ctx);
|
|
210
|
+
const repoRoot = resolveRepoRoot(ctx);
|
|
211
|
+
if (fs.existsSync(target)) {
|
|
212
|
+
const entries = fs.readdirSync(target);
|
|
213
|
+
if (entries.length > 0 && !isManaged(target)) {
|
|
214
|
+
throw new Error(`refusing to replace unmanaged Claude plugin: ${target}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
ensureDir(path.dirname(target));
|
|
218
|
+
const staging = fs.mkdtempSync(path.join(path.dirname(target), `.${PLUGIN_NAME}-staging-`));
|
|
219
|
+
const backup = `${staging}-previous`;
|
|
220
|
+
let movedPrevious = false;
|
|
221
|
+
let published = false;
|
|
222
|
+
try {
|
|
223
|
+
const report = buildStaging(repoRoot, staging, target);
|
|
224
|
+
if (fs.existsSync(target)) {
|
|
225
|
+
fs.renameSync(target, backup);
|
|
226
|
+
movedPrevious = true;
|
|
227
|
+
}
|
|
228
|
+
fs.renameSync(staging, target);
|
|
229
|
+
published = true;
|
|
230
|
+
if (movedPrevious) removeTree(backup);
|
|
231
|
+
return report;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (published && fs.existsSync(target)) removeTree(target);
|
|
234
|
+
else if (fs.existsSync(staging)) removeTree(staging);
|
|
235
|
+
if (movedPrevious && fs.existsSync(backup)) fs.renameSync(backup, target);
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
211
240
|
function doctor(ctx = {}) {
|
|
212
241
|
const repoRoot = resolveRepoRoot(ctx);
|
|
213
242
|
const source = provenance.packageSource(repoRoot);
|
|
214
|
-
|
|
243
|
+
const report = provenance.inspectProvenance({
|
|
215
244
|
file: path.join(resolvePluginRoot(ctx), PROVENANCE_FILE),
|
|
216
245
|
expectedAdapter: 'claude',
|
|
217
246
|
expectedPackageVersion: source.packageInfo.version,
|
|
218
247
|
});
|
|
248
|
+
return applyNativeDoctor(report, path.join(resolvePluginRoot(ctx), 'runtime'));
|
|
219
249
|
}
|
|
220
250
|
|
|
221
251
|
function uninstall(ctx = {}) {
|