ultra-builder-pro-cli 0.5.3 → 0.7.0

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