ultra-builder-pro-cli 0.18.0 → 0.19.1
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/CHANGELOG.md +31 -1
- package/README.md +6 -20
- package/adapters/claude.js +3 -1
- package/adapters/kimi.js +4 -30
- package/adapters/opencode.js +50 -14
- package/docs/AGENT-CONTEXT.md +11 -25
- package/docs/ARCHITECTURE.md +10 -1
- package/docs/DECISIONS.md +1 -1
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +54 -0
- package/docs/ROADMAP.md +1 -2
- package/docs/RUNTIME-COMPAT-MATRIX.md +16 -21
- package/hooks/active_task_context.py +1 -1
- package/hooks/context_spine.py +1 -1
- package/hooks/health_check.py +19 -0
- package/hooks/pre_stop_check.py +1 -1
- package/hooks/workflow_checkpoint.py +1 -1
- package/hooks/workflow_context.py +1 -1
- package/hooks/workflow_resume.py +1 -1
- package/mcp-server/server.cjs +19 -7
- package/package.json +3 -4
- package/skills/ultra-change/SKILL.md +4 -3
- package/skills/ultra-init/SKILL.md +2 -1
- package/adapters/_shared/handbook.cjs +0 -569
- package/bin/handbook.js +0 -107
- package/docs/USER-HANDBOOK-CONTRACT.md +0 -120
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.19.1] — 2026-07-26
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Made the four-host handbook-isolation release test hermetic so a clean CI
|
|
15
|
+
runner does not require the Codex CLI merely to prove that installation and
|
|
16
|
+
uninstallation preserve user-owned instruction files byte for byte.
|
|
17
|
+
|
|
18
|
+
## [0.19.0] — 2026-07-26
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Made every public Ultra workflow explicitly activated. Claude Code and Kimi
|
|
23
|
+
Code disable model invocation, Codex keeps implicit invocation disabled, and
|
|
24
|
+
OpenCode commands load private plugin workflow assets instead of exposing
|
|
25
|
+
public workflows through its model skill catalog.
|
|
26
|
+
- Limited lifecycle context, health, checkpoint, stop, and subagent hooks to
|
|
27
|
+
DB-authoritative active workflows. Initialized idle projects retain only
|
|
28
|
+
generated-projection protection.
|
|
29
|
+
- Changed workflow handoff to return allowed transitions and a host-owned
|
|
30
|
+
recommendation without launching another public workflow.
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
|
|
34
|
+
- Removed `ubp-handbook`, the shared handbook renderer, Kimi session bootstrap,
|
|
35
|
+
and every package path capable of creating or rewriting user-level
|
|
36
|
+
`CLAUDE.md` or `AGENTS.md` files.
|
|
37
|
+
|
|
10
38
|
## [0.18.0] — 2026-07-25
|
|
11
39
|
|
|
12
40
|
### Added
|
|
@@ -789,7 +817,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
789
817
|
skill manifest, CLI protocol + mapping table; 5 spec validators.
|
|
790
818
|
- **Phase 0 — skeleton**: multi-runtime installer scaffolding.
|
|
791
819
|
|
|
792
|
-
[Unreleased]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.
|
|
820
|
+
[Unreleased]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.19.1...HEAD
|
|
821
|
+
[0.19.1]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.19.0...v0.19.1
|
|
822
|
+
[0.19.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.18.0...v0.19.0
|
|
793
823
|
[0.18.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.17.1...v0.18.0
|
|
794
824
|
[0.17.1]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.17.0...v0.17.1
|
|
795
825
|
[0.17.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.16.0...v0.17.0
|
package/README.md
CHANGED
|
@@ -155,24 +155,11 @@ npx --yes ultra-builder-pro-cli@latest --all --global --doctor --json
|
|
|
155
155
|
After installing or upgrading, start a new Claude Code, OpenCode, or Codex
|
|
156
156
|
session. In Kimi Code, run `/reload` or start a new session.
|
|
157
157
|
|
|
158
|
-
Plugin installation
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
npx --yes --package ultra-builder-pro-cli@latest \
|
|
164
|
-
ubp-handbook preview --runtime codex --full > /tmp/ubp-codex-handbook.md
|
|
165
|
-
# Inspect the preview, then copy the confirmation token printed to the terminal.
|
|
166
|
-
npx --yes --package ultra-builder-pro-cli@latest \
|
|
167
|
-
ubp-handbook apply --runtime codex --full \
|
|
168
|
-
--confirm "PASTE_64_CHARACTER_TOKEN_HERE"
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Replace `codex` with `claude`, `opencode`, or `kimi` for the other hosts. The
|
|
172
|
-
apply command rejects a missing or stale preview token, creates a timestamped
|
|
173
|
-
backup, follows user-managed handbook symlinks, and preserves supported
|
|
174
|
-
marker-delimited content owned by other providers. Without `--full`, only the
|
|
175
|
-
small Ultra runtime block is merged.
|
|
158
|
+
Plugin installation, update, doctor, and uninstall never mutate durable user
|
|
159
|
+
instructions such as `CLAUDE.md` or `AGENTS.md`. Ultra policy stays in the plugin
|
|
160
|
+
and becomes active only after an explicit workflow invocation. Existing legacy
|
|
161
|
+
Ultra marker blocks in a user handbook are left untouched for the owner to review;
|
|
162
|
+
the plugin neither claims nor silently deletes user-authored content.
|
|
176
163
|
|
|
177
164
|
### Host invocation
|
|
178
165
|
|
|
@@ -366,7 +353,6 @@ worker execution or verified auto-merge.
|
|
|
366
353
|
| `ultra-builder-pro-cli` / `ubp` | Install, update, uninstall, and diagnose host plugins |
|
|
367
354
|
| `ultra-tools` | Inspect and maintain project tasks, sessions, state, migration, and recovery |
|
|
368
355
|
| `ubp-orchestrator` | Execute current dependency waves or supervise configured workers |
|
|
369
|
-
| `ubp-handbook` | Preview or apply either the bounded Ultra block or the explicit full host-native handbook |
|
|
370
356
|
|
|
371
357
|
Useful read-only checks:
|
|
372
358
|
|
|
@@ -426,7 +412,7 @@ and `test:rest`.
|
|
|
426
412
|
| [Runtime Compatibility Matrix](./docs/RUNTIME-COMPAT-MATRIX.md) | Claude Code, OpenCode, Codex, and Kimi presentation details |
|
|
427
413
|
| [Legacy CLI Crosswalk](./docs/LEGACY-CLI-CROSSWALK.md) | What was preserved, strengthened, or replaced from the original Ultra Builder Pro |
|
|
428
414
|
| [Agent Context](./docs/AGENT-CONTEXT.md) | Context Manifest and host-agent execution contract |
|
|
429
|
-
| [
|
|
415
|
+
| [Plugin Isolation Contract](./docs/PLUGIN-ISOLATION-CONTRACT.md) | Plugin ownership, explicit activation, idle behavior, and user-instruction isolation |
|
|
430
416
|
| [State DB Access Policy](./docs/STATE-DB-ACCESS-POLICY.md) | Multi-process authority and write rules |
|
|
431
417
|
| [Roadmap](./docs/ROADMAP.md) | Current and historical delivery scope |
|
|
432
418
|
| [Changelog](./CHANGELOG.md) | Release history |
|
package/adapters/claude.js
CHANGED
|
@@ -68,10 +68,12 @@ function copySkills(repoRoot, target, names) {
|
|
|
68
68
|
'Claude Code Task workers using the installed review agent definitions',
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
|
+
const policy = skillPolicy(name);
|
|
71
72
|
return Buffer.from(serializeFrontmatter({
|
|
72
73
|
name,
|
|
73
74
|
description: fm.description,
|
|
74
|
-
'user-invocable':
|
|
75
|
+
'user-invocable': policy.userInvocable,
|
|
76
|
+
...(policy.userInvocable ? { 'disable-model-invocation': true } : {}),
|
|
75
77
|
}, adaptedBody));
|
|
76
78
|
},
|
|
77
79
|
});
|
package/adapters/kimi.js
CHANGED
|
@@ -20,6 +20,7 @@ const provenance = require('./_shared/provenance.cjs');
|
|
|
20
20
|
const {
|
|
21
21
|
CORE_PUBLIC_SKILLS,
|
|
22
22
|
WORKFLOW_HOOK_FILES,
|
|
23
|
+
skillPolicy,
|
|
23
24
|
skillsForRuntime,
|
|
24
25
|
} = require('./_shared/runtime-assets.cjs');
|
|
25
26
|
|
|
@@ -28,7 +29,6 @@ const MCP_SERVER_NAME = PLUGIN_ID;
|
|
|
28
29
|
const PROVENANCE_FILE = 'provenance.json';
|
|
29
30
|
const REGISTRY_RELATIVE = path.join('plugins', 'installed.json');
|
|
30
31
|
const MANAGED_RELATIVE = path.join('plugins', 'managed', PLUGIN_ID);
|
|
31
|
-
const BOOTSTRAP_SKILL = 'using-ultra-builder-pro';
|
|
32
32
|
|
|
33
33
|
function resolveTarget(ctx = {}) {
|
|
34
34
|
if (ctx.configDir) return path.resolve(ctx.configDir);
|
|
@@ -127,11 +127,13 @@ function transformSkill(buf, relPath, skillName) {
|
|
|
127
127
|
if (!relPath.endsWith('.md')) return buf;
|
|
128
128
|
const { fm, body } = parseFm(source);
|
|
129
129
|
if (!fm) return Buffer.from(kimiTextTransform(source, skillName), 'utf8');
|
|
130
|
+
const policy = skillPolicy(skillName);
|
|
130
131
|
const nativeFm = {
|
|
131
132
|
name: String(fm.name || skillName),
|
|
132
133
|
description: kimiTextTransform(fm.description || `${skillName} workflow`, skillName)
|
|
133
134
|
.replace(/\s+/g, ' ')
|
|
134
135
|
.trim(),
|
|
136
|
+
...(policy.userInvocable ? { disableModelInvocation: true } : {}),
|
|
135
137
|
};
|
|
136
138
|
return Buffer.from(serializeFm(nativeFm, kimiTextTransform(body, skillName)), 'utf8');
|
|
137
139
|
}
|
|
@@ -162,29 +164,6 @@ function copySkills(repoRoot, pluginRoot) {
|
|
|
162
164
|
});
|
|
163
165
|
installed.push(name);
|
|
164
166
|
}
|
|
165
|
-
const bootstrapRoot = path.join(pluginRoot, 'skills', BOOTSTRAP_SKILL);
|
|
166
|
-
ensureDir(bootstrapRoot);
|
|
167
|
-
writeAtomic(path.join(bootstrapRoot, 'SKILL.md'), `---
|
|
168
|
-
name: ${BOOTSTRAP_SKILL}
|
|
169
|
-
description: Bootstrap the Kimi-native Ultra Builder Pro runtime contract at session start.
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
# Ultra Builder Pro for Kimi Code
|
|
173
|
-
|
|
174
|
-
Kimi Code is the primary host and owns all edits, evidence, and final verification. Use the
|
|
175
|
-
namespaced \`/ultra-builder-pro:ultra-*\` commands or invoke the registered Ultra skills directly.
|
|
176
|
-
|
|
177
|
-
- \`.ultra/state.db\` is the only durable Ultra authority. JSON and Markdown under \`.ultra/\` are
|
|
178
|
-
projections or workflow artifacts and must never replace MCP state writes.
|
|
179
|
-
- Memory and code graph data belong to separately installed external providers. Ultra stores only
|
|
180
|
-
provider metadata references and never captures prompts, transcripts, or cross-session memory.
|
|
181
|
-
- Use Kimi \`TodoList\` for session coordination and Kimi \`Agent\` / \`AgentSwarm\` for bounded
|
|
182
|
-
workers. Bundled files under \`$KIMI_PLUGIN_ROOT/agents/\` are worker prompt templates.
|
|
183
|
-
- Before compaction, Ultra saves its workflow checkpoint. After compaction or recovery, inspect
|
|
184
|
-
\`.ultra/runtime/checkpoint.json\` and call the Ultra status/doctor MCP tools before continuing.
|
|
185
|
-
- If no Ultra workflow is active, the lifecycle hooks remain advisory except projection protection.
|
|
186
|
-
`);
|
|
187
|
-
installed.push(BOOTSTRAP_SKILL);
|
|
188
167
|
return installed;
|
|
189
168
|
}
|
|
190
169
|
|
|
@@ -280,7 +259,6 @@ function writeManifest(repoRoot, pluginRoot) {
|
|
|
280
259
|
homepage: pkg.homepage,
|
|
281
260
|
license: pkg.license || 'MIT',
|
|
282
261
|
skills: ['./skills'],
|
|
283
|
-
sessionStart: { skill: BOOTSTRAP_SKILL },
|
|
284
262
|
mcpServers: { [MCP_SERVER_NAME]: mcpManifest() },
|
|
285
263
|
hooks: buildHooksManifest(),
|
|
286
264
|
commands: ['./commands'],
|
|
@@ -351,9 +329,6 @@ function writePluginProvenance(repoRoot, target) {
|
|
|
351
329
|
mcp_launcher: { root: 'plugin', path: path.join('runtime', 'launch.cjs') },
|
|
352
330
|
hook_event_helper: { root: 'plugin', path: path.join('runtime', 'hook-event.cjs') },
|
|
353
331
|
hook_adapter: { root: 'plugin', path: path.join('hooks', 'adapters', 'kimi.py') },
|
|
354
|
-
session_bootstrap: {
|
|
355
|
-
root: 'plugin', path: path.join('skills', BOOTSTRAP_SKILL, 'SKILL.md'),
|
|
356
|
-
},
|
|
357
332
|
},
|
|
358
333
|
});
|
|
359
334
|
return { file, manifest };
|
|
@@ -444,7 +419,7 @@ function doctor(ctx = {}) {
|
|
|
444
419
|
&& manifest.skills.includes('./skills')
|
|
445
420
|
&& Array.isArray(manifest.commands)
|
|
446
421
|
&& manifest.commands.includes('./commands')
|
|
447
|
-
&& manifest.sessionStart
|
|
422
|
+
&& manifest.sessionStart === undefined;
|
|
448
423
|
const mcp = manifest.mcpServers?.[MCP_SERVER_NAME];
|
|
449
424
|
const expected = mcpManifest();
|
|
450
425
|
mcpOk = !!mcp
|
|
@@ -508,7 +483,6 @@ module.exports = {
|
|
|
508
483
|
name: 'kimi',
|
|
509
484
|
PLUGIN_ID,
|
|
510
485
|
MCP_SERVER_NAME,
|
|
511
|
-
BOOTSTRAP_SKILL,
|
|
512
486
|
buildHooksManifest,
|
|
513
487
|
kimiTextTransform,
|
|
514
488
|
resolveTarget,
|
package/adapters/opencode.js
CHANGED
|
@@ -38,6 +38,11 @@ const PLUGIN_MARKER = '// Managed by Ultra Builder Pro.';
|
|
|
38
38
|
const PROVENANCE_FILE = 'provenance.json';
|
|
39
39
|
const COMMAND_NAMES = CORE_PUBLIC_SKILLS;
|
|
40
40
|
|
|
41
|
+
function discoverableSkillNames() {
|
|
42
|
+
return skillsForRuntime('opencode')
|
|
43
|
+
.filter((name) => !CORE_PUBLIC_SKILLS.includes(name));
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
function resolveTarget(ctx = {}) {
|
|
42
47
|
if (ctx.configDir) return ctx.configDir;
|
|
43
48
|
if (ctx.scope === 'global') {
|
|
@@ -97,16 +102,26 @@ function openCodeTextTransform(input, assetName = '') {
|
|
|
97
102
|
return text;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
function openCodeCommandTransform(buf, relPath, commandName) {
|
|
105
|
+
function openCodeCommandTransform(buf, relPath, commandName, workflowFile) {
|
|
101
106
|
if (!relPath.endsWith('.md')) return buf;
|
|
102
|
-
const { fm
|
|
103
|
-
if (!fm)
|
|
107
|
+
const { fm } = parseFm(buf.toString('utf8'));
|
|
108
|
+
if (!fm) throw new Error(`missing frontmatter in OpenCode command: ${commandName}`);
|
|
104
109
|
const nativeFm = {
|
|
105
110
|
description: openCodeTextTransform(fm.description || `${commandName} workflow`, commandName)
|
|
106
111
|
.replace(/\s+/g, ' ')
|
|
107
112
|
.trim(),
|
|
108
113
|
};
|
|
109
|
-
|
|
114
|
+
const body = [
|
|
115
|
+
`# Run ${commandName}`,
|
|
116
|
+
'',
|
|
117
|
+
`Read and follow \`${workflowFile}\` as the only workflow definition.`,
|
|
118
|
+
'This explicit command is the activation boundary; do not substitute a model-discovered',
|
|
119
|
+
'public skill or start another public Ultra workflow automatically.',
|
|
120
|
+
'',
|
|
121
|
+
'Arguments: $ARGUMENTS',
|
|
122
|
+
'',
|
|
123
|
+
].join('\n');
|
|
124
|
+
return Buffer.from(serializeFm(nativeFm, body), 'utf8');
|
|
110
125
|
}
|
|
111
126
|
|
|
112
127
|
function openCodeSkillAssetTransform(buf, relPath, skillName) {
|
|
@@ -229,7 +244,10 @@ function copyCommands(repoRoot, target) {
|
|
|
229
244
|
const file = `${name}.md`;
|
|
230
245
|
const source = path.join(repoRoot, 'commands', file);
|
|
231
246
|
if (!fs.existsSync(source)) throw new Error(`missing allowlisted OpenCode command: ${file}`);
|
|
232
|
-
const
|
|
247
|
+
const workflowFile = path.join(target, BUNDLE_DIR, 'workflows', name, 'SKILL.md');
|
|
248
|
+
const transformed = withManagedMarker(
|
|
249
|
+
openCodeCommandTransform(fs.readFileSync(source), file, name, workflowFile),
|
|
250
|
+
);
|
|
233
251
|
writeAtomic(path.join(output, file), transformed);
|
|
234
252
|
copied.push(file);
|
|
235
253
|
}
|
|
@@ -238,7 +256,7 @@ function copyCommands(repoRoot, target) {
|
|
|
238
256
|
|
|
239
257
|
function copySkills(repoRoot, target) {
|
|
240
258
|
const copied = [];
|
|
241
|
-
const names =
|
|
259
|
+
const names = discoverableSkillNames();
|
|
242
260
|
const skillsRoot = path.join(target, 'skills');
|
|
243
261
|
ensureDir(skillsRoot);
|
|
244
262
|
for (const entry of fs.readdirSync(skillsRoot, { withFileTypes: true })) {
|
|
@@ -262,6 +280,22 @@ function copySkills(repoRoot, target) {
|
|
|
262
280
|
return copied;
|
|
263
281
|
}
|
|
264
282
|
|
|
283
|
+
function copyPrivateWorkflows(repoRoot, bundleRoot) {
|
|
284
|
+
const copied = [];
|
|
285
|
+
const workflowsRoot = path.join(bundleRoot, 'workflows');
|
|
286
|
+
for (const name of CORE_PUBLIC_SKILLS) {
|
|
287
|
+
const source = path.join(repoRoot, 'skills', name);
|
|
288
|
+
if (!fs.existsSync(path.join(source, 'SKILL.md'))) {
|
|
289
|
+
throw new Error(`missing allowlisted OpenCode workflow: ${name}`);
|
|
290
|
+
}
|
|
291
|
+
const files = copyTree(source, path.join(workflowsRoot, name), {
|
|
292
|
+
transform: (buf, relPath) => openCodeSkillAssetTransform(buf, relPath, name),
|
|
293
|
+
});
|
|
294
|
+
copied.push(...files.map((rel) => path.join('workflows', name, rel)));
|
|
295
|
+
}
|
|
296
|
+
return copied;
|
|
297
|
+
}
|
|
298
|
+
|
|
265
299
|
function copyAgents(repoRoot, target) {
|
|
266
300
|
const sourceRoot = path.join(repoRoot, 'agents');
|
|
267
301
|
const output = path.join(target, 'agents');
|
|
@@ -279,7 +313,7 @@ function preflightAssets(repoRoot, target) {
|
|
|
279
313
|
for (const name of COMMAND_NAMES) {
|
|
280
314
|
assertManagedTextTarget(path.join(target, 'commands', `${name}.md`), 'command');
|
|
281
315
|
}
|
|
282
|
-
for (const name of
|
|
316
|
+
for (const name of discoverableSkillNames()) {
|
|
283
317
|
const destination = path.join(target, 'skills', name);
|
|
284
318
|
if (fs.existsSync(destination) && !isManaged(destination)) {
|
|
285
319
|
throw new Error(`refusing to replace unmanaged OpenCode skill: ${destination}`);
|
|
@@ -316,8 +350,8 @@ const BREADCRUMB_CLI = path.resolve(
|
|
|
316
350
|
function findUltraRoot(directory) {
|
|
317
351
|
let current = path.resolve(directory);
|
|
318
352
|
while (true) {
|
|
319
|
-
const
|
|
320
|
-
if (fs.existsSync(
|
|
353
|
+
const stateDb = path.join(current, ".ultra", "state.db");
|
|
354
|
+
if (fs.existsSync(stateDb) && fs.statSync(stateDb).isFile()) return current;
|
|
321
355
|
const parent = path.dirname(current);
|
|
322
356
|
if (parent === current) return null;
|
|
323
357
|
current = parent;
|
|
@@ -335,12 +369,13 @@ function readUltraContext(directory) {
|
|
|
335
369
|
timeout: 5000,
|
|
336
370
|
});
|
|
337
371
|
const value = JSON.parse(raw);
|
|
338
|
-
|
|
339
|
-
|
|
372
|
+
// A canonical breadcrumb is injectable only while DB authority proves an active workflow.
|
|
373
|
+
if (!value?.breadcrumb || !value.breadcrumb.workflow
|
|
374
|
+
|| typeof value.text !== "string" || !value.text) {
|
|
375
|
+
return { root, breadcrumb: null, text: null };
|
|
340
376
|
}
|
|
341
377
|
return { root, breadcrumb: value.breadcrumb, text: value.text };
|
|
342
|
-
} catch
|
|
343
|
-
process.stderr.write(\`[ultra-builder-pro] cannot read canonical breadcrumb: \${error.message}\\n\`);
|
|
378
|
+
} catch {
|
|
344
379
|
return { root, breadcrumb: null, text: null };
|
|
345
380
|
}
|
|
346
381
|
}
|
|
@@ -415,6 +450,7 @@ function install(ctx = {}) {
|
|
|
415
450
|
const bundleRoot = path.join(target, BUNDLE_DIR);
|
|
416
451
|
if (fs.existsSync(bundleRoot)) removeTree(bundleRoot);
|
|
417
452
|
ensureDir(bundleRoot);
|
|
453
|
+
report.copied.workflows = copyPrivateWorkflows(repoRoot, bundleRoot);
|
|
418
454
|
const runtime = buildMcpRuntime(repoRoot, bundleRoot, { runtime: 'opencode' });
|
|
419
455
|
markManaged(bundleRoot, { adapter: 'opencode', asset: 'runtime-bundle' });
|
|
420
456
|
const configFile = path.join(target, 'opencode.json');
|
|
@@ -433,7 +469,7 @@ function install(ctx = {}) {
|
|
|
433
469
|
const bundleAssets = provenance.assetRefsForTree('config', bundleRoot, {
|
|
434
470
|
exclude: [PROVENANCE_FILE],
|
|
435
471
|
}).map((asset) => ({ ...asset, path: path.join(BUNDLE_DIR, asset.path) }));
|
|
436
|
-
const skillMarkers =
|
|
472
|
+
const skillMarkers = discoverableSkillNames().map((name) => ({
|
|
437
473
|
root: 'config', path: path.join('skills', name, '.ubp-managed'),
|
|
438
474
|
}));
|
|
439
475
|
const provenanceFile = path.join(bundleRoot, PROVENANCE_FILE);
|
package/docs/AGENT-CONTEXT.md
CHANGED
|
@@ -12,8 +12,8 @@ Ultra Builder Pro owns only:
|
|
|
12
12
|
`ultra-think`, plus the daily `ultra-change` and diagnostic `ultra-doctor`;
|
|
13
13
|
- four internal agent-only rule skills: `code-review-expert`, `security-rules`,
|
|
14
14
|
`integration-rules`, and `testing-rules`;
|
|
15
|
-
- the host-specific collaboration companions, `ultra-verify`, and
|
|
16
|
-
|
|
15
|
+
- the host-specific collaboration companions, `ultra-verify`, and host-native
|
|
16
|
+
explicit command or skill entry points;
|
|
17
17
|
- bounded review/debug agents, workflow-only hooks, MCP task state, and the
|
|
18
18
|
portable CLI/orchestrator.
|
|
19
19
|
|
|
@@ -191,31 +191,17 @@ Archive before prune; the confirmation token is intentionally required.
|
|
|
191
191
|
└── delivery/<workflow-id>.json
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
## 10.
|
|
194
|
+
## 10. Plugin and user-instruction isolation
|
|
195
195
|
|
|
196
|
-
General engineering doctrine
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
General engineering doctrine and long-term personal preferences live in each
|
|
197
|
+
host's durable `CLAUDE.md` or `AGENTS.md`. Ultra never writes those files. Its
|
|
198
|
+
workflow doctrine remains in plugin-owned skills and is loaded only through an
|
|
199
|
+
explicit public invocation.
|
|
199
200
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
Full mode replaces legacy unmarked prompt content with one complete host-native
|
|
206
|
-
engineering handbook:
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
ubp-handbook preview --runtime codex --full > /tmp/ubp-codex-handbook.md
|
|
210
|
-
ubp-handbook apply --runtime codex --full \
|
|
211
|
-
--confirm "PASTE_64_CHARACTER_TOKEN_HERE"
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
Supported runtime names are `claude`, `codex`, `opencode`, and `kimi`. The full
|
|
215
|
-
preview prints its content-bound confirmation token to stderr. Both apply modes
|
|
216
|
-
create a timestamped backup. Full mode preserves supported marker-delimited blocks
|
|
217
|
-
owned by other providers, follows handbook symlinks, and rejects missing or stale
|
|
218
|
-
confirmation. Plugin adapters never overwrite handbooks implicitly.
|
|
201
|
+
Installation and uninstall own only adapter-declared commands, skills, workers,
|
|
202
|
+
hooks, runtime assets, provenance, and host registration. Project authority is
|
|
203
|
+
created only by an explicit `ultra-init`, and uninstall never deletes `.ultra/`.
|
|
204
|
+
See [`PLUGIN-ISOLATION-CONTRACT.md`](./PLUGIN-ISOLATION-CONTRACT.md).
|
|
219
205
|
|
|
220
206
|
## 11. Verification
|
|
221
207
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -246,7 +246,8 @@ snapshot as an output; test, review, and delivery reports carry its digest forwa
|
|
|
246
246
|
|
|
247
247
|
`change.breadcrumb` derives the compact current position from state.db. Session,
|
|
248
248
|
edit, resume, and OpenCode lifecycle hooks invoke one bundled read-only reader
|
|
249
|
-
and inject only
|
|
249
|
+
and inject it only while state.db proves an active, blocked, or ready workflow,
|
|
250
|
+
never the intent body,
|
|
250
251
|
provider content, or a conversation summary. A changed Git HEAD, task contract, or
|
|
251
252
|
Change semantic authority marks the snapshot stale and permits recompilation through
|
|
252
253
|
`change.context`.
|
|
@@ -257,6 +258,14 @@ baseline/change ownership, and transaction boundaries, then persists them. It
|
|
|
257
258
|
does not call Anthropic, OpenAI, or another model provider and needs no separate
|
|
258
259
|
model API credential.
|
|
259
260
|
|
|
261
|
+
Plugin installation does not activate a semantic workflow and never edits the
|
|
262
|
+
user's `CLAUDE.md`, `AGENTS.md`, or equivalent durable instructions. Public
|
|
263
|
+
workflows require explicit user invocation. A completed workflow returns
|
|
264
|
+
mechanically allowed transitions and a host-owned recommendation, then stops;
|
|
265
|
+
it never launches the next public workflow. OpenCode realizes this boundary
|
|
266
|
+
with explicit commands backed by private plugin workflow assets, while Claude,
|
|
267
|
+
Codex, and Kimi use their native implicit-invocation controls.
|
|
268
|
+
|
|
260
269
|
File count, token estimate, and context-share overflow produce warnings, never a
|
|
261
270
|
refusal. The agent may narrow reads, load files lazily, or split a slice when that
|
|
262
271
|
preserves correctness; it must not raise a threshold merely to clear a gate or
|
package/docs/DECISIONS.md
CHANGED
|
@@ -70,6 +70,6 @@ release, deployment, or destructive effect.
|
|
|
70
70
|
- Runtime architecture: [`ARCHITECTURE.md`](./ARCHITECTURE.md)
|
|
71
71
|
- Workflow state and invalidation: [`WORKFLOW-LIFECYCLE.md`](./WORKFLOW-LIFECYCLE.md)
|
|
72
72
|
- Host compatibility: [`RUNTIME-COMPAT-MATRIX.md`](./RUNTIME-COMPAT-MATRIX.md)
|
|
73
|
-
-
|
|
73
|
+
- Plugin and user-instruction boundary: [`PLUGIN-ISOLATION-CONTRACT.md`](./PLUGIN-ISOLATION-CONTRACT.md)
|
|
74
74
|
- MCP interface: [`../spec/mcp-tools.yaml`](../spec/mcp-tools.yaml)
|
|
75
75
|
- Database contract: [`../spec/schemas/state-db.sql`](../spec/schemas/state-db.sql)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Plugin Isolation Contract
|
|
2
|
+
|
|
3
|
+
Ultra Builder Pro is an explicitly invoked, removable workflow plugin. Installation
|
|
4
|
+
must not change a user's durable engineering policy or initialize any repository.
|
|
5
|
+
|
|
6
|
+
## Owned surfaces
|
|
7
|
+
|
|
8
|
+
An adapter may write only:
|
|
9
|
+
|
|
10
|
+
- the host's plugin-owned command, skill, worker, hook, and runtime directories;
|
|
11
|
+
- the host registry or manifest entries required to load those assets;
|
|
12
|
+
- the plugin's own provenance and managed-file markers.
|
|
13
|
+
|
|
14
|
+
The first command allowed to create project authority is `ultra-init`. Once invoked,
|
|
15
|
+
Ultra owns only the repository-local `.ultra/` authority and its generated projections.
|
|
16
|
+
|
|
17
|
+
## Surfaces Ultra does not own
|
|
18
|
+
|
|
19
|
+
Install, update, doctor, and uninstall must not create, rewrite, trim, merge, or delete:
|
|
20
|
+
|
|
21
|
+
- user-level `CLAUDE.md`, `AGENTS.md`, or equivalent instruction files;
|
|
22
|
+
- repository-level instruction files;
|
|
23
|
+
- project source, Git history, remotes, or `.ultra/` data;
|
|
24
|
+
- memory or code-graph provider data.
|
|
25
|
+
|
|
26
|
+
Existing Ultra marker blocks in a user handbook are legacy user content after this
|
|
27
|
+
contract takes effect. They are not silently removed because the plugin cannot prove
|
|
28
|
+
ownership or preserve unrelated edits. The owner may review and remove them separately.
|
|
29
|
+
|
|
30
|
+
## Activation
|
|
31
|
+
|
|
32
|
+
Public Ultra workflows run only after an explicit user command or skill invocation.
|
|
33
|
+
Completion of one workflow may return allowed transitions and a recommendation, but it
|
|
34
|
+
must not start another public workflow.
|
|
35
|
+
|
|
36
|
+
Host adapters prevent implicit model activation with the strongest native mechanism:
|
|
37
|
+
|
|
38
|
+
- Claude Code and Kimi Code mark public workflow skills as model-disabled;
|
|
39
|
+
- Codex marks workflow metadata as implicit-invocation-disabled;
|
|
40
|
+
- OpenCode exposes public workflows through commands backed by private plugin assets,
|
|
41
|
+
not through the model-discoverable skill catalog.
|
|
42
|
+
|
|
43
|
+
Internal review-rule skills remain available only to their bounded workers.
|
|
44
|
+
|
|
45
|
+
## Idle behavior
|
|
46
|
+
|
|
47
|
+
Outside a repository containing `.ultra/state.db`, all Ultra hooks are silent. Inside
|
|
48
|
+
an initialized repository they remain silent unless a DB-authoritative workflow is
|
|
49
|
+
active, blocked, or ready. The sole idle-time enforcement is protection against direct
|
|
50
|
+
writes to generated Ultra projections; the DB remains authoritative.
|
|
51
|
+
|
|
52
|
+
Installation tests preserve byte-for-byte snapshots of all supported user handbook
|
|
53
|
+
paths. Runtime tests cover explicit activation, idle silence, active recovery, and
|
|
54
|
+
projection protection.
|
package/docs/ROADMAP.md
CHANGED
|
@@ -80,7 +80,6 @@ ultra-tools/ ← CLI fallback, migration, and diagnostics
|
|
|
80
80
|
└── legacy-memory.cjs (explicit inspect/archive/confirmed prune)
|
|
81
81
|
|
|
82
82
|
bin/install.js ← multi-runtime installer
|
|
83
|
-
bin/handbook.js ← explicit bounded or full host-native handbook sync
|
|
84
83
|
adapters/ ← native Claude/OpenCode/Codex plugin builders
|
|
85
84
|
skills/ ← allowlisted Ultra workflows, internal rules, and collab companions
|
|
86
85
|
hooks/ ← 7 executable workflow hooks + shared Context Spine helper; OpenCode uses native JavaScript hooks
|
|
@@ -89,7 +88,7 @@ docs/
|
|
|
89
88
|
├── ARCHITECTURE.md Phase 1 single-page entry point
|
|
90
89
|
├── WORKFLOW-LIFECYCLE.md baseline classification + DB transition contract
|
|
91
90
|
├── AGENT-CONTEXT.md Phase 3 canonical runtime context contract
|
|
92
|
-
├──
|
|
91
|
+
├── PLUGIN-ISOLATION-CONTRACT.md install, activation, idle, and ownership contract
|
|
93
92
|
├── RUNTIME-COMPAT-MATRIX.md Phase 4 runtime capability matrix
|
|
94
93
|
├── STATE-DB-ACCESS-POLICY.md Phase 2 multi-process write contract
|
|
95
94
|
├── COMMIT-HASH-BACKFILL.md Phase 2.8 two-commit completion flow
|
|
@@ -15,10 +15,10 @@ not installed.
|
|
|
15
15
|
|---|---|---|---|---|
|
|
16
16
|
| Package form | `.claude-plugin` native plugin | native config bundle + JS plugin | personal `.codex-plugin` | managed `kimi.plugin.json` plugin |
|
|
17
17
|
| Public entry | `/ultra-*`, `/learn` | `/ultra-*`, `/learn` | `$ultra-builder-pro:<skill>` | `/ultra-builder-pro:<command>` |
|
|
18
|
-
| Public Ultra workflows | 12 | 12 | 12 | 12 |
|
|
18
|
+
| Public Ultra workflows | 12, model invocation disabled | 12 commands backed by private workflow assets | 12, implicit invocation disabled | 12, model invocation disabled |
|
|
19
19
|
| Internal agent-rule skills | 4, non-user-facing | 4, non-user-facing | 4 with implicit invocation disabled | 4, consumed by review workers |
|
|
20
20
|
| Collaboration companions | `codex-collab`, `ultra-verify` | `cc-collab`, `codex-collab`, `ultra-verify` | `cc-collab`, `ultra-verify` | `cc-collab`, `codex-collab`, `ultra-verify` |
|
|
21
|
-
|
|
|
21
|
+
| Automatic workflow bootstrap | N/A | N/A | N/A | N/A |
|
|
22
22
|
| External browser/deploy/framework skills | N/A | N/A | N/A | N/A |
|
|
23
23
|
|
|
24
24
|
Codex converts workflows into namespaced skills and records eleven legacy
|
|
@@ -46,21 +46,17 @@ not presented as nonexistent custom agents.
|
|
|
46
46
|
|
|
47
47
|
| Lifecycle | Claude Code | OpenCode | Codex | Kimi Code 0.26+ |
|
|
48
48
|
|---|---|---|---|---|
|
|
49
|
-
| Session context/health | FULL,
|
|
49
|
+
| Session context/health | FULL, active-workflow breadcrumb on native `SessionStart` | FULL, native JS plugin injects only an active-workflow breadcrumb; health via `system.doctor` | FULL, active-workflow breadcrumb on native `SessionStart` | FULL, active-workflow breadcrumb via native hooks |
|
|
50
50
|
| Active edit boundary | FULL, `PreToolUse Edit|Write` | FULL, `tool.execute.before` rejects projection writes | FULL, `PreToolUse Edit|Write|apply_patch` | FULL, native `PreToolUse Edit|Write` deny contract |
|
|
51
51
|
| Pre-compact checkpoint | FULL | FULL | FULL | FULL, native `PreCompact` |
|
|
52
52
|
| Post-compact context injection | FULL | FULL, native compacting context | FULL, native `PostCompact` restore | DEGRADED; checkpoint restoration runs, but Kimi 0.26/0.27 does not reinject fire-and-forget hook text |
|
|
53
53
|
| Stop lifecycle advisory | FULL, native non-blocking `Stop` | N/A, no equivalent stop event needed | FULL, native non-blocking `Stop` | FULL, native non-blocking `Stop` |
|
|
54
54
|
| Subagent lifecycle evidence | FULL | DEGRADED, no equivalent packaged event | FULL | FULL, native `SubagentStart` / `SubagentStop` |
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
`.ultra/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Health/context hooks inspect initialized projects; projection protection also
|
|
62
|
-
applies at baseline, while compact/stop/subagent enforcement remains
|
|
63
|
-
active-workflow scoped. No host receives prompt capture, transcript capture,
|
|
56
|
+
Health/context, compact, stop, and subagent hooks remain silent unless
|
|
57
|
+
`.ultra/state.db` proves an active, blocked, or ready workflow. Projection
|
|
58
|
+
protection applies to every initialized project because generated projections
|
|
59
|
+
must never become a second authority. No host receives prompt capture, transcript capture,
|
|
64
60
|
observation journaling, session-summary memory, generic command blocking, or
|
|
65
61
|
generic post-edit policy.
|
|
66
62
|
Baseline-adoption and context-budget warnings are injected as advisory context;
|
|
@@ -89,20 +85,19 @@ generated launcher recovers the project from the inherited `PWD`, sets
|
|
|
89
85
|
cannot be established. On POSIX it intentionally uses `env node` because Kimi
|
|
90
86
|
0.26/0.27's embedded Node ABI differs from the ABI used to install `better-sqlite3`.
|
|
91
87
|
|
|
92
|
-
## 5. User
|
|
88
|
+
## 5. User instruction isolation
|
|
93
89
|
|
|
94
90
|
| Capability | Claude Code | OpenCode | Codex | Kimi Code 0.26+ |
|
|
95
91
|
|---|---|---|---|---|
|
|
96
92
|
| Durable user file | `~/.claude/CLAUDE.md` | `~/.config/opencode/AGENTS.md` | `~/.codex/AGENTS.md` | `~/.kimi-code/AGENTS.md` |
|
|
97
93
|
| Automatic overwrite by plugin install | no | no | no | no |
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
| Backup before change | FULL | FULL | FULL | FULL |
|
|
94
|
+
| Writer shipped by Ultra | N/A | N/A | N/A | N/A |
|
|
95
|
+
| Uninstall removes user instructions | no | no | no | no |
|
|
101
96
|
|
|
102
|
-
The Kimi plugin installer does not edit `~/.kimi-code/config.toml`.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
The Kimi plugin installer also does not edit `~/.kimi-code/config.toml`. Durable
|
|
98
|
+
user preferences belong to each host and remain outside Ultra's ownership.
|
|
99
|
+
Legacy Ultra handbook text is not proof of plugin ownership and is never
|
|
100
|
+
silently rewritten or removed.
|
|
106
101
|
|
|
107
102
|
## 6. Install, update, and uninstall
|
|
108
103
|
|
|
@@ -151,8 +146,8 @@ records. Uninstall refuses an unmanaged or conflicting root.
|
|
|
151
146
|
skills and generic/memory hooks from re-entering a package.
|
|
152
147
|
- `tests/retired-runtime.test.cjs` prevents retired runtime code or prompt text
|
|
153
148
|
from re-entering active product surfaces.
|
|
154
|
-
- `adapters/_shared/tests/
|
|
155
|
-
|
|
149
|
+
- `adapters/_shared/tests/plugin-isolation.test.cjs` and `tests/install.test.cjs`
|
|
150
|
+
verify the absence of a handbook writer and byte-preservation of all user instruction files.
|
|
156
151
|
- `adapters/_shared/tests/provenance.test.cjs` verifies normalized manifests,
|
|
157
152
|
content hashes, source attribution, corruption handling, and contract drift.
|
|
158
153
|
- `tests/install.test.cjs` verifies healthy four-host doctor output and
|
|
@@ -52,7 +52,7 @@ def main() -> None:
|
|
|
52
52
|
except ContextSpineError as exc:
|
|
53
53
|
print(f"[active_task_context] cannot inspect Context Spine: {exc}", file=sys.stderr)
|
|
54
54
|
breadcrumb = None
|
|
55
|
-
if breadcrumb and breadcrumb.get("
|
|
55
|
+
if breadcrumb and breadcrumb.get("workflow"):
|
|
56
56
|
print(json.dumps({"hookSpecificOutput": {
|
|
57
57
|
"hookEventName": "PreToolUse",
|
|
58
58
|
"additionalContext": render_breadcrumb(root, breadcrumb),
|
package/hooks/context_spine.py
CHANGED
package/hooks/health_check.py
CHANGED
|
@@ -27,6 +27,22 @@ def hook_input() -> dict:
|
|
|
27
27
|
return {}
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def has_active_workflow(db_path: Path) -> bool:
|
|
31
|
+
if not db_path.is_file():
|
|
32
|
+
return False
|
|
33
|
+
try:
|
|
34
|
+
uri = f"file:{db_path}?mode=ro"
|
|
35
|
+
with sqlite3.connect(uri, uri=True, timeout=1) as conn:
|
|
36
|
+
row = conn.execute(
|
|
37
|
+
"""SELECT 1 FROM workflow_runs
|
|
38
|
+
WHERE status IN ('active', 'blocked', 'ready')
|
|
39
|
+
LIMIT 1"""
|
|
40
|
+
).fetchone()
|
|
41
|
+
return row is not None
|
|
42
|
+
except sqlite3.Error:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
30
46
|
def inspect(root: Path) -> dict:
|
|
31
47
|
db_path = root / ".ultra" / "state.db"
|
|
32
48
|
report = {"status": "healthy", "project": str(root), "checks": {}}
|
|
@@ -171,6 +187,9 @@ def main() -> None:
|
|
|
171
187
|
if root is None:
|
|
172
188
|
print(json.dumps({}))
|
|
173
189
|
return
|
|
190
|
+
if not has_active_workflow(root / ".ultra" / "state.db"):
|
|
191
|
+
print(json.dumps({}))
|
|
192
|
+
return
|
|
174
193
|
report = inspect(root)
|
|
175
194
|
if report["status"] != "healthy":
|
|
176
195
|
print("[Ultra health] " + json.dumps(report, ensure_ascii=False, sort_keys=True), file=sys.stderr)
|