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 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.18.0...HEAD
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 does not mutate durable user instructions. To preview and
159
- explicitly converge a legacy handbook into the complete host-native engineering
160
- contract, use full mode:
161
-
162
- ```bash
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
- | [User Handbook Contract](./docs/USER-HANDBOOK-CONTRACT.md) | Shared user-level policy and managed host renderings |
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 |
@@ -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': skillPolicy(name).userInvocable,
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?.skill === BOOTSTRAP_SKILL;
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,
@@ -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, body } = parseFm(buf.toString('utf8'));
103
- if (!fm) return Buffer.from(openCodeTextTransform(buf.toString('utf8'), commandName), 'utf8');
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
- return Buffer.from(serializeFm(nativeFm, openCodeTextTransform(body, commandName)), 'utf8');
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 transformed = withManagedMarker(openCodeCommandTransform(fs.readFileSync(source), file, name));
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 = skillsForRuntime('opencode');
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 skillsForRuntime('opencode')) {
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 ultra = path.join(current, ".ultra");
320
- if (fs.existsSync(ultra) && fs.statSync(ultra).isDirectory()) return current;
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
- if (!value?.breadcrumb || typeof value.text !== "string" || !value.text) {
339
- throw new Error("canonical breadcrumb response is incomplete");
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 (error) {
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 = skillsForRuntime('opencode').map((name) => ({
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);
@@ -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 the minimal
16
- Kimi session bootstrap required to establish the shared runtime boundary;
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. User handbook integration
194
+ ## 10. Plugin and user-instruction isolation
195
195
 
196
- General engineering doctrine lives in the host's durable `CLAUDE.md` or `AGENTS.md`.
197
- Ultra Builder Pro supports two explicit operations. Default mode updates only the
198
- bounded runtime contract:
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
- ```bash
201
- ubp-handbook preview --runtime codex
202
- ubp-handbook apply --runtime codex
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
 
@@ -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 this breadcrumb, never the intent body,
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
- - User handbook projection: [`USER-HANDBOOK-CONTRACT.md`](./USER-HANDBOOK-CONTRACT.md)
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
- ├── USER-HANDBOOK-CONTRACT.md bounded/full CLAUDE.md / AGENTS.md contract
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
- | Host bootstrap | native plugin context | native plugin context | native plugin context | `using-ultra-builder-pro` session-start skill |
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, DB-derived Context Spine breadcrumb on native `SessionStart` | FULL, the native JS plugin invokes the same bundled DB breadcrumb reader; health via `system.doctor` | FULL, DB-derived breadcrumb on native `SessionStart` | FULL, DB-derived breadcrumb via session-start skill + hooks |
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
- Kimi's session bootstrap therefore also instructs recovery to inspect
57
- `.ultra/runtime/checkpoint.json` and call Ultra status/doctor after compaction.
58
- This keeps durable recovery intact without claiming dynamic context injection
59
- that the host does not guarantee.
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 handbook presentation
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
- | Explicit managed-block sync | `ubp-handbook apply --runtime claude` | `ubp-handbook apply --runtime opencode` | `ubp-handbook apply --runtime codex` | `ubp-handbook apply --runtime kimi` |
99
- | Explicit complete-handbook convergence | `--full --confirm TOKEN` | `--full --confirm TOKEN` | `--full --confirm TOKEN` | `--full --confirm TOKEN` |
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`. Handbook
103
- sync remains a separate, previewable action so user policy and plugin mechanics
104
- do not become one irreversible mutation. Full mode is a semantic host rendering,
105
- not a product-name or path substitution.
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/handbook.test.cjs` verifies bounded and full host
155
- rendering, external-provider block preservation, backup, migration, and idempotency.
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("change_id"):
55
+ if breadcrumb and breadcrumb.get("workflow"):
56
56
  print(json.dumps({"hookSpecificOutput": {
57
57
  "hookEventName": "PreToolUse",
58
58
  "additionalContext": render_breadcrumb(root, breadcrumb),
@@ -22,7 +22,7 @@ class Breadcrumb(dict):
22
22
 
23
23
  def find_root(start: Path):
24
24
  for root in (start, *start.parents):
25
- if (root / ".ultra").is_dir():
25
+ if (root / ".ultra" / "state.db").is_file():
26
26
  return root
27
27
  return None
28
28
 
@@ -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)