ultra-builder-pro-cli 0.5.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 (206) hide show
  1. package/.ultra-template/delivery-report.json +9 -0
  2. package/.ultra-template/docs/research/README.md +53 -0
  3. package/.ultra-template/specs/architecture.md +250 -0
  4. package/.ultra-template/specs/discovery.md +248 -0
  5. package/.ultra-template/specs/product.md +223 -0
  6. package/.ultra-template/tasks/contexts/TEMPLATE.md +57 -0
  7. package/.ultra-template/tasks/tasks.json +33 -0
  8. package/.ultra-template/test-report.json +8 -0
  9. package/CHANGELOG.md +194 -0
  10. package/LICENSE +21 -0
  11. package/README.md +187 -0
  12. package/adapters/_shared/codex-assets.cjs +1128 -0
  13. package/adapters/_shared/codex-ultra-tools-entry.cjs +11 -0
  14. package/adapters/_shared/file-ops.cjs +115 -0
  15. package/adapters/_shared/frontmatter.cjs +52 -0
  16. package/adapters/_shared/handbook.cjs +144 -0
  17. package/adapters/_shared/md-to-toml.cjs +54 -0
  18. package/adapters/_shared/path-rewrite.cjs +79 -0
  19. package/adapters/_shared/rtk-detect.cjs +86 -0
  20. package/adapters/_shared/runtime-assets.cjs +70 -0
  21. package/adapters/_shared/settings-merge.cjs +76 -0
  22. package/adapters/_shared/tests/file-ops.test.cjs +54 -0
  23. package/adapters/_shared/tests/frontmatter.test.cjs +47 -0
  24. package/adapters/_shared/tests/handbook.test.cjs +86 -0
  25. package/adapters/_shared/tests/md-to-toml.test.cjs +48 -0
  26. package/adapters/_shared/tests/path-rewrite.test.cjs +60 -0
  27. package/adapters/_shared/tests/resolve-target.test.cjs +85 -0
  28. package/adapters/_shared/tests/rtk-detect.test.cjs +116 -0
  29. package/adapters/_shared/tests/runtime-assets.test.cjs +107 -0
  30. package/adapters/_shared/tests/settings-merge.test.cjs +85 -0
  31. package/adapters/_shared/tests/validate.test.cjs +30 -0
  32. package/adapters/_shared/validate.cjs +24 -0
  33. package/adapters/claude.js +182 -0
  34. package/adapters/codex.js +385 -0
  35. package/adapters/gemini.js +204 -0
  36. package/adapters/opencode.js +506 -0
  37. package/adapters/tests/claude.test.cjs +117 -0
  38. package/adapters/tests/codex-hook.test.cjs +90 -0
  39. package/adapters/tests/codex.test.cjs +356 -0
  40. package/adapters/tests/gemini.test.cjs +112 -0
  41. package/adapters/tests/opencode.test.cjs +246 -0
  42. package/agents/code-reviewer.md +102 -0
  43. package/agents/debugger.md +127 -0
  44. package/agents/review-code.md +125 -0
  45. package/agents/review-comments.md +98 -0
  46. package/agents/review-coordinator.md +146 -0
  47. package/agents/review-design.md +133 -0
  48. package/agents/review-errors.md +87 -0
  49. package/agents/review-tests.md +93 -0
  50. package/agents/tdd-runner.md +92 -0
  51. package/bin/handbook.js +54 -0
  52. package/bin/install.js +254 -0
  53. package/bin/orchestrator.js +214 -0
  54. package/commands/learn.md +41 -0
  55. package/commands/ultra-deliver.md +45 -0
  56. package/commands/ultra-dev.md +50 -0
  57. package/commands/ultra-init.md +51 -0
  58. package/commands/ultra-plan.md +49 -0
  59. package/commands/ultra-research.md +59 -0
  60. package/commands/ultra-status.md +46 -0
  61. package/commands/ultra-test.md +48 -0
  62. package/commands/ultra-think.md +45 -0
  63. package/docs/AGENT-CONTEXT.md +143 -0
  64. package/docs/ARCHITECTURE.md +197 -0
  65. package/docs/COMMIT-HASH-BACKFILL.md +101 -0
  66. package/docs/LEGACY-HERMES.md +1175 -0
  67. package/docs/PLAN.zh-CN.md +1683 -0
  68. package/docs/ROADMAP.md +102 -0
  69. package/docs/RUNTIME-COMPAT-MATRIX.md +98 -0
  70. package/docs/STATE-DB-ACCESS-POLICY.md +109 -0
  71. package/docs/USER-HANDBOOK-CONTRACT.md +61 -0
  72. package/hooks/README.md +24 -0
  73. package/hooks/active_task_context.py +44 -0
  74. package/hooks/adapters/codex.py +302 -0
  75. package/hooks/health_check.py +61 -0
  76. package/hooks/pre_stop_check.py +56 -0
  77. package/hooks/subagent_tracker.py +116 -0
  78. package/hooks/workflow_checkpoint.py +56 -0
  79. package/hooks/workflow_context.py +63 -0
  80. package/hooks/workflow_resume.py +50 -0
  81. package/mcp-server/lib/init-project.cjs +162 -0
  82. package/mcp-server/lib/llm-client.cjs +157 -0
  83. package/mcp-server/lib/llm-client.test.cjs +156 -0
  84. package/mcp-server/lib/plan-store.cjs +96 -0
  85. package/mcp-server/lib/plan-store.test.cjs +132 -0
  86. package/mcp-server/lib/prd-parser.cjs +110 -0
  87. package/mcp-server/lib/prd-parser.test.cjs +171 -0
  88. package/mcp-server/lib/pricing.cjs +53 -0
  89. package/mcp-server/lib/projector.cjs +158 -0
  90. package/mcp-server/lib/state-db.cjs +113 -0
  91. package/mcp-server/lib/state-db.test.cjs +116 -0
  92. package/mcp-server/lib/state-ops.cjs +725 -0
  93. package/mcp-server/lib/task-expander.cjs +123 -0
  94. package/mcp-server/lib/task-expander.test.cjs +253 -0
  95. package/mcp-server/lib/telemetry.cjs +72 -0
  96. package/mcp-server/lib/topo.cjs +131 -0
  97. package/mcp-server/lib/topo.test.cjs +158 -0
  98. package/mcp-server/server.cjs +482 -0
  99. package/mcp-server/tests/circuit-breaker.test.cjs +125 -0
  100. package/mcp-server/tests/commit-hash-flow.test.cjs +117 -0
  101. package/mcp-server/tests/concurrency-update.test.cjs +96 -0
  102. package/mcp-server/tests/concurrency.test.cjs +112 -0
  103. package/mcp-server/tests/db-ops.test.cjs +117 -0
  104. package/mcp-server/tests/init-project.test.cjs +141 -0
  105. package/mcp-server/tests/migrate.test.cjs +144 -0
  106. package/mcp-server/tests/projector.test.cjs +130 -0
  107. package/mcp-server/tests/server-contract.test.cjs +750 -0
  108. package/mcp-server/tests/staleness.test.cjs +190 -0
  109. package/mcp-server/tests/state-ops.test.cjs +286 -0
  110. package/mcp-server/tests/tagged-tasks.test.cjs +163 -0
  111. package/mcp-server/tests/telemetry.test.cjs +205 -0
  112. package/orchestrator/auto-merge.cjs +109 -0
  113. package/orchestrator/code-graph-watcher.cjs +121 -0
  114. package/orchestrator/daemon.cjs +112 -0
  115. package/orchestrator/dispatch-rules.cjs +93 -0
  116. package/orchestrator/parallel-orchestrator.cjs +195 -0
  117. package/orchestrator/planner/expand-prompt.cjs +62 -0
  118. package/orchestrator/planner/expand-prompt.test.cjs +52 -0
  119. package/orchestrator/planner/plan-builder.cjs +116 -0
  120. package/orchestrator/planner/plan-builder.test.cjs +165 -0
  121. package/orchestrator/planner/prd-prompt.cjs +44 -0
  122. package/orchestrator/planner/prd-prompt.test.cjs +30 -0
  123. package/orchestrator/recovery.cjs +70 -0
  124. package/orchestrator/session-runner.cjs +224 -0
  125. package/orchestrator/skill-miner.cjs +133 -0
  126. package/orchestrator/tests/auto-merge.test.cjs +216 -0
  127. package/orchestrator/tests/code-graph-watcher.test.cjs +183 -0
  128. package/orchestrator/tests/daemon.test.cjs +260 -0
  129. package/orchestrator/tests/dispatch-rules.test.cjs +122 -0
  130. package/orchestrator/tests/parallel-orchestrator.test.cjs +233 -0
  131. package/orchestrator/tests/phase-8b-gate.test.cjs +183 -0
  132. package/orchestrator/tests/recovery.test.cjs +190 -0
  133. package/orchestrator/tests/session-runner.test.cjs +314 -0
  134. package/orchestrator/tests/skill-miner.test.cjs +147 -0
  135. package/orchestrator/tests/worktree-manager.test.cjs +148 -0
  136. package/orchestrator/worktree-manager.cjs +131 -0
  137. package/output-styles/coding-vibes.md +67 -0
  138. package/output-styles/structural-thinking.md +61 -0
  139. package/package.json +109 -0
  140. package/settings.json +8 -0
  141. package/skills/cc-collab/SKILL.md +41 -0
  142. package/skills/code-review-expert/SKILL.md +118 -0
  143. package/skills/code-review-expert/references/code-quality-checklist.md +112 -0
  144. package/skills/code-review-expert/references/integration-checklist.md +46 -0
  145. package/skills/code-review-expert/references/removal-plan.md +52 -0
  146. package/skills/code-review-expert/references/security-checklist.md +100 -0
  147. package/skills/code-review-expert/references/solid-checklist.md +65 -0
  148. package/skills/codex-collab/SKILL.md +52 -0
  149. package/skills/codex-collab/evals/evals.json +40 -0
  150. package/skills/gemini-collab/SKILL.md +51 -0
  151. package/skills/gemini-collab/evals/evals.json +42 -0
  152. package/skills/integration-rules/SKILL.md +71 -0
  153. package/skills/learn/SKILL.md +117 -0
  154. package/skills/security-rules/SKILL.md +60 -0
  155. package/skills/testing-rules/SKILL.md +56 -0
  156. package/skills/ultra-deliver/SKILL.md +171 -0
  157. package/skills/ultra-dev/SKILL.md +324 -0
  158. package/skills/ultra-init/SKILL.md +148 -0
  159. package/skills/ultra-plan/SKILL.md +314 -0
  160. package/skills/ultra-research/SKILL.md +156 -0
  161. package/skills/ultra-research/steps/step-00-problem-validation.md +154 -0
  162. package/skills/ultra-research/steps/step-01-opportunity-discovery.md +172 -0
  163. package/skills/ultra-research/steps/step-02-market-assessment.md +195 -0
  164. package/skills/ultra-research/steps/step-03-competitive-landscape.md +197 -0
  165. package/skills/ultra-research/steps/step-04-product-strategy.md +196 -0
  166. package/skills/ultra-research/steps/step-05-assumptions-validation.md +234 -0
  167. package/skills/ultra-research/steps/step-10-user-personas.md +169 -0
  168. package/skills/ultra-research/steps/step-11-user-scenarios.md +181 -0
  169. package/skills/ultra-research/steps/step-20-user-stories.md +156 -0
  170. package/skills/ultra-research/steps/step-21-features-scope.md +157 -0
  171. package/skills/ultra-research/steps/step-22-success-metrics.md +195 -0
  172. package/skills/ultra-research/steps/step-30-architecture-context.md +154 -0
  173. package/skills/ultra-research/steps/step-31-solution-strategy.md +172 -0
  174. package/skills/ultra-research/steps/step-32-building-blocks.md +188 -0
  175. package/skills/ultra-research/steps/step-40-deployment.md +123 -0
  176. package/skills/ultra-research/steps/step-41-quality-risks.md +135 -0
  177. package/skills/ultra-research/steps/step-99-synthesis.md +188 -0
  178. package/skills/ultra-review/SKILL.md +437 -0
  179. package/skills/ultra-review/references/unified-schema.md +127 -0
  180. package/skills/ultra-review/scripts/review_verdict_update.py +99 -0
  181. package/skills/ultra-review/scripts/review_wait.py +120 -0
  182. package/skills/ultra-status/SKILL.md +162 -0
  183. package/skills/ultra-test/SKILL.md +195 -0
  184. package/skills/ultra-think/SKILL.md +109 -0
  185. package/skills/ultra-verify/SKILL.md +111 -0
  186. package/skills/ultra-verify/evals/evals.json +51 -0
  187. package/skills/ultra-verify/references/confidence-system.md +38 -0
  188. package/skills/ultra-verify/references/cross-verify-modes.md +73 -0
  189. package/skills/ultra-verify/scripts/verify_wait.py +131 -0
  190. package/spec/cli-protocol.md +118 -0
  191. package/spec/mcp-tools.yaml +658 -0
  192. package/spec/schemas/command-manifest.schema.json +47 -0
  193. package/spec/schemas/context-file.v4.5.schema.json +54 -0
  194. package/spec/schemas/mcp-tools.schema.json +136 -0
  195. package/spec/schemas/skill-manifest.schema.json +94 -0
  196. package/spec/schemas/state-db.sql +154 -0
  197. package/spec/schemas/tasks.v4.5.schema.json +66 -0
  198. package/ultra-tools/cli.cjs +121 -0
  199. package/ultra-tools/commands/db.cjs +175 -0
  200. package/ultra-tools/commands/legacy-memory.cjs +159 -0
  201. package/ultra-tools/commands/migrate.cjs +354 -0
  202. package/ultra-tools/commands/session.cjs +180 -0
  203. package/ultra-tools/commands/status.cjs +158 -0
  204. package/ultra-tools/commands/task.cjs +95 -0
  205. package/ultra-tools/legacy-memory.test.cjs +80 -0
  206. package/ultra-tools/status-command.test.cjs +132 -0
@@ -0,0 +1,1128 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const { spawnSync } = require('node:child_process');
8
+ const yaml = require('js-yaml');
9
+ const {
10
+ CORE_PUBLIC_SKILLS,
11
+ INTERNAL_AGENT_SKILLS,
12
+ WORKFLOW_HOOK_FILES,
13
+ skillsForRuntime,
14
+ } = require('./runtime-assets.cjs');
15
+
16
+ const { parse: parseFrontmatter } = require('./frontmatter.cjs');
17
+ const {
18
+ copyTree,
19
+ ensureDir,
20
+ listRelative,
21
+ removeTree,
22
+ writeAtomic,
23
+ } = require('./file-ops.cjs');
24
+
25
+ const PLUGIN_NAME = 'ultra-builder-pro';
26
+ const MANAGED_MARKER = 'Managed by Ultra Builder Pro Codex adapter.';
27
+ const CODEX_NATIVE_MCP_REPLACEMENTS = Object.freeze({
28
+ 'review.run': {
29
+ surface: 'native_custom_agents',
30
+ replacement: '$ultra-builder-pro:ultra-review with the installed review-* agents',
31
+ },
32
+ 'review.verdict': {
33
+ surface: 'native_custom_agents',
34
+ replacement: 'review-coordinator synthesis using the Ultra unified review artifact',
35
+ },
36
+ 'impact.radius': {
37
+ surface: 'codex_code_discovery',
38
+ replacement: 'indexed code graph tools when current, otherwise targeted repository reads',
39
+ },
40
+ 'impact.changes': {
41
+ surface: 'codex_code_discovery',
42
+ replacement: 'git diff plus indexed code graph tools when current',
43
+ },
44
+ 'impact.dependents': {
45
+ surface: 'codex_code_discovery',
46
+ replacement: 'indexed caller/dependency tracing when current, otherwise targeted repository search',
47
+ },
48
+ 'skill.resolve': {
49
+ surface: 'plugin_skill_discovery',
50
+ replacement: 'Codex plugin skill discovery with explicit $ultra-builder-pro:<skill> invocation',
51
+ },
52
+ 'skill.manifest': {
53
+ surface: 'plugin_skill_discovery',
54
+ replacement: 'the installed plugin skill SKILL.md and agents/openai.yaml contract',
55
+ },
56
+ 'ask.question': {
57
+ surface: 'direct_user_interaction',
58
+ replacement: 'ask the user directly, or request_user_input when that Codex surface is available',
59
+ },
60
+ 'ask.menu': {
61
+ surface: 'direct_user_interaction',
62
+ replacement: 'present a concise choice directly, or request_user_input when that Codex surface is available',
63
+ },
64
+ });
65
+ const COMMAND_NAMES = Object.freeze(CORE_PUBLIC_SKILLS.filter((name) => name !== 'ultra-review'));
66
+ const SKILL_REFERENCE_NAMES = Object.freeze([
67
+ ...COMMAND_NAMES,
68
+ 'cc-collab',
69
+ 'gemini-collab',
70
+ 'ultra-review',
71
+ 'ultra-verify',
72
+ ]);
73
+ const INTERNAL_SKILLS = new Set(INTERNAL_AGENT_SKILLS);
74
+ const CODEX_PRIMARY_SKILLS = new Set([...COMMAND_NAMES, 'ultra-review']);
75
+ const TEXT_EXTENSIONS = new Set(['.md', '.json', '.py', '.sh', '.txt', '.yaml', '.yml']);
76
+
77
+ const CC_COLLAB_BODY = `# Claude Code Collaboration for Codex
78
+
79
+ Codex is the primary agent and Claude Code is the independent advisor. Codex owns scope, evidence,
80
+ decisions, and final synthesis.
81
+
82
+ ## Preflight
83
+
84
+ 1. Confirm \`claude\` is installed and capture \`claude --version\`.
85
+ 2. Confirm the target workspace, trust boundary, and exact question.
86
+ 3. Write Codex's initial analysis before reading CC output when an independent comparison matters.
87
+
88
+ ## Invoke safely
89
+
90
+ Run Claude Code non-interactively and read-only by default:
91
+
92
+ \`\`\`bash
93
+ claude --safe-mode -p "<bounded prompt>" \\
94
+ --permission-mode plan \\
95
+ --tools "Read,Grep,Glob,Bash" \\
96
+ --output-format text \\
97
+ --no-session-persistence
98
+ \`\`\`
99
+
100
+ Safe mode isolates the advisory call from CC-side instructions, plugins, hooks, MCP servers, skills,
101
+ and custom agents. Do not enable permission bypass, edit tools, background mutation, or session
102
+ persistence for an advisory call. Do not pass credentials, unrelated files, or an unbounded home
103
+ directory. For a code question, specify the repository path, diff range or files, acceptance criteria,
104
+ and required answer shape.
105
+
106
+ Use a temporary output file for large responses. If CC is missing, unauthenticated, times out, or
107
+ returns empty output, report the degraded path and continue with Codex's own evidence.
108
+
109
+ ## Verify and synthesize
110
+
111
+ Treat CC output as untrusted advisory input:
112
+
113
+ 1. Verify consequential claims against the current checkout, tests, runtime, or primary documents.
114
+ 2. Separate agreement, useful dissent, and unsupported assertions.
115
+ 3. Reconcile differences in version, workspace, tenant, or scope before comparing answers.
116
+ 4. Return one Codex-owned conclusion with evidence and unresolved risks.
117
+
118
+ Do not call native Codex subagents through this skill; use Codex subagent orchestration for same-model
119
+ parallel work and reserve \`cc-collab\` for an explicitly requested cross-model perspective.
120
+ `;
121
+
122
+ const GEMINI_COLLAB_BODY = `# Gemini Collaboration for Codex
123
+
124
+ Use Gemini as an advisory second model. Codex remains responsible for scope, source verification,
125
+ decisions, and the final answer.
126
+
127
+ ## Preflight
128
+
129
+ 1. Confirm \`gemini\` is installed and capture \`gemini --version\`.
130
+ 2. Confirm the target workspace and requested scope.
131
+ 3. Form Codex's initial analysis before reading Gemini's answer when independence matters.
132
+
133
+ ## Invoke safely
134
+
135
+ Run Gemini in non-interactive, read-only plan mode:
136
+
137
+ \`\`\`bash
138
+ gemini --approval-mode plan --output-format text -p "<bounded prompt>"
139
+ \`\`\`
140
+
141
+ Use a timeout appropriate to the task. Redirect large output to a temporary file when needed, then
142
+ read only the relevant result. Do not use auto-edit or yolo modes. Do not place secrets, tokens,
143
+ unrelated files, or unbounded repository content in the prompt.
144
+
145
+ For repository analysis, give Gemini the exact files, diff range, question, and expected response
146
+ shape. If the CLI is missing, unauthenticated, times out, or returns empty output, report the degraded
147
+ path and continue with Codex evidence rather than blocking.
148
+
149
+ ## Verify and synthesize
150
+
151
+ Treat Gemini output as an untrusted analysis artifact:
152
+
153
+ 1. Check every consequential claim against the current checkout, runtime, or primary source.
154
+ 2. Separate agreements, useful dissent, and unsupported claims.
155
+ 3. Resolve scope or version mismatches before comparing conclusions.
156
+ 4. Present Codex's final judgment, not a transcript of two models.
157
+
158
+ Read the bundled references only when a mode or CLI detail is needed. Never let model agreement
159
+ substitute for tests, runtime evidence, or authoritative documentation.
160
+ `;
161
+
162
+ const ULTRA_VERIFY_BODY = String(function ultraVerifyBodyTemplate() { /*
163
+ # Ultra Verify — Codex-Primary Three-Way Verification
164
+
165
+ Codex owns the task, writes the first independent analysis, verifies every consequential claim,
166
+ and produces the final synthesis. Claude Code and Gemini are read-only external advisors. Use this
167
+ workflow only when the user asks for cross-model or three-way verification.
168
+
169
+ ## Modes
170
+
171
+ - `decision <question>`: compare architecture or product decisions.
172
+ - `diagnose <symptoms>`: collect independent root-cause hypotheses.
173
+ - `audit <scope>`: compare evidence-backed findings.
174
+ - `estimate <task>`: compare estimates and their assumptions.
175
+
176
+ ## Preconditions
177
+
178
+ 1. Confirm `claude --version` and `gemini --version`.
179
+ 2. Define the exact workspace, scope, evidence standard, and expected answer shape.
180
+ 3. Do not send secrets, unrelated files, or an unbounded home directory to either advisor.
181
+
182
+ ## Workflow
183
+
184
+ ### 1. Create the session and write Codex's independent view
185
+
186
+ ```bash
187
+ SESSION_ID="$(date +%Y%m%d-%H%M%S)-verify-<mode>"
188
+ SESSION_PATH=".ultra/collab/${SESSION_ID}"
189
+ mkdir -p "${SESSION_PATH}"
190
+ ```
191
+
192
+ Before invoking either advisor, write the evidence-backed Codex analysis to
193
+ `${SESSION_PATH}/codex-analysis.md`. This ordering is mandatory because it prevents the external
194
+ answers from priming the primary analysis.
195
+
196
+ ### 2. Launch Claude Code and Gemini concurrently
197
+
198
+ Give both advisors the same bounded raw question and evidence, without Codex's conclusions.
199
+
200
+ Claude Code:
201
+
202
+ ```bash
203
+ claude --safe-mode -p "<BOUNDED_PROMPT>" \\
204
+ --permission-mode plan \\
205
+ --tools "Read,Grep,Glob,Bash" \\
206
+ --output-format text \\
207
+ --no-session-persistence \\
208
+ > "${SESSION_PATH}/claude-output.md" \\
209
+ 2> "${SESSION_PATH}/claude-error.log"
210
+ ```
211
+
212
+ Gemini:
213
+
214
+ ```bash
215
+ gemini --approval-mode plan --output-format text -p "<BOUNDED_PROMPT>" \\
216
+ > "${SESSION_PATH}/gemini-output.md" \\
217
+ 2> "${SESSION_PATH}/gemini-error.log"
218
+ ```
219
+
220
+ Start the two commands with parallel `exec_command` calls and a short initial yield. If a command
221
+ returns a live session id, poll it with `write_stdin` in bounded intervals. Do not use YOLO,
222
+ auto-edit, permission bypass, or write-capable external tools.
223
+
224
+ ### 3. Collect only completed outputs
225
+
226
+ Wait for both CLI sessions to finish or reach their explicit timeout. The bundled
227
+ `scripts/verify_wait.py` can verify file stability for automation, but Codex must not perform one
228
+ blocking wait longer than 60 seconds. Read an output only when it is non-empty; retain the matching
229
+ error log as evidence when a CLI fails.
230
+
231
+ ### 4. Verify and synthesize
232
+
233
+ Compare `codex-analysis.md`, `claude-output.md`, and `gemini-output.md`:
234
+
235
+ 1. Verify claims against the current checkout, runtime, tests, or primary documentation.
236
+ 2. Separate consensus, majority views, useful dissent, and unsupported assertions.
237
+ 3. Explain scope, version, or assumption differences before scoring agreement.
238
+ 4. Write `${SESSION_PATH}/synthesis.md` and `metadata.json`, then present one Codex-owned answer.
239
+
240
+ Use the bundled confidence rules. Three agreeing, independently verified views are consensus; two
241
+ are a majority; three materially different answers are no consensus and require decomposition or
242
+ more evidence. Model agreement never overrides failing tests or authoritative runtime evidence.
243
+
244
+ ## Degraded operation
245
+
246
+ - One advisor fails: continue with Codex plus the available advisor and name the missing view.
247
+ - Both advisors fail: return Codex-only analysis with an explicit single-source warning.
248
+ - Never block the user's task solely because an external CLI is absent, unauthenticated, or slow.
249
+
250
+ ## Session files
251
+
252
+ ```text
253
+ .ultra/collab/<SESSION_ID>/
254
+ codex-analysis.md
255
+ claude-output.md
256
+ claude-error.log
257
+ gemini-output.md
258
+ gemini-error.log
259
+ metadata.json
260
+ synthesis.md
261
+ ```
262
+
263
+ Read the bundled references for mode-specific prompts, scoring, metadata, and automation details.
264
+ */ }).match(/\/\*([\s\S]*?)\*\//)[1].trim();
265
+
266
+ const ULTRA_VERIFY_FLOW = String(function ultraVerifyFlowTemplate() { /*
267
+ # Codex-Native Orchestration Flow
268
+
269
+ ## 1. Primary analysis
270
+
271
+ Create `.ultra/collab/<SESSION_ID>/` and write `codex-analysis.md` before reading any external
272
+ answer. Record the mode, scope, checkout, and evidence boundary.
273
+
274
+ ## 2. Parallel advisors
275
+
276
+ Launch Claude Code in `--safe-mode --permission-mode plan` and Gemini in
277
+ `--approval-mode plan` through separate `exec_command` calls. Redirect their output to
278
+ `claude-output.md` and `gemini-output.md`. If a call yields a session id, poll with
279
+ `write_stdin` in bounded intervals; do not grant either advisor mutation authority.
280
+
281
+ ## 3. Completion gate
282
+
283
+ Proceed only after both sessions have completed or timed out. For automation, run:
284
+
285
+ ```bash
286
+ python3 ~/plugins/ultra-builder-pro/skills/ultra-verify/scripts/verify_wait.py \\
287
+ "${SESSION_PATH}" --timeout 1200
288
+ ```
289
+
290
+ Run the waiter as a yielded exec session and poll it; never hold one blocking tool call for more
291
+ than 60 seconds. Its JSON reports `claude` and `gemini` as `complete`, `failed`, `empty`, or
292
+ `pending`.
293
+
294
+ ## 4. Synthesis
295
+
296
+ Read `codex-analysis.md` plus each completed advisor output. Verify consequential claims, compute
297
+ consensus using `confidence-system.md`, and write `synthesis.md` plus:
298
+
299
+ ```json
300
+ {
301
+ "id": "<SESSION_ID>",
302
+ "agent": "ultra-verify",
303
+ "mode": "<mode>",
304
+ "models": {"codex": "<primary>", "claude": "<advisor>", "gemini": "<advisor>"},
305
+ "scope": "<scope>",
306
+ "timestamp": "<ISO 8601>",
307
+ "confidence": "<consensus|majority|no_consensus>",
308
+ "degraded": false
309
+ }
310
+ ```
311
+
312
+ If one advisor fails, cap the result at majority and name the missing perspective. If both fail,
313
+ set `degraded: true`, `agents_responded: ["codex"]`, and mark the result single-source.
314
+ */ }).match(/\/\*([\s\S]*?)\*\//)[1].trim();
315
+
316
+ function titleCase(name) {
317
+ const special = {
318
+ 'ai-collab-base': 'AI Collaboration Base',
319
+ 'cc-collab': 'Claude Code Collaboration',
320
+ 'gemini-collab': 'Gemini Collaboration',
321
+ 'use-railway': 'Use Railway',
322
+ };
323
+ if (special[name]) return special[name];
324
+ return name.split('-').map((part) => part ? part[0].toUpperCase() + part.slice(1) : part).join(' ');
325
+ }
326
+
327
+ function replaceSlashCommand(text, command, replacement) {
328
+ const escaped = command.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
329
+ return text.replace(
330
+ new RegExp(`(^|[\\s\\x60(>])/${escaped}(?=$|[\\s\\x60,.;):])`, 'gm'),
331
+ (_match, prefix) => `${prefix}${replacement}`,
332
+ );
333
+ }
334
+
335
+ function adaptCodexPrimaryText(input, skillName) {
336
+ let text = String(input);
337
+ text = text.replaceAll('mcp__claude-in-chrome__*', 'the available Codex browser verification tools');
338
+ text = text.replaceAll('claude_md_rule', 'agents_md_rule');
339
+ text = text.replaceAll('claude-opus-4-6', '<codex-model>');
340
+ text = text.replaceAll('Claude', 'Codex');
341
+ text = text.replaceAll('TaskCreate/TaskUpdate', 'the Codex plan tool');
342
+ text = text.replaceAll('TaskCreate', 'the Codex plan tool');
343
+ text = text.replaceAll('TaskUpdate', 'the Codex plan tool');
344
+ text = text.replaceAll('TaskList', 'the current Codex plan');
345
+ text = text.replaceAll('TaskOutput', 'verbose subagent transcript output');
346
+ text = text.replaceAll('run_in_background: true', 'native parallel execution');
347
+ text = text.replaceAll('Bash tool', '`exec_command`');
348
+ text = text.replaceAll('Bash calls', '`exec_command` calls');
349
+ text = text.replaceAll('Read tool', 'targeted file reads');
350
+ text = text.replaceAll('Write tool', '`apply_patch`');
351
+ text = text.replaceAll('`ask.question`', 'a direct user question');
352
+ text = text.replaceAll('ask.question', 'ask the user directly');
353
+ text = text.replaceAll('Playwright via Bash', 'Playwright through `exec_command`');
354
+ text = text.replaceAll('mcp__context7__query-docs', 'official documentation lookup');
355
+ text = text.replaceAll('mcp__exa__web_search_exa', 'verified web search');
356
+ text = text.replaceAll('Context7 MCP', 'official primary documentation');
357
+ text = text.replaceAll('Context7/Exa', 'official primary documentation and verified web search');
358
+ text = text.replaceAll('Context7', 'official primary documentation');
359
+ text = text.replaceAll('Exa MCP', 'verified web search');
360
+ text = text.replaceAll('a direct user question (or runtime `ask the user directly`)', 'ask the user directly');
361
+ text = text.replaceAll('a direct user question (fallback: `ask the user directly` / CLI menu)', 'ask the user directly');
362
+ text = text.replaceAll('questions via ask the user directly.', 'questions directly to the user.');
363
+ text = text.replaceAll('using `the Codex plan tool`', 'with the Codex plan tool');
364
+ text = text.replaceAll("via the runtime's native the Codex plan tool", 'with the Codex plan tool when available');
365
+ text = text.replaceAll('`the Codex plan tool` → `status: "in_progress"`', 'Mark the current Codex plan item `in_progress`');
366
+ text = text.replaceAll('`the Codex plan tool` → `status: "completed"`', 'Mark the current Codex plan item `completed`');
367
+ text = text.replaceAll('`the current Codex plan` → resume from last incomplete step', 'Resume from the first incomplete item in the current Codex plan');
368
+ text = text.replaceAll('`the Codex plan tool`', 'the Codex plan tool');
369
+
370
+ if (skillName === 'ultra-init') {
371
+ text = text.replace(
372
+ /MCP 不可达时回退[^。]*。/,
373
+ 'MCP 不可达时只使用插件内置的可验证 CLI 初始化路径;不得手写投影文件。',
374
+ );
375
+ text = text.replace(
376
+ /- \*\*无 Codex 独占依赖\*\*[::][\s\S]*?(?=\n- \*\*模板内置)/,
377
+ '- **Codex-native boundary**: use the bundled `task.init_project` MCP tool; the plugin-local CLI below is only the verified initialization fallback.',
378
+ );
379
+ text = text.replace(
380
+ /在调 `task\.init_project` 之前,调用方(Codex \/ CLI \/ SDK)需要先判断:/,
381
+ '在调 `task.init_project` 之前,Codex 需要先判断:',
382
+ );
383
+ text = text.replace(
384
+ /- 如果调用方是 Codex,可在此用 the Codex plan tool 跟踪 Step 0–4 的 session 内进度\n (这是 runtime 的 session-local 跟踪,不走 MCP)/,
385
+ '- 多步初始化可用 Codex plan 跟踪;持久项目状态仍只通过 Ultra MCP 写入。',
386
+ );
387
+ text = text.replace(
388
+ /交互渠道:[\s\S]*?(?=\n确认 4 个问题:)/,
389
+ '交互渠道:直接向用户提问;不要调用未注册的人机交互 MCP tool。\n',
390
+ );
391
+ text = text.replace(
392
+ /\*\*CLI 回退路径\*\*[\s\S]*?(?=\n\*\*错误处理\*\*)/,
393
+ `**插件内置 CLI 回退路径**(只在 MCP 启动失败时使用):
394
+
395
+ \`\`\`bash
396
+ node ~/plugins/ultra-builder-pro/runtime/ultra-tools.cjs task init-project \\
397
+ --target-dir "$TARGET" \\
398
+ --project-name "$NAME" \\
399
+ --project-type "$TYPE" \\
400
+ --stack "$STACK"
401
+ \`\`\`
402
+
403
+ 该 CLI 与 MCP 使用同一模板和校验规则。不得改写 \`.ultra/tasks/tasks.json\` 来模拟成功。
404
+ `,
405
+ );
406
+ text = text.replace(
407
+ /## 调用方式(按 runtime)[\s\S]*?(?=\n## 输出锚点)/,
408
+ '## Codex 调用方式\n\n在 Codex 中显式调用 `$ultra-builder-pro:ultra-init`,并在同一条任务消息中提供项目名、类型、技术栈或 git 约束。\n',
409
+ );
410
+ text = text.replace(
411
+ /- \*\*不\*\*写入 state\.db(state\.db 由第一次 MCP 写操作或显式 `ultra-tools db init` 触发)/,
412
+ '- **不**直接写入 state.db;它由第一次 Ultra MCP 状态写操作初始化。',
413
+ );
414
+ text = text.replaceAll('不再依赖 `~/.codex/.ultra-template/`', '不依赖任何用户目录模板');
415
+ }
416
+
417
+ if (skillName === 'ultra-plan') {
418
+ text = text.replace(
419
+ /Interactive prompt uses[\s\S]*?(?=\n\n\*\*Dual-scale effort)/,
420
+ 'Ask the user directly in Codex and record the selected posture.',
421
+ );
422
+ text = text.replace(
423
+ /\*\*CLI fallback\*\* \(per task\):[\s\S]*?(?=\nAfter each `task\.create`)/,
424
+ '**Failure boundary**: if the bundled MCP cannot execute `task.create`, stop and report the MCP error. Do not write state.db or its JSON/Markdown projections directly.\n\n',
425
+ );
426
+ text = text.replace(
427
+ /## MCP → CLI fallback matrix[\s\S]*?(?=\n## What this skill DOES NOT do)/,
428
+ `## Codex runtime boundary
429
+
430
+ - Ask scope and approval questions directly in the Codex conversation.
431
+ - Persist tasks only through bundled MCP \`task.create\` or \`task.parse_prd\`.
432
+ - If that MCP is unavailable, stop the state mutation with evidence; the legacy CLI does not
433
+ implement task creation and is not a valid fallback.
434
+ `,
435
+ );
436
+ }
437
+
438
+ if (skillName === 'ultra-dev') {
439
+ text = text.replace(
440
+ /## Design decisions vs pre-Phase-3[\s\S]*?(?=\n## Prerequisites)/,
441
+ `## Codex-native execution boundaries
442
+
443
+ - Status changes use bundled MCP \`task.update\`; projected JSON and frontmatter are read-only.
444
+ - Review uses \`$ultra-builder-pro:ultra-review\` and the installed native Codex custom agents.
445
+ The active MCP server does not expose a server-side review worker.
446
+ - Recovery uses the Codex plan plus \`.ultra/workflow-state.json\`, with the bundled compact hooks.
447
+ The active MCP server does not expose a checkpoint tool, and no manual compact is required.
448
+ `,
449
+ );
450
+ text = text.replaceAll(
451
+ '**CLI fallback**: `ultra-tools task update <id> --status in_progress`.',
452
+ '**Failure boundary**: if `task.update` is unavailable, stop; do not edit state.db or projections directly.',
453
+ );
454
+ text = text.replaceAll(
455
+ '**CLI fallback**: `ultra-tools task update <id> --status completed`.',
456
+ '**Failure boundary**: if `task.update` is unavailable, stop; do not mark the projection completed manually.',
457
+ );
458
+ text = text.replace(
459
+ /\*\*Subagent isolation \(complexity ≥ 7\)\*\*:[\s\S]*?subagent returns summary\./,
460
+ '**Subagent isolation (complexity ≥ 7)**: use a bounded installed Codex custom agent only when the Subagent Policy or this invoked workflow authorizes delegation; the parent retains decisions and verification.',
461
+ );
462
+ text = text.replace(
463
+ /### Step 4\.4 — Pre-Review Checkpoint[\s\S]*?(?=\n### Step 4\.5)/,
464
+ `### Step 4.4 — Pre-Review Checkpoint
465
+
466
+ Atomically write \`.ultra/workflow-state.json\` with \`step=4.5\`,
467
+ \`status=pre_review\`, the task id, branch, and timestamp. Mark the matching Codex plan item
468
+ complete. The PreCompact/PostCompact hooks preserve and restore this checkpoint automatically;
469
+ do not require the user to compact or call a nonexistent checkpoint tool.
470
+ `,
471
+ );
472
+ text = text.replace(
473
+ /### Step 4\.5 — Ultra Review \(MANDATORY\)[\s\S]*?(?=\n\*\*MAX_REVIEW_ITERATIONS)/,
474
+ `### Step 4.5 — Ultra Review (MANDATORY)
475
+
476
+ Invoke \`$ultra-builder-pro:ultra-review all\`. That skill selects and dispatches the installed
477
+ native Codex review agents, waits on their structured files, and owns \`SUMMARY.json\`. Do not call
478
+ an unregistered review MCP tool or shell out to another Codex process as a substitute.
479
+
480
+ `,
481
+ );
482
+ text = text.replace(
483
+ /## MCP → CLI fallback matrix[\s\S]*?(?=\n## What this skill DOES NOT do)/,
484
+ `## Codex runtime boundary
485
+
486
+ | Purpose | Authoritative Codex path |
487
+ |---------|--------------------------|
488
+ | Select/read/update task | bundled MCP \`task.list\`, \`task.get\`, \`task.update\` |
489
+ | Pre-review recovery | Codex plan + \`.ultra/workflow-state.json\` + compact hooks |
490
+ | Review | \`$ultra-builder-pro:ultra-review\` + native custom agents |
491
+ | Human decision | ask the user directly |
492
+
493
+ If a required task MCP operation fails, stop and report it; never fall back to direct database or
494
+ projection writes.
495
+ `,
496
+ );
497
+ text = text.replace(
498
+ /- Does NOT assume `review\.run` or `session\.checkpoint` are wired — fallbacks are first-class/,
499
+ '- Does NOT call unregistered review or checkpoint MCP tools.',
500
+ );
501
+ }
502
+
503
+ if (skillName === 'ultra-test') {
504
+ text = text.replace(
505
+ /## MCP → CLI fallback matrix[\s\S]*?(?=\n## What this skill DOES NOT do)/,
506
+ `## Codex runtime boundary
507
+
508
+ - Read task completion through bundled MCP \`task.list\`.
509
+ - Ask before risky auto-fixes directly in the Codex conversation.
510
+ - If task state is unavailable, report the missing gate rather than trusting a projection alone.
511
+ `,
512
+ );
513
+ }
514
+
515
+ if (skillName === 'ultra-deliver') {
516
+ text = text.replace(
517
+ /## MCP → CLI fallback matrix[\s\S]*?(?=\n## What this skill DOES NOT do)/,
518
+ `## Codex runtime boundary
519
+
520
+ Ask the user directly before committing, tagging, pushing, or overriding the version bump. These are
521
+ external repository mutations; never infer approval from a prior read-only or test request.
522
+ `,
523
+ );
524
+ }
525
+
526
+ if (skillName === 'ultra-status') {
527
+ text = text.replace(
528
+ /\*\*CLI fallback\*\*: `ultra-tools task list` \+ `ultra-tools session list`\./,
529
+ '**Failure boundary**: if the bundled task/session MCP tools are unavailable, report those panels as unavailable; do not trust projections as authority.',
530
+ );
531
+ text = text.replace(
532
+ /\*\*Output block\*\*:[\s\S]*?(?=\n## Single-task mode)/,
533
+ `**Output block**: name the concrete next skill and use its namespaced Codex invocation, for
534
+ example \`$ultra-builder-pro:ultra-dev\`. If a fresh context would help, recommend starting a new
535
+ Codex task; do not emit a legacy slash-command placeholder.
536
+
537
+ `,
538
+ );
539
+ text = text.replace(
540
+ /## MCP → CLI fallback matrix[\s\S]*?(?=\n## Cost panel)/,
541
+ `## Codex data paths
542
+
543
+ | Purpose | Path |
544
+ |---------|------|
545
+ | List/detail tasks | bundled MCP \`task.list\` / \`task.get\` |
546
+ | Cost panel | \`node ~/plugins/ultra-builder-pro/runtime/ultra-tools.cjs status --cost --json --since 7d\` |
547
+
548
+ Task projections are not an authority fallback. If the MCP is unavailable, label the task panel
549
+ unavailable while still reporting file and git evidence.
550
+ `,
551
+ );
552
+ text = text.replaceAll(
553
+ '`ultra-tools status --cost --json`',
554
+ '`node ~/plugins/ultra-builder-pro/runtime/ultra-tools.cjs status --cost --json`',
555
+ );
556
+ }
557
+
558
+ if (skillName === 'ultra-think') {
559
+ text = text.replace(
560
+ /Every factual claim about tech\/API\/best-practices must be verified via:[\s\S]*?(?=\n\nLabel each assertion:)/,
561
+ `Every factual claim about technology, APIs, or current behavior must use the best available
562
+ primary evidence in this order:
563
+ - current repository or installed source;
564
+ - official primary documentation;
565
+ - web search when the fact may have changed or local evidence is insufficient.
566
+ `,
567
+ );
568
+ text = text.replaceAll(
569
+ 'docs via Context7/Exa',
570
+ 'official primary documentation or verified web sources',
571
+ );
572
+ text = text.replace(
573
+ /## MCP → CLI fallback matrix[\s\S]*?(?=\n## What this skill DOES NOT do)/,
574
+ `## Codex interaction boundary
575
+
576
+ Ask clarifying questions directly in the Codex conversation. This reasoning workflow is read-only
577
+ and does not require an interactive MCP or legacy CLI menu.
578
+ `,
579
+ );
580
+ }
581
+
582
+ if (skillName === 'ultra-review') {
583
+ text = text.replaceAll('### Phase 3: Background Execution', '### Phase 3: Parallel Native Agent Execution');
584
+ text = text.replaceAll('**Step 4b: Launch coordinator in background:**', '**Step 4b: Delegate coordination:**');
585
+ text = text.replaceAll('in **background mode** (`native parallel execution`)', 'concurrently with native Codex subagent orchestration');
586
+ text = text.replaceAll('using multiple native Codex subagent orchestration calls in a single message', 'using the installed native Codex custom agents');
587
+ text = text.replaceAll('Set `native parallel execution` on every Task call.', 'Dispatch every selected custom agent concurrently and keep each delegated scope bounded.');
588
+ text = text.replaceAll('NEVER call verbose subagent transcript output for any review agent', 'Do not copy verbose subagent transcripts into the parent context');
589
+ text = text.replaceAll('Call `review_wait.py` IMMEDIATELY — do NOT process idle notifications', 'Use `review_wait.py` as the file-completion gate before synthesis');
590
+ text = text.replaceAll('Ignore all agent idle/completion messages between launch and wait script return', 'Treat completion messages only as lifecycle evidence; findings still come from the JSON files');
591
+ text = text.replaceAll('Launch review-coordinator with `native parallel execution`:', 'After reviewer completion, delegate coordination to the native `review-coordinator` agent:');
592
+ text = text.replaceAll('Use Bash to block until all agents finish writing:', 'Run the waiter through `exec_command` with a short yield; if it returns a live session, poll in bounded intervals:');
593
+ text = text.replaceAll('After launching background agents', 'After dispatching the review agents');
594
+ text = text.replaceAll('The review agents write to files; you read from files.', 'The review agents write findings to files; the parent reads only the structured files and concise lifecycle acknowledgements.');
595
+ text = text.replaceAll('The ONLY information path from agents is: wait script → Read SUMMARY.json', 'The authoritative finding path is: wait script → inspect SUMMARY.json');
596
+ text = text.replaceAll('**Read SUMMARY.json**', '**Inspect SUMMARY.json**');
597
+ }
598
+
599
+ return text;
600
+ }
601
+
602
+ function adaptUltraVerifyAsset(input, rel) {
603
+ if (rel === path.join('references', 'orchestration-flow.md')) return ULTRA_VERIFY_FLOW;
604
+
605
+ let text = adaptHostText(String(input), '');
606
+ if (rel === path.join('scripts', 'verify_wait.py')) {
607
+ text = text.replaceAll('Codex', 'Claude Code');
608
+ text = text.replaceAll('codex', 'claude');
609
+ text = text.replaceAll('run_in_background', 'a yielded exec session');
610
+ return text;
611
+ }
612
+
613
+ text = text.replaceAll('Claude', '__UBP_CODEX_PRIMARY__');
614
+ text = text.replaceAll('Codex', 'Claude Code');
615
+ text = text.replaceAll('__UBP_CODEX_PRIMARY__', 'Codex');
616
+ text = text.replaceAll('claude-analysis.md', 'codex-analysis.md');
617
+ text = text.replaceAll('codex-output.md', 'claude-output.md');
618
+ text = text.replaceAll('codex-error.log', 'claude-error.log');
619
+ text = text.replaceAll('codex-review', 'claude-review');
620
+ text = text.replaceAll('"claude": "claude-opus-4-6"', '"codex": "<primary>"');
621
+ text = text.replaceAll('"codex": "<model>"', '"claude": "<advisor>"');
622
+ text = text.replaceAll('["claude"]', '["codex"]');
623
+ text = text.replaceAll('Claude Code-only', 'Codex-only');
624
+ text = text.replaceAll('Gemini and Claude Code in parallel (native parallel execution)', 'Claude Code and Gemini concurrently');
625
+ text = text.replaceAll('run_in_background', 'native concurrent execution');
626
+ text = text.replaceAll('Bash tool', '`exec_command`');
627
+ text = text.replaceAll('Read tool', 'targeted file reads');
628
+ return text;
629
+ }
630
+
631
+ function adaptSkillAsset(input, targetName, rel) {
632
+ if (targetName === 'ultra-verify') return adaptUltraVerifyAsset(input, rel);
633
+ return adaptHostText(input, targetName);
634
+ }
635
+
636
+ function adaptHostText(input, skillName = '') {
637
+ let text = String(input);
638
+ text = text.replaceAll('codex-collab', 'cc-collab');
639
+ text = text.replaceAll('CLAUDE.md', 'AGENTS.md');
640
+ text = text.replaceAll('$CLAUDE_PLUGIN_ROOT/skills', '~/plugins/ultra-builder-pro/skills');
641
+ text = text.replaceAll('~/.claude/skills', '~/plugins/ultra-builder-pro/skills');
642
+ text = text.replaceAll('~/.codex/skills', '~/plugins/ultra-builder-pro/skills');
643
+ text = text.replaceAll('~/.claude/hooks', '~/plugins/ultra-builder-pro/hooks');
644
+ text = text.replaceAll('~/.claude', '~/.codex');
645
+ text = text.replaceAll('AskUserQuestion', 'ask the user directly');
646
+ text = text.replaceAll('Claude runtime', 'Codex runtime');
647
+ text = text.replaceAll('Claude-only', 'Codex-only');
648
+ text = text.replaceAll('Claude Task tool', 'Codex native subagent orchestration');
649
+ text = text.replaceAll('Claude `Task` tool', 'Codex native subagent orchestration');
650
+ text = text.replaceAll('Claude: `Task`', 'Codex: native subagent orchestration');
651
+ text = text.replaceAll('Task tool', 'native Codex subagent orchestration');
652
+ text = text.replaceAll('`Task`/`ultra-tools subagent run`', 'native Codex subagent orchestration / `ultra-tools subagent run`');
653
+ text = text.replaceAll('`Task` →', 'native Codex subagent orchestration →');
654
+
655
+ for (const skill of SKILL_REFERENCE_NAMES) {
656
+ text = replaceSlashCommand(text, skill, `$ultra-builder-pro:${skill}`);
657
+ }
658
+ text = replaceSlashCommand(text, 'clear', 'start a new Codex task');
659
+
660
+ if (CODEX_PRIMARY_SKILLS.has(skillName)) {
661
+ text = adaptCodexPrimaryText(text, skillName);
662
+ }
663
+
664
+ if (skillName === 'gemini-collab' || skillName === 'ai-collab-base') {
665
+ text = text.replaceAll("Claude's", "Codex's");
666
+ text = text.replaceAll('Claude-only', 'Codex-only');
667
+ text = text.replaceAll('Claude ', 'Codex ');
668
+ text = text.replaceAll('Claude\n', 'Codex\n');
669
+ text = text.replaceAll('--approval-mode yolo', '--approval-mode plan');
670
+ text = text.replaceAll('--yolo', '--approval-mode plan');
671
+ text = text.replaceAll('Bash tool', '`exec_command`');
672
+ text = text.replaceAll('Read tool', 'targeted file reads');
673
+ text = text.replaceAll('Write tool', '`apply_patch`');
674
+ }
675
+
676
+ if (skillName === 'learn') {
677
+ text = text.replaceAll(
678
+ '~/plugins/ultra-builder-pro/skills/learned-<name>-unverified/SKILL.md',
679
+ '~/.agents/skills/learned-<name>-unverified/SKILL.md',
680
+ );
681
+ text = text.replaceAll(
682
+ '~/plugins/ultra-builder-pro/skills/learned-<pattern-slug>-unverified/SKILL.md',
683
+ '~/.agents/skills/learned-<pattern-slug>-unverified/SKILL.md',
684
+ );
685
+ text = text.replaceAll(
686
+ '~/plugins/ultra-builder-pro/skills/learned-<slug>-unverified/SKILL.md',
687
+ '~/.agents/skills/learned-<slug>-unverified/SKILL.md',
688
+ );
689
+ text = text.replaceAll(
690
+ '~/plugins/ultra-builder-pro/skills/learned/<name>_unverified.md',
691
+ '~/.agents/skills/learned-<name>-unverified/SKILL.md',
692
+ );
693
+ text = text.replaceAll(
694
+ '~/plugins/ultra-builder-pro/skills/learned/<pattern-slug>_unverified.md',
695
+ '~/.agents/skills/learned-<pattern-slug>-unverified/SKILL.md',
696
+ );
697
+ text = text.replaceAll(
698
+ '~/plugins/ultra-builder-pro/skills/learned/<slug>_unverified.md',
699
+ '~/.agents/skills/learned-<slug>-unverified/SKILL.md',
700
+ );
701
+ text = text.replaceAll('append the `_unverified` suffix to the filename', 'append `-unverified` to the skill directory name');
702
+ text = text.replaceAll('Never overwrite an existing unverified file', 'Never overwrite an existing learned skill directory');
703
+ text = text.replaceAll('remove the `_unverified` suffix', 'rename the directory to remove `-unverified` and update the frontmatter name');
704
+ text = text.replaceAll('(`pattern-slug-2_unverified.md`)', '(`learned-pattern-slug-2-unverified/`)');
705
+ text = text.replaceAll('Anything the user already wrote in AGENTS.md or similar project instructions', 'Anything already captured in AGENTS.md or equivalent project instructions');
706
+ text = text.replace(
707
+ /## MCP → CLI fallback matrix[\s\S]*?(?=\n## What this skill DOES NOT do)/,
708
+ '## Codex approval boundary\n\nAsk the user directly before writing a learned skill. No MCP or legacy CLI interaction layer is required.\n',
709
+ );
710
+ 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`;
711
+ }
712
+
713
+ return text;
714
+ }
715
+
716
+ function adaptedDescription(sourceDescription, targetName) {
717
+ const special = {
718
+ '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.',
719
+ 'gemini-collab': 'Ask Gemini CLI for an independent read-only analysis while Codex verifies and synthesizes. Use only when the user explicitly requests Gemini collaboration.',
720
+ 'ultra-verify': 'Run Codex-primary three-way verification with read-only Claude Code and Gemini advisors, then verify and synthesize the evidence.',
721
+ 'ultra-dev': 'Execute one Ultra task with Codex-native TDD, persistent task.update state, workflow checkpoints, and the native Ultra review agents.',
722
+ 'learn': 'Extract one reusable pattern from the current Codex task into a valid user skill, with explicit user approval before writing.',
723
+ };
724
+ return special[targetName] || adaptHostText(String(sourceDescription || `${titleCase(targetName)} workflow for Codex.`), targetName)
725
+ .replace(/\s+/g, ' ')
726
+ .trim();
727
+ }
728
+
729
+ function buildSkillMarkdown(sourceText, sourceName, targetName) {
730
+ const { fm, body } = parseFrontmatter(sourceText);
731
+ let adaptedBody;
732
+ if (targetName === 'cc-collab') adaptedBody = CC_COLLAB_BODY;
733
+ else if (targetName === 'gemini-collab') adaptedBody = GEMINI_COLLAB_BODY;
734
+ else if (targetName === 'ultra-verify') adaptedBody = ULTRA_VERIFY_BODY;
735
+ else adaptedBody = adaptHostText(body, targetName);
736
+ return yaml.dump({
737
+ name: targetName,
738
+ description: adaptedDescription(fm && fm.description, targetName),
739
+ }, { lineWidth: -1, noRefs: true }).trimEnd()
740
+ .replace(/^/, '---\n') + `\n---\n\n${adaptedBody.trim()}\n`;
741
+ }
742
+
743
+ function buildOpenAiYaml(name, description) {
744
+ const implicit = !(
745
+ COMMAND_NAMES.includes(name)
746
+ || INTERNAL_SKILLS.has(name)
747
+ || name === 'cc-collab'
748
+ || name === 'gemini-collab'
749
+ || name.startsWith('ultra-')
750
+ );
751
+ const short = description.replace(/\s+/g, ' ').trim().slice(0, 63).replace(/[\s.,;:]+$/, '');
752
+ return yaml.dump({
753
+ interface: {
754
+ display_name: titleCase(name),
755
+ short_description: short || `${titleCase(name)} for Codex`,
756
+ default_prompt: `Use $ultra-builder-pro:${name} for this task and follow its workflow.`,
757
+ },
758
+ policy: { allow_implicit_invocation: implicit },
759
+ }, { lineWidth: -1, noRefs: true });
760
+ }
761
+
762
+ function isTextFile(rel) {
763
+ return TEXT_EXTENSIONS.has(path.extname(rel).toLowerCase());
764
+ }
765
+
766
+ function copySkill(sourceDir, targetDir, sourceName, targetName) {
767
+ if (targetName === 'cc-collab') {
768
+ ensureDir(targetDir);
769
+ const sourceText = fs.readFileSync(path.join(sourceDir, 'SKILL.md'), 'utf8');
770
+ const skillText = buildSkillMarkdown(sourceText, sourceName, targetName);
771
+ writeAtomic(path.join(targetDir, 'SKILL.md'), skillText);
772
+ const description = parseFrontmatter(skillText).fm.description;
773
+ writeAtomic(path.join(targetDir, 'agents', 'openai.yaml'), buildOpenAiYaml(targetName, description));
774
+ return;
775
+ }
776
+
777
+ copyTree(sourceDir, targetDir, {
778
+ transform(original, rel) {
779
+ if (rel === path.join('agents', 'openai.yaml')) return original;
780
+ if (rel === 'SKILL.md') {
781
+ return Buffer.from(buildSkillMarkdown(original.toString('utf8'), sourceName, targetName));
782
+ }
783
+ if (!isTextFile(rel)) return original;
784
+ return Buffer.from(adaptSkillAsset(original.toString('utf8'), targetName, rel));
785
+ },
786
+ });
787
+ const skillText = fs.readFileSync(path.join(targetDir, 'SKILL.md'), 'utf8');
788
+ const description = parseFrontmatter(skillText).fm.description;
789
+ writeAtomic(path.join(targetDir, 'agents', 'openai.yaml'), buildOpenAiYaml(targetName, description));
790
+ }
791
+
792
+ function buildCommandMap() {
793
+ return Object.fromEntries(COMMAND_NAMES.map((name) => [`/${name}`, `$ultra-builder-pro:${name}`]));
794
+ }
795
+
796
+ function hookCommand(feature, ...args) {
797
+ return [
798
+ 'python3 "$PLUGIN_ROOT/hooks/adapters/codex.py"',
799
+ JSON.stringify(feature),
800
+ ...args.map((arg) => JSON.stringify(arg)),
801
+ ].join(' ');
802
+ }
803
+
804
+ function commandHook(feature, timeout, statusMessage, ...args) {
805
+ return {
806
+ type: 'command',
807
+ command: hookCommand(feature, ...args),
808
+ timeout,
809
+ statusMessage,
810
+ };
811
+ }
812
+
813
+ function buildHooksManifest() {
814
+ return {
815
+ hooks: {
816
+ SessionStart: [
817
+ { hooks: [commandHook('health_check.py', 5, 'Checking Ultra runtime')] },
818
+ { hooks: [commandHook('workflow_context.py', 10, 'Loading active Ultra workflow')] },
819
+ ],
820
+ PreToolUse: [
821
+ { matcher: 'Edit|Write', hooks: [commandHook('active_task_context.py', 3, 'Checking active Ultra task')] },
822
+ ],
823
+ PreCompact: [
824
+ { matcher: 'manual|auto', hooks: [commandHook('workflow_checkpoint.py', 10, 'Saving Ultra workflow checkpoint')] },
825
+ ],
826
+ PostCompact: [
827
+ { hooks: [commandHook('workflow_resume.py', 10, 'Restoring Ultra workflow checkpoint')] },
828
+ ],
829
+ Stop: [
830
+ { hooks: [commandHook('pre_stop_check.py', 5, 'Checking Ultra completion gates')] },
831
+ ],
832
+ SubagentStart: [
833
+ { hooks: [commandHook('subagent_tracker.py', 5, 'Tracking Ultra subagent', 'start')] },
834
+ ],
835
+ SubagentStop: [
836
+ { hooks: [commandHook('subagent_tracker.py', 5, 'Tracking Ultra subagent', 'stop')] },
837
+ ],
838
+ },
839
+ };
840
+ }
841
+
842
+ function copyHooks(repoRoot, pluginRoot) {
843
+ const sourceRoot = path.join(repoRoot, 'hooks');
844
+ const targetRoot = path.join(pluginRoot, 'hooks');
845
+ ensureDir(targetRoot);
846
+ for (const name of WORKFLOW_HOOK_FILES) {
847
+ fs.copyFileSync(path.join(sourceRoot, name), path.join(targetRoot, name));
848
+ }
849
+ ensureDir(path.join(targetRoot, 'adapters'));
850
+ fs.copyFileSync(
851
+ path.join(sourceRoot, 'adapters', 'codex.py'),
852
+ path.join(targetRoot, 'adapters', 'codex.py'),
853
+ );
854
+ writeAtomic(path.join(targetRoot, 'hooks.json'), JSON.stringify(buildHooksManifest(), null, 2) + '\n');
855
+ }
856
+
857
+ function buildMcpRuntime(repoRoot, pluginRoot, { runtime = 'codex' } = {}) {
858
+ const source = path.join(repoRoot, 'mcp-server', 'server.cjs');
859
+ const cliSource = path.join(repoRoot, 'adapters', '_shared', 'codex-ultra-tools-entry.cjs');
860
+ const runtimeRoot = path.join(pluginRoot, 'runtime');
861
+ const buildRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-codex-mcp-build-'));
862
+ let nccCli;
863
+ try {
864
+ nccCli = require.resolve('@vercel/ncc/dist/ncc/cli.js');
865
+ } catch (error) {
866
+ throw new Error(`Ultra MCP bundling requires @vercel/ncc: ${error.message}`);
867
+ }
868
+
869
+ try {
870
+ const bundled = spawnSync(process.execPath, [
871
+ nccCli,
872
+ 'build',
873
+ source,
874
+ '-o',
875
+ buildRoot,
876
+ '--no-cache',
877
+ '--quiet',
878
+ ], {
879
+ cwd: repoRoot,
880
+ encoding: 'utf8',
881
+ maxBuffer: 16 * 1024 * 1024,
882
+ });
883
+ if (bundled.error) throw bundled.error;
884
+ if (bundled.status !== 0) {
885
+ const detail = (bundled.stderr || bundled.stdout || '').trim();
886
+ throw new Error(`ncc failed to bundle the Ultra MCP runtime${detail ? `: ${detail}` : ''}`);
887
+ }
888
+ copyTree(buildRoot, runtimeRoot);
889
+ } finally {
890
+ removeTree(buildRoot);
891
+ }
892
+
893
+ const cliBuildRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-codex-cli-build-'));
894
+ try {
895
+ const bundled = spawnSync(process.execPath, [
896
+ nccCli,
897
+ 'build',
898
+ cliSource,
899
+ '-o',
900
+ cliBuildRoot,
901
+ '--no-cache',
902
+ '--quiet',
903
+ ], {
904
+ cwd: repoRoot,
905
+ encoding: 'utf8',
906
+ maxBuffer: 16 * 1024 * 1024,
907
+ });
908
+ if (bundled.error) throw bundled.error;
909
+ if (bundled.status !== 0) {
910
+ const detail = (bundled.stderr || bundled.stdout || '').trim();
911
+ throw new Error(`ncc failed to bundle the Ultra fallback CLI${detail ? `: ${detail}` : ''}`);
912
+ }
913
+ for (const rel of listRelative(cliBuildRoot)) {
914
+ const target = rel === 'index.cjs'
915
+ ? path.join(runtimeRoot, 'ultra-tools.cjs')
916
+ : path.join(runtimeRoot, rel);
917
+ ensureDir(path.dirname(target));
918
+ fs.copyFileSync(path.join(cliBuildRoot, rel), target);
919
+ }
920
+ } finally {
921
+ removeTree(cliBuildRoot);
922
+ }
923
+
924
+ const launcher = `'use strict';
925
+
926
+ const path = require('node:path');
927
+
928
+ process.env.UBP_RUNTIME_ROOT = path.resolve(__dirname, '..');
929
+
930
+ const { main } = require('./index.cjs');
931
+
932
+ main().catch((error) => {
933
+ process.stderr.write(\`mcp-server fatal: \${error.message}\\n\`);
934
+ process.exit(1);
935
+ });
936
+ `;
937
+ writeAtomic(path.join(runtimeRoot, 'launch.cjs'), launcher);
938
+
939
+ const sourceToolsFile = path.join(repoRoot, 'spec', 'mcp-tools.yaml');
940
+ const upstreamManifest = yaml.load(fs.readFileSync(sourceToolsFile, 'utf8'));
941
+ const { REGISTERED_TOOLS } = require(path.join(repoRoot, 'mcp-server', 'server.cjs'));
942
+ const registered = new Set(REGISTERED_TOOLS);
943
+ const liveFamilies = new Set(
944
+ upstreamManifest.tools.filter((tool) => registered.has(tool.name)).map((tool) => tool.family),
945
+ );
946
+ const liveManifest = {
947
+ ...upstreamManifest,
948
+ info: {
949
+ ...upstreamManifest.info,
950
+ notes: `${upstreamManifest.info.notes.trim()}\n\n${runtime} bundled runtime: only tools registered by the MCP server are listed here.`,
951
+ },
952
+ families: upstreamManifest.families.filter((family) => liveFamilies.has(family.name)),
953
+ tools: upstreamManifest.tools.filter((tool) => registered.has(tool.name)),
954
+ };
955
+ const specRoot = path.join(pluginRoot, 'spec');
956
+ ensureDir(specRoot);
957
+ writeAtomic(path.join(specRoot, 'mcp-tools.yaml'), yaml.dump(liveManifest, { lineWidth: -1, noRefs: true }));
958
+ if (runtime === 'codex') {
959
+ fs.copyFileSync(sourceToolsFile, path.join(specRoot, 'upstream-mcp-tools.yaml'));
960
+ writeAtomic(path.join(specRoot, 'codex-capability-map.json'), JSON.stringify({
961
+ runtime: 'codex',
962
+ live_mcp_tools: REGISTERED_TOOLS,
963
+ codex_native_replacements: CODEX_NATIVE_MCP_REPLACEMENTS,
964
+ }, null, 2) + '\n');
965
+ }
966
+ const sourceSchema = path.join(repoRoot, 'spec', 'schemas', 'state-db.sql');
967
+ const targetSchema = path.join(specRoot, 'schemas', 'state-db.sql');
968
+ ensureDir(path.dirname(targetSchema));
969
+ fs.copyFileSync(sourceSchema, targetSchema);
970
+
971
+ const preferredTemplate = path.join(repoRoot, 'templates', '.ultra');
972
+ const packagedTemplate = path.join(repoRoot, '.ultra-template');
973
+ const templateRoot = fs.existsSync(preferredTemplate) ? preferredTemplate : packagedTemplate;
974
+ if (!fs.existsSync(templateRoot)) {
975
+ throw new Error(`Ultra project template missing from ${repoRoot}`);
976
+ }
977
+ copyTree(templateRoot, path.join(pluginRoot, 'templates', '.ultra'));
978
+
979
+ return {
980
+ launcher: path.join(runtimeRoot, 'launch.cjs'),
981
+ bundle: path.join(runtimeRoot, 'index.cjs'),
982
+ ultraTools: path.join(runtimeRoot, 'ultra-tools.cjs'),
983
+ };
984
+ }
985
+
986
+ function pluginContentHash(pluginRoot, baseVersion) {
987
+ const hash = crypto.createHash('sha256');
988
+ hash.update(baseVersion);
989
+ for (const rel of listRelative(pluginRoot)) {
990
+ if (rel === path.join('.codex-plugin', 'plugin.json') || rel === '.ubp-managed') continue;
991
+ hash.update(rel);
992
+ hash.update(fs.readFileSync(path.join(pluginRoot, rel)));
993
+ }
994
+ return hash.digest('hex').slice(0, 12);
995
+ }
996
+
997
+ function buildPlugin({ repoRoot, pluginRoot }) {
998
+ if (fs.existsSync(pluginRoot)) {
999
+ const marker = path.join(pluginRoot, '.ubp-managed');
1000
+ if (!fs.existsSync(marker)) {
1001
+ throw new Error(`refusing to replace unmanaged plugin directory: ${pluginRoot}`);
1002
+ }
1003
+ removeTree(pluginRoot);
1004
+ }
1005
+ ensureDir(pluginRoot);
1006
+
1007
+ const installedSkills = [];
1008
+ for (const name of skillsForRuntime('codex')) {
1009
+ const sourceDir = path.join(repoRoot, 'skills', name);
1010
+ if (!fs.existsSync(path.join(sourceDir, 'SKILL.md'))) {
1011
+ throw new Error(`missing allowlisted Codex skill: ${name}`);
1012
+ }
1013
+ copySkill(sourceDir, path.join(pluginRoot, 'skills', name), name, name);
1014
+ installedSkills.push(name);
1015
+ }
1016
+
1017
+ copyHooks(repoRoot, pluginRoot);
1018
+ const mcpRuntime = buildMcpRuntime(repoRoot, pluginRoot);
1019
+ writeAtomic(path.join(pluginRoot, '.mcp.json'), JSON.stringify({
1020
+ mcpServers: {
1021
+ [PLUGIN_NAME]: {
1022
+ type: 'stdio',
1023
+ command: process.execPath,
1024
+ args: [mcpRuntime.launcher],
1025
+ },
1026
+ },
1027
+ }, null, 2) + '\n');
1028
+ writeAtomic(path.join(pluginRoot, 'command-map.json'), JSON.stringify(buildCommandMap(), null, 2) + '\n');
1029
+ if (fs.existsSync(path.join(repoRoot, 'LICENSE'))) {
1030
+ fs.copyFileSync(path.join(repoRoot, 'LICENSE'), path.join(pluginRoot, 'LICENSE'));
1031
+ }
1032
+
1033
+ const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
1034
+ const version = `${pkg.version}+codex.${pluginContentHash(pluginRoot, pkg.version)}`;
1035
+ const manifest = {
1036
+ name: PLUGIN_NAME,
1037
+ version,
1038
+ description: 'Codex-native Ultra Builder Pro workflows, agents, hooks, and MCP task state.',
1039
+ author: { name: typeof pkg.author === 'string' ? pkg.author : 'Ultra Builder Pro contributors' },
1040
+ homepage: pkg.homepage,
1041
+ repository: 'https://github.com/rocky2431/ultra-builder-pro-cli',
1042
+ license: pkg.license || 'MIT',
1043
+ keywords: ['codex', 'skills', 'agents', 'hooks', 'mcp', 'ultra-builder-pro'],
1044
+ skills: './skills/',
1045
+ mcpServers: './.mcp.json',
1046
+ interface: {
1047
+ displayName: 'Ultra Builder Pro',
1048
+ shortDescription: 'Codex-native engineering workflows and verification gates.',
1049
+ longDescription: 'Complete Codex adaptation of Ultra Builder Pro skills, command workflows, custom agents, lifecycle hooks, and project-local MCP state.',
1050
+ developerName: typeof pkg.author === 'string' ? pkg.author : 'Ultra Builder Pro contributors',
1051
+ category: 'Developer Tools',
1052
+ capabilities: ['Interactive', 'Write'],
1053
+ websiteURL: pkg.homepage,
1054
+ defaultPrompt: [
1055
+ 'Initialize this project with the Ultra Builder Pro workflow.',
1056
+ 'Run the Ultra review pipeline on my current changes.',
1057
+ 'Show the current Ultra project status and next action.',
1058
+ ],
1059
+ brandColor: '#111827',
1060
+ },
1061
+ };
1062
+ writeAtomic(path.join(pluginRoot, '.codex-plugin', 'plugin.json'), JSON.stringify(manifest, null, 2) + '\n');
1063
+ writeAtomic(path.join(pluginRoot, '.ubp-managed'), JSON.stringify({ source: 'ubp', adapter: 'codex', version }, null, 2) + '\n');
1064
+ return { root: pluginRoot, version, skills: installedSkills.sort() };
1065
+ }
1066
+
1067
+ function tomlMultiline(value) {
1068
+ const escaped = String(value)
1069
+ .replace(/\\/g, '\\\\')
1070
+ .replace(/"""/g, '\\"\\"\\"')
1071
+ .replace(/\r/g, '')
1072
+ .replace(/\u0000/g, '');
1073
+ return `"""\\\n${escaped.trim()}\n"""`;
1074
+ }
1075
+
1076
+ function buildAgentToml(sourceText) {
1077
+ const { fm, body } = parseFrontmatter(sourceText);
1078
+ const name = fm.name;
1079
+ const description = adaptHostText(String(fm.description || `${name} Ultra Builder Pro agent.`), name)
1080
+ .replace(/\s+/g, ' ')
1081
+ .trim();
1082
+ let instructions = adaptHostText(body, name);
1083
+ instructions = instructions.replace(
1084
+ /Consult your agent memory[^\n]*/g,
1085
+ 'Use the current checkout and parent-supplied context; do not assume persistent custom-agent memory.',
1086
+ );
1087
+ instructions = `You are a native Codex custom agent. Stay inside the delegated scope, preserve unrelated changes, and return concise evidence to the parent task.\n\n${instructions}`;
1088
+ return [
1089
+ `# ${MANAGED_MARKER}`,
1090
+ `name = ${JSON.stringify(name)}`,
1091
+ `description = ${JSON.stringify(description)}`,
1092
+ 'model_reasoning_effort = "high"',
1093
+ `developer_instructions = ${tomlMultiline(instructions)}`,
1094
+ '',
1095
+ ].join('\n');
1096
+ }
1097
+
1098
+ function installAgents({ repoRoot, configDir }) {
1099
+ const sourceRoot = path.join(repoRoot, 'agents');
1100
+ const targetRoot = path.join(configDir, 'agents');
1101
+ ensureDir(targetRoot);
1102
+ const installed = [];
1103
+ for (const file of fs.readdirSync(sourceRoot).filter((name) => name.endsWith('.md')).sort()) {
1104
+ const targetFile = path.join(targetRoot, file.replace(/\.md$/, '.toml'));
1105
+ if (fs.existsSync(targetFile)) {
1106
+ const existing = fs.readFileSync(targetFile, 'utf8');
1107
+ if (!existing.startsWith(`# ${MANAGED_MARKER}`)) {
1108
+ throw new Error(`refusing to overwrite unmanaged Codex agent: ${targetFile}`);
1109
+ }
1110
+ }
1111
+ writeAtomic(targetFile, buildAgentToml(fs.readFileSync(path.join(sourceRoot, file), 'utf8')));
1112
+ installed.push(path.basename(targetFile));
1113
+ }
1114
+ return { root: targetRoot, installed };
1115
+ }
1116
+
1117
+ module.exports = {
1118
+ PLUGIN_NAME,
1119
+ MANAGED_MARKER,
1120
+ COMMAND_NAMES,
1121
+ adaptHostText,
1122
+ buildCommandMap,
1123
+ buildHooksManifest,
1124
+ buildMcpRuntime,
1125
+ buildPlugin,
1126
+ buildAgentToml,
1127
+ installAgents,
1128
+ };