ultra-builder-pro-cli 0.20.0 → 0.22.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.
Files changed (109) hide show
  1. package/.ultra-template/docs/research/README.md +26 -5
  2. package/.ultra-template/specs/discovery.md +1 -1
  3. package/.ultra-template/tasks/tasks.json +10 -3
  4. package/.ultra-template/templates/task-context.md +19 -0
  5. package/CHANGELOG.md +106 -0
  6. package/README.md +215 -41
  7. package/adapters/_shared/codex-assets.cjs +69 -11
  8. package/adapters/_shared/interaction-contract.cjs +36 -3
  9. package/adapters/_shared/runtime-assets.cjs +41 -1
  10. package/adapters/claude.js +7 -5
  11. package/adapters/codex.js +1 -1
  12. package/adapters/kimi.js +2 -8
  13. package/adapters/opencode.js +43 -30
  14. package/bin/orchestrator.js +64 -24
  15. package/docs/AGENT-CONTEXT.md +89 -49
  16. package/docs/ARCHITECTURE.md +112 -45
  17. package/docs/ARTIFACT-AUTHORITY.md +274 -0
  18. package/docs/COMMIT-HASH-BACKFILL.md +33 -90
  19. package/docs/DECISIONS.md +14 -6
  20. package/docs/LEGACY-CLI-CROSSWALK.md +16 -6
  21. package/docs/PLUGIN-ISOLATION-CONTRACT.md +6 -4
  22. package/docs/ROADMAP.md +20 -6
  23. package/docs/RUNTIME-COMPAT-MATRIX.md +29 -16
  24. package/docs/STATE-DB-ACCESS-POLICY.md +43 -13
  25. package/docs/WORKFLOW-LIFECYCLE.md +105 -50
  26. package/hooks/README.md +10 -8
  27. package/hooks/active_task_context.py +105 -18
  28. package/hooks/context_spine.py +16 -4
  29. package/hooks/health_check.py +5 -4
  30. package/hooks/pre_stop_check.py +5 -2
  31. package/hooks/runtime_paths.py +396 -0
  32. package/hooks/subagent_tracker.py +5 -2
  33. package/hooks/workflow_checkpoint.py +16 -3
  34. package/hooks/workflow_context.py +11 -3
  35. package/hooks/workflow_resume.py +7 -4
  36. package/mcp-server/breadcrumb.cjs +20 -2
  37. package/mcp-server/lib/archive-journal.cjs +1026 -111
  38. package/mcp-server/lib/archive-mutation-worker.py +365 -0
  39. package/mcp-server/lib/artifact-registry.cjs +1127 -0
  40. package/mcp-server/lib/baseline-workflow.cjs +69 -28
  41. package/mcp-server/lib/change-packet.cjs +818 -0
  42. package/mcp-server/lib/change-workflow.cjs +932 -176
  43. package/mcp-server/lib/context-paths.cjs +142 -0
  44. package/mcp-server/lib/context-spine.cjs +511 -45
  45. package/mcp-server/lib/decision-dialogue.cjs +345 -3
  46. package/mcp-server/lib/delivery-transaction.cjs +471 -0
  47. package/mcp-server/lib/doctor-backup-worker.cjs +328 -0
  48. package/mcp-server/lib/doctor.cjs +401 -11
  49. package/mcp-server/lib/git-bootstrap.cjs +458 -47
  50. package/mcp-server/lib/init-project.cjs +240 -95
  51. package/mcp-server/lib/plan-publication-worker.cjs +830 -0
  52. package/mcp-server/lib/plan-store.cjs +700 -5
  53. package/mcp-server/lib/project-breadcrumb.cjs +23 -4
  54. package/mcp-server/lib/projector.cjs +211 -42
  55. package/mcp-server/lib/runtime-paths.cjs +1687 -0
  56. package/mcp-server/lib/safe-project-file.cjs +385 -0
  57. package/mcp-server/lib/spec-learning.cjs +28 -12
  58. package/mcp-server/lib/state-authority.cjs +19 -3
  59. package/mcp-server/lib/state-db.cjs +263 -9
  60. package/mcp-server/lib/state-ops.cjs +254 -50
  61. package/mcp-server/lib/task-ledger.cjs +1342 -0
  62. package/mcp-server/lib/telemetry.cjs +12 -3
  63. package/mcp-server/lib/workflow-state.cjs +610 -58
  64. package/mcp-server/server.cjs +423 -42
  65. package/orchestrator/daemon.cjs +2 -10
  66. package/orchestrator/parallel-orchestrator.cjs +314 -50
  67. package/orchestrator/planner/plan-builder.cjs +1 -1
  68. package/orchestrator/recovery.cjs +125 -7
  69. package/orchestrator/session-close-journal-worker.cjs +5 -0
  70. package/orchestrator/session-close-journal.cjs +1014 -0
  71. package/orchestrator/session-runner.cjs +679 -69
  72. package/orchestrator/worktree-manager.cjs +186 -35
  73. package/package.json +2 -3
  74. package/skills/cc-collab/SKILL.md +4 -1
  75. package/skills/codex-collab/SKILL.md +5 -2
  76. package/skills/ultra-change/SKILL.md +34 -8
  77. package/skills/ultra-deliver/SKILL.md +26 -11
  78. package/skills/ultra-dev/SKILL.md +52 -10
  79. package/skills/ultra-doctor/SKILL.md +16 -3
  80. package/skills/ultra-init/SKILL.md +22 -11
  81. package/skills/ultra-plan/SKILL.md +58 -18
  82. package/skills/ultra-research/SKILL.md +37 -35
  83. package/skills/ultra-review/SKILL.md +14 -8
  84. package/skills/ultra-status/SKILL.md +18 -9
  85. package/skills/ultra-test/SKILL.md +11 -5
  86. package/skills/ultra-think/SKILL.md +6 -2
  87. package/skills/ultra-think/references/decision-dialogue.md +17 -6
  88. package/skills/ultra-verify/SKILL.md +4 -1
  89. package/spec/cli-protocol.md +21 -7
  90. package/spec/mcp-tools.yaml +629 -44
  91. package/spec/schemas/change-delta.v1.schema.json +153 -0
  92. package/spec/schemas/context-file.v4.5.schema.json +1 -1
  93. package/spec/schemas/documentation-reconciliation.v1.schema.json +106 -0
  94. package/spec/schemas/mcp-tools.schema.json +1 -1
  95. package/spec/schemas/state-db.sql +51 -14
  96. package/spec/schemas/task-ledger.v1.schema.json +217 -0
  97. package/spec/schemas/tasks.v4.5.schema.json +1 -1
  98. package/ultra-tools/cli.cjs +3 -2
  99. package/ultra-tools/commands/db.cjs +76 -25
  100. package/ultra-tools/commands/migrate.cjs +1000 -82
  101. package/ultra-tools/commands/session.cjs +34 -8
  102. package/ultra-tools/commands/status.cjs +32 -6
  103. package/ultra-tools/commands/system.cjs +129 -22
  104. package/.ultra-template/tasks/contexts/TEMPLATE.md +0 -19
  105. package/commands/learn.md +0 -10
  106. package/orchestrator/skill-miner.cjs +0 -133
  107. package/output-styles/coding-vibes.md +0 -67
  108. package/output-styles/structural-thinking.md +0 -61
  109. package/skills/learn/SKILL.md +0 -58
@@ -8,6 +8,8 @@ const { spawnSync } = require('node:child_process');
8
8
  const yaml = require('js-yaml');
9
9
  const {
10
10
  CORE_PUBLIC_SKILLS,
11
+ RUNTIME_SUPPORT_FILES,
12
+ RUNTIME_WORKER_FILES,
11
13
  WORKFLOW_HOOK_FILES,
12
14
  skillPolicy,
13
15
  skillsForRuntime,
@@ -21,7 +23,10 @@ const {
21
23
  removeTree,
22
24
  writeAtomic,
23
25
  } = require('./file-ops.cjs');
24
- const { interactionContract } = require('./interaction-contract.cjs');
26
+ const {
27
+ adaptInteractionGuidance,
28
+ interactionContract,
29
+ } = require('./interaction-contract.cjs');
25
30
 
26
31
  const PLUGIN_NAME = 'ultra-builder-pro';
27
32
  const MANAGED_MARKER = 'Managed by Ultra Builder Pro Codex adapter.';
@@ -104,7 +109,7 @@ function adaptSkillAsset(input, targetName, _rel) {
104
109
  }
105
110
 
106
111
  function adaptHostText(input, skillName = '') {
107
- let text = String(input);
112
+ let text = adaptInteractionGuidance(input, 'codex');
108
113
  text = text.replaceAll('codex-collab', 'cc-collab');
109
114
  text = text.replaceAll('CLAUDE.md', 'AGENTS.md');
110
115
  text = text.replaceAll('$CLAUDE_PLUGIN_ROOT/skills', '~/plugins/ultra-builder-pro/skills');
@@ -122,11 +127,6 @@ function adaptHostText(input, skillName = '') {
122
127
  text = adaptCodexPrimaryText(text, skillName);
123
128
  }
124
129
 
125
- if (skillName === 'learn') {
126
- text = text.replaceAll("current host's user skill directory", '`~/.agents/skills`');
127
- text += `\n\n## Codex packaging requirement\n\nEach learned pattern must be a valid skill directory, not a loose Markdown file. The generated\n\`SKILL.md\` must start with only \`name\` and \`description\` frontmatter. Also create\n\`agents/openai.yaml\` with \`policy.allow_implicit_invocation: false\`. A new Codex task is required\nbefore the learned skill appears in discovery.\n`;
128
- }
129
-
130
130
  return text;
131
131
  }
132
132
 
@@ -135,7 +135,6 @@ function adaptedDescription(sourceDescription, targetName) {
135
135
  'cc-collab': 'Ask Claude Code for an independent read-only analysis while Codex owns verification and synthesis. Use only when the user explicitly requests CC or Claude Code collaboration.',
136
136
  'ultra-verify': 'Run Codex-primary cross-model verification with a read-only Claude Code advisor. Use only when the user explicitly requests independent model verification.',
137
137
  'ultra-dev': 'Execute one authoritative Ultra task with Codex-native implementation, verification, and review. Use when a dependency-ready slice is ready for development.',
138
- 'learn': 'Extract one verified reusable workflow from the current Codex task into a portable user skill. Use when the user explicitly asks to preserve the method.',
139
138
  };
140
139
  return special[targetName] || adaptHostText(String(sourceDescription || `${titleCase(targetName)} workflow for Codex.`), targetName)
141
140
  .replace(/\s+/g, ' ')
@@ -295,6 +294,16 @@ function copyHooks(repoRoot, pluginRoot) {
295
294
  function buildMcpRuntime(repoRoot, pluginRoot, { runtime = 'codex' } = {}) {
296
295
  const source = path.join(repoRoot, 'mcp-server', 'server.cjs');
297
296
  const cliSource = path.join(repoRoot, 'adapters', '_shared', 'codex-ultra-tools-entry.cjs');
297
+ const runtimeWorkerSources = new Map([
298
+ [
299
+ 'session-close-journal-worker.cjs',
300
+ path.join(repoRoot, 'orchestrator', 'session-close-journal-worker.cjs'),
301
+ ],
302
+ [
303
+ 'doctor-backup-worker.cjs',
304
+ path.join(repoRoot, 'mcp-server', 'lib', 'doctor-backup-worker.cjs'),
305
+ ],
306
+ ]);
298
307
  const runtimeRoot = path.join(pluginRoot, 'runtime');
299
308
  const buildRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-codex-mcp-build-'));
300
309
  let nccCli;
@@ -359,6 +368,54 @@ function buildMcpRuntime(repoRoot, pluginRoot, { runtime = 'codex' } = {}) {
359
368
  removeTree(cliBuildRoot);
360
369
  }
361
370
 
371
+ for (const workerFile of RUNTIME_WORKER_FILES) {
372
+ const workerSource = runtimeWorkerSources.get(workerFile);
373
+ if (!workerSource) {
374
+ throw new Error(`Ultra runtime worker source is not registered: ${workerFile}`);
375
+ }
376
+ const workerBuildRoot = fs.mkdtempSync(path.join(
377
+ os.tmpdir(),
378
+ `ubp-${path.basename(workerFile, '.cjs')}-build-`,
379
+ ));
380
+ try {
381
+ const bundled = spawnSync(process.execPath, [
382
+ nccCli,
383
+ 'build',
384
+ workerSource,
385
+ '-o',
386
+ workerBuildRoot,
387
+ '--no-cache',
388
+ '--quiet',
389
+ ], {
390
+ cwd: repoRoot,
391
+ encoding: 'utf8',
392
+ maxBuffer: 16 * 1024 * 1024,
393
+ });
394
+ if (bundled.error) throw bundled.error;
395
+ if (bundled.status !== 0) {
396
+ const detail = (bundled.stderr || bundled.stdout || '').trim();
397
+ throw new Error(
398
+ `ncc failed to bundle Ultra runtime worker ${workerFile}${detail ? `: ${detail}` : ''}`,
399
+ );
400
+ }
401
+ const workerOutput = path.join(workerBuildRoot, 'index.cjs');
402
+ if (!fs.existsSync(workerOutput)) {
403
+ throw new Error(`ncc did not emit Ultra runtime worker ${workerFile}`);
404
+ }
405
+ fs.copyFileSync(workerOutput, path.join(runtimeRoot, workerFile));
406
+ } finally {
407
+ removeTree(workerBuildRoot);
408
+ }
409
+ }
410
+
411
+ for (const supportFile of RUNTIME_SUPPORT_FILES) {
412
+ const sourceFile = path.join(repoRoot, 'mcp-server', 'lib', supportFile);
413
+ if (!fs.existsSync(sourceFile) || !fs.statSync(sourceFile).isFile()) {
414
+ throw new Error(`Ultra runtime support asset is missing: ${supportFile}`);
415
+ }
416
+ fs.copyFileSync(sourceFile, path.join(runtimeRoot, supportFile));
417
+ }
418
+
362
419
  const runtimeBootstrap = runtime === 'kimi' ? `
363
420
  const fs = require('node:fs');
364
421
  const pluginRoot = path.resolve(__dirname, '..');
@@ -381,9 +438,6 @@ if (!process.env.UBP_ROOT_DIR) {
381
438
  }
382
439
  process.env.UBP_ROOT_DIR = rootDir;
383
440
  }
384
- if (!process.env.UBP_DB_PATH) {
385
- process.env.UBP_DB_PATH = path.join(process.env.UBP_ROOT_DIR, '.ultra', 'state.db');
386
- }
387
441
  ` : '';
388
442
 
389
443
  const launcher = `'use strict';
@@ -409,6 +463,10 @@ main().catch((error) => {
409
463
  path.join(repoRoot, 'mcp-server', 'hook-event.cjs'),
410
464
  path.join(runtimeRoot, 'hook-event.cjs'),
411
465
  );
466
+ fs.copyFileSync(
467
+ path.join(repoRoot, 'mcp-server', 'lib', 'plan-publication-worker.cjs'),
468
+ path.join(runtimeRoot, 'plan-publication-worker.cjs'),
469
+ );
412
470
 
413
471
  const sourceToolsFile = path.join(repoRoot, 'spec', 'mcp-tools.yaml');
414
472
  const upstreamManifest = yaml.load(fs.readFileSync(sourceToolsFile, 'utf8'));
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { PUBLIC_CAPABILITY_GRAPH } = require('./runtime-assets.cjs');
4
+
3
5
  const SURFACES = Object.freeze({
4
6
  claude: Object.freeze({
5
7
  primary: 'AskUserQuestion',
@@ -23,12 +25,16 @@ const SURFACES = Object.freeze({
23
25
  }),
24
26
  });
25
27
 
28
+ const GENERIC_QUESTION_GUIDANCE =
29
+ /Use the host-native structured question surface declared by the installed interaction\s+contract(?: when it exists)?\./g;
30
+
26
31
  function interactionContract(runtime) {
27
32
  const question = SURFACES[runtime];
28
33
  if (!question) throw new Error(`unsupported interaction runtime: ${runtime}`);
29
34
  return {
30
- schema_version: '1.1',
35
+ schema_version: '1.2',
31
36
  runtime,
37
+ public_capability_graph: PUBLIC_CAPABILITY_GRAPH,
32
38
  authority: {
33
39
  user: [
34
40
  'product_intent',
@@ -54,10 +60,13 @@ function interactionContract(runtime) {
54
60
  semantic_selection_flow: [
55
61
  'inspect',
56
62
  'suggest',
57
- 'ask_if_unresolved',
63
+ 'host_native_ask',
58
64
  'normalize',
59
65
  'persist',
66
+ 'apply',
67
+ 'read_back',
60
68
  ],
69
+ adapter_authority: 'none',
61
70
  dependent_decisions: 'one_at_a_time',
62
71
  independent_low_load_questions: { maximum: 3 },
63
72
  explicit_current_intent: 'normalize_without_reconfirmation',
@@ -67,9 +76,11 @@ function interactionContract(runtime) {
67
76
  },
68
77
  persistence: {
69
78
  accepted_intent: 'trust_as_current_authority',
79
+ accepted_intent_recall: 'breadcrumb_and_decision_list',
70
80
  user_interaction_proof: 'not_required',
71
81
  raw_prompt_or_transcript: 'never_store',
72
82
  pending_question: 'store_only_when_recovery_requires_it',
83
+ application_evidence: 'record_applied_refs_when_another_authority_changes',
73
84
  },
74
85
  routing: {
75
86
  semantic_recommendation_owner: 'host_model',
@@ -80,4 +91,26 @@ function interactionContract(runtime) {
80
91
  };
81
92
  }
82
93
 
83
- module.exports = { SURFACES, interactionContract };
94
+ function interactionPrompt(runtime) {
95
+ const contract = interactionContract(runtime);
96
+ const surface = contract.interaction.question_surface;
97
+ const host = {
98
+ claude: 'Claude Code',
99
+ codex: 'Codex',
100
+ opencode: 'OpenCode',
101
+ kimi: 'Kimi Code',
102
+ }[runtime];
103
+ const primary = runtime === 'opencode' ? `\`${surface.primary}\`` : surface.primary;
104
+ return `Use ${host} ${primary} when ${surface.availability.replaceAll('_', ' ')} permits it.`;
105
+ }
106
+
107
+ function adaptInteractionGuidance(input, runtime) {
108
+ return String(input).replace(GENERIC_QUESTION_GUIDANCE, interactionPrompt(runtime));
109
+ }
110
+
111
+ module.exports = {
112
+ SURFACES,
113
+ adaptInteractionGuidance,
114
+ interactionContract,
115
+ interactionPrompt,
116
+ };
@@ -8,7 +8,6 @@
8
8
  */
9
9
 
10
10
  const CORE_PUBLIC_SKILLS = Object.freeze([
11
- 'learn',
12
11
  'ultra-init',
13
12
  'ultra-research',
14
13
  'ultra-plan',
@@ -22,6 +21,34 @@ const CORE_PUBLIC_SKILLS = Object.freeze([
22
21
  'ultra-doctor',
23
22
  ]);
24
23
 
24
+ const PUBLIC_CAPABILITY_MODES = Object.freeze({
25
+ 'ultra-init': 'setup',
26
+ 'ultra-research': 'workflow',
27
+ 'ultra-plan': 'workflow',
28
+ 'ultra-dev': 'workflow',
29
+ 'ultra-test': 'workflow',
30
+ 'ultra-review': 'workflow',
31
+ 'ultra-deliver': 'workflow',
32
+ 'ultra-status': 'read_only',
33
+ 'ultra-think': 'reasoning',
34
+ 'ultra-change': 'workflow',
35
+ 'ultra-doctor': 'diagnostic',
36
+ });
37
+
38
+ const PUBLIC_CAPABILITY_GRAPH = Object.freeze(Object.fromEntries(
39
+ CORE_PUBLIC_SKILLS.map((name) => [
40
+ name,
41
+ Object.freeze({
42
+ mode: PUBLIC_CAPABILITY_MODES[name],
43
+ activation: 'explicit_only',
44
+ next_capability_source: 'mcp_allowed_transitions',
45
+ recommendation_owner: 'host_model',
46
+ selection_owner: 'user',
47
+ automatic_invocation: false,
48
+ }),
49
+ ]),
50
+ ));
51
+
25
52
  const INTERNAL_AGENT_SKILLS = Object.freeze([
26
53
  'code-review-expert',
27
54
  'security-rules',
@@ -56,12 +83,22 @@ const WORKFLOW_HOOK_FILES = Object.freeze([
56
83
  'context_spine.py',
57
84
  'health_check.py',
58
85
  'pre_stop_check.py',
86
+ 'runtime_paths.py',
59
87
  'subagent_tracker.py',
60
88
  'workflow_checkpoint.py',
61
89
  'workflow_context.py',
62
90
  'workflow_resume.py',
63
91
  ]);
64
92
 
93
+ const RUNTIME_WORKER_FILES = Object.freeze([
94
+ 'session-close-journal-worker.cjs',
95
+ 'doctor-backup-worker.cjs',
96
+ ]);
97
+
98
+ const RUNTIME_SUPPORT_FILES = Object.freeze([
99
+ 'archive-mutation-worker.py',
100
+ ]);
101
+
65
102
  function skillsForRuntime(runtime) {
66
103
  const collab = COLLAB_SKILLS_BY_RUNTIME[runtime];
67
104
  if (!collab) throw new Error(`unsupported Ultra runtime: ${runtime}`);
@@ -84,11 +121,14 @@ function skillPolicy(name) {
84
121
 
85
122
  module.exports = {
86
123
  CORE_PUBLIC_SKILLS,
124
+ PUBLIC_CAPABILITY_GRAPH,
87
125
  INTERNAL_AGENT_SKILLS,
88
126
  SUPPORTED_RUNTIMES,
89
127
  COLLAB_SKILLS_BY_RUNTIME,
90
128
  MCP_DEPENDENT_SKILLS,
129
+ RUNTIME_SUPPORT_FILES,
91
130
  WORKFLOW_HOOK_FILES,
131
+ RUNTIME_WORKER_FILES,
92
132
  isSupportedRuntime,
93
133
  skillPolicy,
94
134
  skillsForRuntime,
@@ -16,6 +16,7 @@ const {
16
16
  } = require('./_shared/file-ops.cjs');
17
17
  const { buildMcpRuntime } = require('./_shared/codex-assets.cjs');
18
18
  const { parse: parseFrontmatter, serialize: serializeFrontmatter } = require('./_shared/frontmatter.cjs');
19
+ const { adaptInteractionGuidance } = require('./_shared/interaction-contract.cjs');
19
20
  const provenance = require('./_shared/provenance.cjs');
20
21
  const {
21
22
  CORE_PUBLIC_SKILLS,
@@ -55,13 +56,14 @@ function copySkills(repoRoot, target, names) {
55
56
  if (!fs.existsSync(source)) throw new Error(`missing allowlisted Claude skill: ${name}`);
56
57
  const files = copyTree(source, path.join(target, 'skills', name), {
57
58
  transform(buf, rel) {
58
- if (rel !== 'SKILL.md') return buf;
59
+ if (rel !== 'SKILL.md') {
60
+ return rel.endsWith('.md')
61
+ ? Buffer.from(adaptInteractionGuidance(buf.toString('utf8'), 'claude'))
62
+ : buf;
63
+ }
59
64
  const { fm, body } = parseFrontmatter(buf.toString('utf8'));
60
65
  if (!fm) throw new Error(`missing frontmatter in Claude skill: ${name}`);
61
- let adaptedBody = body;
62
- if (name === 'learn') {
63
- adaptedBody = adaptedBody.replaceAll("current host's user skill directory", '`~/.claude/skills`');
64
- }
66
+ let adaptedBody = adaptInteractionGuidance(body, 'claude');
65
67
  if (name === 'ultra-review') {
66
68
  adaptedBody = adaptedBody.replace(
67
69
  /the current host's native bounded-worker\s+mechanism/g,
package/adapters/codex.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * skills/** -> ~/plugins/ultra-builder-pro/skills/**
9
9
  * agents/*.md -> ~/.codex/agents/*.toml
10
10
  * hooks/*.py -> plugin hooks with a Codex wire adapter
11
- * mcp-server -> plugin .mcp.json (project cwd owns .ultra/state.db)
11
+ * mcp-server -> plugin .mcp.json (project cwd owns .ultra/.runtime/state.db)
12
12
  * plugin marketplace -> ~/.agents/plugins/marketplace.json
13
13
  *
14
14
  * Deprecated ~/.codex/prompts and ~/.codex/skills projection is intentionally
package/adapters/kimi.js CHANGED
@@ -16,6 +16,7 @@ const {
16
16
  } = require('./_shared/file-ops.cjs');
17
17
  const { buildMcpRuntime } = require('./_shared/codex-assets.cjs');
18
18
  const { parse: parseFm, serialize: serializeFm } = require('./_shared/frontmatter.cjs');
19
+ const { adaptInteractionGuidance } = require('./_shared/interaction-contract.cjs');
19
20
  const provenance = require('./_shared/provenance.cjs');
20
21
  const {
21
22
  CORE_PUBLIC_SKILLS,
@@ -66,7 +67,7 @@ function loadRegistry(file) {
66
67
  }
67
68
 
68
69
  function kimiTextTransform(input, assetName = '') {
69
- let text = String(input);
70
+ let text = adaptInteractionGuidance(input, 'kimi');
70
71
  text = text.replaceAll('$CLAUDE_PLUGIN_ROOT', '$KIMI_PLUGIN_ROOT');
71
72
  text = text.replaceAll('~/.claude/skills', '~/.kimi-code/skills');
72
73
  text = text.replaceAll('~/.claude/hooks', '~/.kimi-code/hooks');
@@ -99,17 +100,10 @@ function kimiTextTransform(input, assetName = '') {
99
100
  'Kimi `AgentSwarm` for parallel reviewers or one foreground Kimi `Agent` for a single reviewer, using the worker prompt files under `$KIMI_PLUGIN_ROOT/agents/`',
100
101
  );
101
102
  }
102
- if (assetName === 'learn') {
103
- text = text.replaceAll("current host's user skill directory", '`~/.kimi-code/skills`');
104
- }
105
103
  text = text.replace(
106
104
  /(^|[\s`("'])\/ultra-(?!builder-pro:)([a-z][a-z0-9-]*)/gm,
107
105
  '$1/ultra-builder-pro:ultra-$2',
108
106
  );
109
- text = text.replace(
110
- /(^|[\s`("'])\/learn(?=$|[\s`),.;:])/gm,
111
- '$1/ultra-builder-pro:learn',
112
- );
113
107
  text = text.replace(
114
108
  /(^|[\s`("'])\/clear(?=$|[\s`),.;:])/gm,
115
109
  '$1/new',
@@ -15,6 +15,7 @@ const {
15
15
  writeAtomic,
16
16
  } = require('./_shared/file-ops.cjs');
17
17
  const { buildMcpRuntime } = require('./_shared/codex-assets.cjs');
18
+ const { adaptInteractionGuidance } = require('./_shared/interaction-contract.cjs');
18
19
  const provenance = require('./_shared/provenance.cjs');
19
20
  const {
20
21
  CORE_PUBLIC_SKILLS,
@@ -67,7 +68,7 @@ function lowercaseFrontmatterTransform(buf, relPath) {
67
68
  }
68
69
 
69
70
  function openCodeTextTransform(input, assetName = '') {
70
- let text = String(input);
71
+ let text = adaptInteractionGuidance(input, 'opencode');
71
72
  text = text.replaceAll('$CLAUDE_PLUGIN_ROOT/skills', '~/.config/opencode/skills');
72
73
  text = text.replaceAll('~/.claude/skills', '~/.config/opencode/skills');
73
74
  text = text.replaceAll('~/.claude/hooks', '~/.config/opencode/.ultra-builder-pro/hooks');
@@ -96,9 +97,6 @@ function openCodeTextTransform(input, assetName = '') {
96
97
  'the OpenCode `task` tool using the installed bounded review agents',
97
98
  );
98
99
  }
99
- if (assetName === 'learn') {
100
- text = text.replaceAll("current host's user skill directory", '`~/.config/opencode/skills`');
101
- }
102
100
  return text;
103
101
  }
104
102
 
@@ -335,48 +333,41 @@ function preflightAssets(repoRoot, target) {
335
333
 
336
334
  function pluginSource() {
337
335
  return `// Managed by Ultra Builder Pro. Rebuild through the adapter; do not edit in place.
338
- import fs from "node:fs";
339
336
  import path from "node:path";
340
337
  import { execFileSync } from "node:child_process";
341
338
  import { fileURLToPath } from "node:url";
342
339
 
343
- const TASKS_PROJECTION = ".ultra/tasks/tasks.json";
340
+ const TEAM_TASK_LEDGER = ".ultra/tasks/tasks.json";
341
+ const LIVE_TASK_PROJECTION = ".ultra/.runtime/projections/tasks.json";
344
342
  const NODE_BINARY = ${JSON.stringify(process.execPath)};
345
343
  const PLUGIN_DIR = path.dirname(fileURLToPath(import.meta.url));
346
344
  const BREADCRUMB_CLI = path.resolve(
347
345
  PLUGIN_DIR, "..", ${JSON.stringify(BUNDLE_DIR)}, "runtime", "breadcrumb.cjs",
348
346
  );
349
347
 
350
- function findUltraRoot(directory) {
351
- let current = path.resolve(directory);
352
- while (true) {
353
- const stateDb = path.join(current, ".ultra", "state.db");
354
- if (fs.existsSync(stateDb) && fs.statSync(stateDb).isFile()) return current;
355
- const parent = path.dirname(current);
356
- if (parent === current) return null;
357
- current = parent;
358
- }
359
- }
360
-
361
348
  function readUltraContext(directory) {
362
- const root = findUltraRoot(directory);
363
- if (!root) return null;
364
349
  try {
365
- const raw = execFileSync(NODE_BINARY, [BREADCRUMB_CLI, root], {
366
- cwd: root,
350
+ const raw = execFileSync(NODE_BINARY, [BREADCRUMB_CLI, "--discover", directory], {
351
+ cwd: directory,
367
352
  encoding: "utf8",
368
353
  stdio: ["ignore", "pipe", "pipe"],
369
354
  timeout: 5000,
370
355
  });
371
356
  const value = JSON.parse(raw);
357
+ const root = value?.root;
358
+ if (typeof root !== "string" || !root) return null;
372
359
  // A canonical breadcrumb is injectable only while DB authority proves an active workflow.
373
360
  if (!value?.breadcrumb || !value.breadcrumb.workflow
374
361
  || typeof value.text !== "string" || !value.text) {
375
362
  return { root, breadcrumb: null, text: null };
376
363
  }
377
364
  return { root, breadcrumb: value.breadcrumb, text: value.text };
378
- } catch {
379
- return { root, breadcrumb: null, text: null };
365
+ } catch (error) {
366
+ const detail = String(error?.stderr || error?.message || error);
367
+ if (/RUNTIME_(?:STATE_CONFLICT|PATH_UNSAFE|ORPHAN_SIDECAR|ROOT_INVALID|AUTHORITY_MISMATCH)|both legacy .*runtime.*state\\.db/i.test(detail)) {
368
+ throw new Error(detail.trim());
369
+ }
370
+ return null;
380
371
  }
381
372
  }
382
373
 
@@ -392,11 +383,30 @@ function targetPaths(tool, args) {
392
383
  return [...new Set(paths)];
393
384
  }
394
385
 
395
- function isTasksProjection(active, candidate) {
386
+ function managedTaskRootForTarget(baseRoot, candidate) {
396
387
  const target = path.isAbsolute(candidate)
397
388
  ? path.resolve(candidate)
398
- : path.resolve(active.root, candidate);
399
- return target === path.resolve(active.root, TASKS_PROJECTION);
389
+ : path.resolve(baseRoot, candidate);
390
+ if (target === path.resolve(baseRoot, TEAM_TASK_LEDGER)
391
+ || target === path.resolve(baseRoot, LIVE_TASK_PROJECTION)) return baseRoot;
392
+ if (path.basename(target) !== "tasks.json") return null;
393
+ const parent = path.dirname(target);
394
+ if (path.basename(parent) === "tasks"
395
+ && path.basename(path.dirname(parent)) === ".ultra") {
396
+ return path.dirname(path.dirname(parent));
397
+ }
398
+ if (path.basename(parent) === "projections"
399
+ && path.basename(path.dirname(parent)) === ".runtime"
400
+ && path.basename(path.dirname(path.dirname(parent))) === ".ultra") {
401
+ return path.dirname(path.dirname(path.dirname(parent)));
402
+ }
403
+ return null;
404
+ }
405
+
406
+ function protectsManagedTaskFile(baseRoot, candidate) {
407
+ const targetRoot = managedTaskRootForTarget(baseRoot, candidate);
408
+ if (!targetRoot) return false;
409
+ return Boolean(readUltraContext(targetRoot));
400
410
  }
401
411
 
402
412
  export const UltraBuilderProPlugin = async ({ directory, worktree }) => {
@@ -417,13 +427,16 @@ export const UltraBuilderProPlugin = async ({ directory, worktree }) => {
417
427
  },
418
428
  "tool.execute.before": async (input, output) => {
419
429
  refresh();
420
- if (!active) return;
421
430
  const tool = String(input?.tool ?? "").toLowerCase();
422
431
  if (!["write", "edit", "apply_patch"].includes(tool)) return;
423
- if (targetPaths(tool, output?.args).some((candidate) => isTasksProjection(active, candidate))) {
432
+ if (targetPaths(tool, output?.args).some(
433
+ (candidate) => protectsManagedTaskFile(root, candidate),
434
+ )) {
424
435
  throw new Error(
425
- "Ultra Builder Pro refused a direct write to .ultra/tasks/tasks.json. " +
426
- ".ultra/state.db is authoritative; use MCP task tools and run ultra-doctor when state or projection health is degraded."
436
+ "Ultra Builder Pro refused a direct write to an MCP-managed task file. " +
437
+ ".ultra/tasks/tasks.json is the MCP-published team checkpoint and " +
438
+ ".ultra/.runtime/projections/tasks.json is the checkout-local DB view. " +
439
+ "Use the Ultra MCP task or task.ledger tools."
427
440
  );
428
441
  }
429
442
  },